第4章:React组件基础——函数组件与类组件、JSX语法详解、组件的props与state、事件处理
好,咱们进入正题。React 的核心就是组件。你想想看,整个页面就是一堆组件拼起来的。这一章我会带你彻底搞懂组件的基础,包括怎么写、怎么用、怎么传数据、怎么处理交互。
4.1 函数组件与类组件
React 有两种定义组件的方式:函数组件和类组件。我个人习惯用函数组件,因为更简洁。但类组件你也要懂,老项目里到处都是。
4.1.1 函数组件
说白了就是一个 JavaScript 函数。接收 props,返回 JSX。
// 最简单的函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 或者用箭头函数
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
嗯,这里要注意:函数组件没有自己的实例,也没有生命周期。但在 React 16.8 之后,有了 Hooks,函数组件也能做类组件能做的事了。
4.1.2 类组件
类组件是用 ES6 class 定义的。必须继承 React.Component,而且必须有 render 方法。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
我在项目中遇到过这样的情况:一个老项目全是类组件,后来新功能用函数组件写,结果团队成员在两种风格之间切换,容易搞混。我的建议是——统一风格。新项目直接用函数组件 + Hooks。
核心区别:
- 函数组件:无状态(以前),简洁,容易测试
- 类组件:有状态,有生命周期,适合复杂逻辑
- 现在:函数组件 + Hooks 可以替代大部分类组件场景
4.2 JSX语法详解
JSX 是 JavaScript 的语法扩展。长得像 HTML,但其实是 JS。我第一次看到 JSX 时觉得怪怪的,但用了一天就真香了。
4.2.1 JSX 的基本规则
- 必须有一个根元素:可以用
<div>或者<React.Fragment>(简写<></>) - 属性用驼峰命名:
className代替class,onClick代替onclick - 花括号里写 JS 表达式:
{变量}、{函数调用()}、{条件 ? 'a' : 'b'} - 不能写 if/else 语句:但可以用三元表达式或者逻辑与
function Greeting({ isLoggedIn }) {
return (
<>
<h1>{isLoggedIn ? '欢迎回来' : '请登录'}</h1>
<p className="subtitle">今天是个好日子</p>
</>
);
}
我曾经踩过的坑:在 JSX 里直接写 if (condition) { ... },结果页面直接报错。记住,花括号里只能放表达式,不能放语句。
4.2.2 JSX 中的列表渲染
用 map() 方法。每个子元素必须有一个唯一的 key 属性。
const todos = ['学习React', '写项目', '复习'];
function TodoList() {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
);
}
小技巧:key 最好用数据的 id,不要用 index。为什么?因为列表顺序变了,React 会搞混。我吃过这个亏,列表删除一项后,后面的组件状态全乱了。
4.3 组件的 props 与 state
props 和 state 是组件的两个核心概念。说白了,props 是外部传进来的,state 是组件自己管的。
4.3.1 props:父组件给子组件的数据
props 是只读的。你不能在子组件里修改 props。这是 React 的核心理念——单向数据流。
// 父组件
function App() {
return <User name="张三" age={25} />;
}
// 子组件
function User(props) {
return (
<div>
<p>姓名:{props.name}</p>
<p>年龄:{props.age}</p>
</div>
);
}
你想想看,如果子组件能随便改 props,那数据流就乱套了。所以 React 强制 props 只读。
4.3.2 state:组件内部的状态
state 是组件自己管理的数据。变了,组件就重新渲染。
类组件的 state:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>计数:{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
加1
</button>
</div>
);
}
}
函数组件的 state(用 useState):
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>
加1
</button>
</div>
);
}
重要提醒:
- 类组件用
this.setState()更新 state,是合并的 - 函数组件用
setCount()更新 state,是替换的 - 永远不要直接修改 state:
this.state.count = 1是错的
4.4 事件处理
React 的事件处理跟原生 DOM 事件有点像,但有几个关键区别。
4.4.1 基本用法
- 事件名用驼峰:
onClick、onChange、onSubmit - 传一个函数,不是字符串:
onClick={handleClick},不是onClick="handleClick()" - 要阻止默认行为,必须显式调用
e.preventDefault()
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单提交了');
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">提交</button>
</form>
);
}
4.4.2 事件中的 this 问题(类组件)
我记得刚学 React 时,在类组件里写事件处理,this 指向经常搞错。看这个例子:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
// 这行很重要!绑定 this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isOn: !prevState.isOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isOn ? '开' : '关'}
</button>
);
}
}
如果不绑定 this,handleClick 里的 this 就是 undefined。三种解决办法:
- 在 constructor 里 bind(如上)
- 用箭头函数定义方法:
handleClick = () => { ... } - 在 render 里用箭头函数:
onClick={() => this.handleClick()}(不推荐,每次渲染都创建新函数)
我的建议:用函数组件 + Hooks 就没有 this 的问题了。这也是我推荐新项目用函数组件的原因之一。
4.4.3 向事件处理函数传参
有时候你需要传额外的参数。比如列表中的每一项都有一个删除按钮:
function TodoList() {
const deleteItem = (id) => {
console.log('删除', id);
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => deleteItem(todo.id)}>删除</button>
</li>
))}
</ul>
);
}
这里用箭头函数包一层,就能把 todo.id 传进去。注意不要写成 onClick={deleteItem(todo.id)},那样会立即执行函数。
4.5 本章小结
这一章我们覆盖了 React 组件的基础知识。说白了,就是三件事:
- 怎么写组件:函数组件还是类组件,我推荐函数组件
- 怎么写 JSX:记住花括号里放表达式,属性用驼峰
- 怎么管理数据:props 是外部传入的,state 是内部管理的
- 怎么处理事件:注意 this 绑定,用箭头函数传参
下一章我们会深入组件的生命周期和 Hooks,到时候你会发现,函数组件真的香。