2、React组件基础:函数组件与类组件、Props与State、组件生命周期
好,咱们今天聊聊React组件的根基。说实话,这部分内容我每次带新人时都会反复强调。因为组件就是React应用的砖瓦,你连砖瓦都搞不明白,后面搭房子肯定要塌。
2.1 函数组件 vs 类组件
先说说最直观的区别。函数组件就是一个普通的JavaScript函数,接收props,返回JSX。类组件呢?它是一个ES6 class,必须继承React.Component,还得有个render方法。
我个人习惯,现在新项目一律用函数组件。为什么?因为简单。你想想看,类组件里那些this绑定、constructor、生命周期方法,写起来多累啊。
函数组件示例:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
类组件示例:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
看到区别了吗?函数组件直接拿props当参数,类组件得用this.props。我在项目中遇到过好几次,新人把类组件里的this.props写成props,结果页面白屏了。嗯,这种坑踩过一次就记住了。
我的建议:新项目优先用函数组件 + Hooks。除非你维护的是老项目,否则别碰类组件了。
2.2 Props:组件的输入
Props说白了就是父组件传给子组件的数据。它是只读的,你千万别在子组件里修改props。为什么?因为React是单向数据流,改了父组件也不知道,容易出bug。
// 父组件
function App() {
return <Child name="张三" age={25} />;
}
// 子组件
function Child(props) {
return <p>我叫{props.name},今年{props.age}岁</p>;
}
这里有个细节要注意:props可以传任何类型——字符串、数字、对象、函数,甚至React元素。我在项目中经常传回调函数给子组件,让子组件通知父组件「我这边发生了什么事」。
避坑指南:我曾经遇到一个bug,子组件里直接修改了props中的对象属性。虽然对象引用没变,但属性值变了,导致父组件状态和UI不一致。排查了一下午才发现。记住:props不可变!
2.3 State:组件的记忆
State和props最大的区别是什么?State是组件内部自己管理的数据,可以变化。props是外部传进来的,不能改。
在类组件里,state是个对象,用this.setState来更新。在函数组件里,我们用useState这个Hook。
类组件中的State:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>计数:{this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
函数组件中的State:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
你发现没有?函数组件用useState,代码量少了一半。而且不用写constructor,不用操心this绑定。我个人觉得,这就是React团队推函数组件的原因——开发体验好太多了。
小技巧:setState是异步的。如果你要基于当前state计算新值,最好传一个函数:setCount(prev => prev + 1)。这样能避免多次调用时的竞态问题。
2.4 组件生命周期
生命周期这个概念,说白了就是组件从出生到死亡的过程。类组件有明确的生命周期方法,函数组件用useEffect来模拟。
| 阶段 | 类组件方法 | 函数组件等效 | 说明 |
|---|---|---|---|
| 挂载 | componentDidMount | useEffect(() => {}, []) | 组件第一次渲染到DOM后执行 |
| 更新 | componentDidUpdate | useEffect(() => {}, [deps]) | 组件更新后执行,依赖变化时触发 |
| 卸载 | componentWillUnmount | useEffect的清理函数 | 组件从DOM移除前执行 |
我刚开始学React时,总觉得生命周期方法太多了,记不住。后来发现,其实大部分场景只需要关注三个:挂载时发请求、更新时做同步、卸载时清理资源。
类组件生命周期示例:
class UserProfile extends React.Component {
componentDidMount() {
// 组件挂载后,请求用户数据
fetchUser(this.props.userId);
}
componentDidUpdate(prevProps) {
// 如果userId变了,重新请求
if (prevProps.userId !== this.props.userId) {
fetchUser(this.props.userId);
}
}
componentWillUnmount() {
// 组件卸载前,清理定时器或取消请求
clearTimeout(this.timer);
}
render() {
return <div>用户信息...</div>;
}
}
函数组件生命周期等效:
function UserProfile({ userId }) {
useEffect(() => {
fetchUser(userId);
// 清理函数,相当于componentWillUnmount
return () => {
clearTimeout(timer);
};
}, [userId]); // 依赖数组,相当于componentDidUpdate的对比逻辑
return <div>用户信息...</div>;
}
你看,函数组件用useEffect一个Hook,就把三个生命周期方法全搞定了。是不是清爽多了?
注意:我曾经在项目里看到有人把useEffect的依赖数组写成空数组,但里面却用了外部变量。结果组件更新时,useEffect不执行,bug藏得很深。记住:依赖数组里要列出所有你用到的外部变量。
2.5 总结一下
好了,咱们把今天的内容捋一捋:
- 函数组件 vs 类组件:函数组件更简洁,推荐新项目使用
- Props:父传子,只读不可改
- State:组件内部状态,可变,用setState或useState更新
- 生命周期:类组件有明确方法,函数组件用useEffect模拟
说实话,这些基础概念你花一天时间就能掌握。但真正用好它们,需要在实际项目中反复练习。我建议你从现在开始,把所有新组件都写成函数组件,用useState和useEffect。等你写了几十个组件后,自然就熟练了。
下一章咱们聊React Hooks的进阶用法,包括自定义Hook、useReducer这些好东西。到时候你会发现,函数组件比类组件强大太多了。