Appearance
类组件和函数组件的区别
类组件一直是构建有状态和复杂组件的主要方式。在 React 16.8 引入 Hooks 之后,函数组件变得空前强大,并逐渐成为社区的主流选择。
类组件
类组件都是使用class的写法,不能使用hooks,代码量更多,有完整的生命周期,内部方法需要绑定this
jsx
import React, { Component } from 'react';
class Counter extends Component {
// --- 挂载阶段 ---
// 1. 构造函数:组件被创建时首先调用
constructor(props) {
super(props);
this.state = {
count: 0,
data: null,
};
console.log(' constructor() - 组件初始化');
}
// 3. 组件挂载后:组件已经被渲染到DOM中
componentDidMount() {
console.log('componentDidMount() - 组件已挂载到DOM');
console.log('适合执行:API请求、添加事件监听等');
// 模拟API请求
this.timerID = setTimeout(() => {
this.setState({ data: '从服务器获取的数据' });
console.log('componentDidMount() - 异步数据已获取');
}, 2000);
}
// --- 更新阶段 ---
// 1. 是否应该更新?:在接收到新的props或state后,但在重新渲染前调用
shouldComponentUpdate(nextProps, nextState) {
console.log('更新shouldComponentUpdate()');
// 可以在此进行性能优化,如果返回 false,后续的 render 和 componentDidUpdate 都不会执行
return true;
}
// 3. 组件更新后:在组件更新并渲染到DOM后立即调用
componentDidUpdate(prevProps, prevState) {
console.log('更新componentDidUpdate()');
// 可以在此根据 props 的变化执行副作用,例如当用户ID变化时重新请求数据
if (this.props.user !== prevProps.user) {
console.log('user prop 发生变化,可以执行新的API请求');
}
}
// --- 卸载阶段 ---
// 组件即将卸载:在组件从DOM中移除之前调用
componentWillUnmount() {
console.log('componentWillUnmount() - 组件即将卸载');
console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
clearTimeout(this.timerID); // 清理在 componentDidMount 中创建的定时器
}
// --- 通用方法 ---
// 点击事件处理函数
handleIncrement = () => {
this.setState(prevState => ({
count: prevState.count + 1,
}));
};
// 2. 渲染:无论是挂载还是更新,都需要这个方法来返回UI
render() {
console.log('render() - 正在渲染UI');
return (
<div style={{ border: '2px solid steelblue', padding: '10px', marginTop: '15px' }}>
<h3>LifecycleLogger 组件</h3>
<p>传入的用户 Prop: {this.props.user}</p>
<p>内部的计数 State: {this.state.count}</p>
<p>获取的数据: {this.state.data || '加载中...'}</p>
<button onClick={this.handleIncrement}>增加 Count</button>
</div>
);
}
}
export default Counter;import React, { Component } from 'react';
class Counter extends Component {
// --- 挂载阶段 ---
// 1. 构造函数:组件被创建时首先调用
constructor(props) {
super(props);
this.state = {
count: 0,
data: null,
};
console.log(' constructor() - 组件初始化');
}
// 3. 组件挂载后:组件已经被渲染到DOM中
componentDidMount() {
console.log('componentDidMount() - 组件已挂载到DOM');
console.log('适合执行:API请求、添加事件监听等');
// 模拟API请求
this.timerID = setTimeout(() => {
this.setState({ data: '从服务器获取的数据' });
console.log('componentDidMount() - 异步数据已获取');
}, 2000);
}
// --- 更新阶段 ---
// 1. 是否应该更新?:在接收到新的props或state后,但在重新渲染前调用
shouldComponentUpdate(nextProps, nextState) {
console.log('更新shouldComponentUpdate()');
// 可以在此进行性能优化,如果返回 false,后续的 render 和 componentDidUpdate 都不会执行
return true;
}
// 3. 组件更新后:在组件更新并渲染到DOM后立即调用
componentDidUpdate(prevProps, prevState) {
console.log('更新componentDidUpdate()');
// 可以在此根据 props 的变化执行副作用,例如当用户ID变化时重新请求数据
if (this.props.user !== prevProps.user) {
console.log('user prop 发生变化,可以执行新的API请求');
}
}
// --- 卸载阶段 ---
// 组件即将卸载:在组件从DOM中移除之前调用
componentWillUnmount() {
console.log('componentWillUnmount() - 组件即将卸载');
console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
clearTimeout(this.timerID); // 清理在 componentDidMount 中创建的定时器
}
// --- 通用方法 ---
// 点击事件处理函数
handleIncrement = () => {
this.setState(prevState => ({
count: prevState.count + 1,
}));
};
// 2. 渲染:无论是挂载还是更新,都需要这个方法来返回UI
render() {
console.log('render() - 正在渲染UI');
return (
<div style={{ border: '2px solid steelblue', padding: '10px', marginTop: '15px' }}>
<h3>LifecycleLogger 组件</h3>
<p>传入的用户 Prop: {this.props.user}</p>
<p>内部的计数 State: {this.state.count}</p>
<p>获取的数据: {this.state.data || '加载中...'}</p>
<button onClick={this.handleIncrement}>增加 Count</button>
</div>
);
}
}
export default Counter;| 生命周期方法 | 调用时机 | 主要用途 |
|---|---|---|
| constructor() | 组件实例创建时 | 初始化 state、绑定 this |
| render() | 挂载和更新时 | 返回JSX,描述UI |
| componentDidMount() | 组件挂载到DOM后 | 网络请求、DOM操作、添加订阅 |
| componentDidUpdate() | state或props更新后 | 基于props变化执行副作用 |
| componentWillUnmount() | 组件从DOM移除前 | 清理定时器、取消订阅、移除事件监听器 |
| shouldComponentUpdate() | render()前,接收新props或state时 | 性能优化,避免不必要的渲染 |
函数组件
函数组件就简单很多了,代码简洁,加上hooks的引入可以处理复杂的逻辑,没有this关键字
jsx
import React, { useState, useEffect } from 'react';
function HooksLogger({ user }) {
// 使用 useState 来管理 count 和 data 状态
// 相当于类组件 constructor 中的 this.state
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
console.log('render() - 函数组件正在渲染UI');
// --- 使用 useEffect 处理副作用 ---
// 1. 模拟 componentDidMount
// 第二个参数是空数组 [],表示这个 effect 只在组件首次挂载后执行一次。
useEffect(() => {
console.log('useEffect (mount) - 组件已挂载到DOM');
console.log('适合执行:API请求、添加事件监听等');
// 模拟API请求
const timerID = setTimeout(() => {
setData('从服务器获取的数据');
console.log('useEffect (mount) - 异步数据已获取');
}, 2000);
// 清理函数:将在组件卸载时执行,模拟 componentWillUnmount
return () => {
console.log('useEffect (unmount) - 组件即将卸载');
console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
clearTimeout(timerID);
};
}, []); //空数组是关键!
// 2. 模拟 componentDidUpdate
// 这个 effect 依赖于 `user` 和 `count`。当它们任何一个发生变化时,effect 都会重新执行。
useEffect(() => {
// 为了避免在挂载时也打印“更新”日志,可以使用一个 ref 来判断是否是首次渲染
console.log('useEffect (update) - 组件已更新');
// 示例:可以根据 user 的变化执行操作
if (user) {
document.title = `当前用户: ${user}`;
}
}, [user, count]); //依赖项数组是关键!
// --- 事件处理函数 ---
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
};
return (
<div style={{ border: '2px solid mediumseagreen', padding: '10px', marginTop: '15px' }}>
<h3>HooksLogger 组件</h3>
<p>传入的用户 Prop: {user}</p>
<p>内部的计数 State: {count}</p>
<p>获取的数据: {data || '加载中...'}</p>
<button onClick={handleIncrement}>增加 Count</button>
</div>
);
}
export default HooksLogger;import React, { useState, useEffect } from 'react';
function HooksLogger({ user }) {
// 使用 useState 来管理 count 和 data 状态
// 相当于类组件 constructor 中的 this.state
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
console.log('render() - 函数组件正在渲染UI');
// --- 使用 useEffect 处理副作用 ---
// 1. 模拟 componentDidMount
// 第二个参数是空数组 [],表示这个 effect 只在组件首次挂载后执行一次。
useEffect(() => {
console.log('useEffect (mount) - 组件已挂载到DOM');
console.log('适合执行:API请求、添加事件监听等');
// 模拟API请求
const timerID = setTimeout(() => {
setData('从服务器获取的数据');
console.log('useEffect (mount) - 异步数据已获取');
}, 2000);
// 清理函数:将在组件卸载时执行,模拟 componentWillUnmount
return () => {
console.log('useEffect (unmount) - 组件即将卸载');
console.log('适合执行:清理定时器、取消网络请求、移除事件监听');
clearTimeout(timerID);
};
}, []); //空数组是关键!
// 2. 模拟 componentDidUpdate
// 这个 effect 依赖于 `user` 和 `count`。当它们任何一个发生变化时,effect 都会重新执行。
useEffect(() => {
// 为了避免在挂载时也打印“更新”日志,可以使用一个 ref 来判断是否是首次渲染
console.log('useEffect (update) - 组件已更新');
// 示例:可以根据 user 的变化执行操作
if (user) {
document.title = `当前用户: ${user}`;
}
}, [user, count]); //依赖项数组是关键!
// --- 事件处理函数 ---
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
};
return (
<div style={{ border: '2px solid mediumseagreen', padding: '10px', marginTop: '15px' }}>
<h3>HooksLogger 组件</h3>
<p>传入的用户 Prop: {user}</p>
<p>内部的计数 State: {count}</p>
<p>获取的数据: {data || '加载中...'}</p>
<button onClick={handleIncrement}>增加 Count</button>
</div>
);
}
export default HooksLogger;| 核心思想 | 按功能划分: 将相关逻辑聚合在同一个 useEffect 中。一个 useEffect 负责一个功能的所有方面(设置、更新、清理)。 |
| 状态管理 | useState,可以是任何数据类型,状态和其更新函数是独立的,更灵活。 |
| 副作用 | 统一使用 useEffect Hook。通过依赖项数组 [] 控制执行时机。 |
| 代码简洁性 | 代码更少,更易读,没有 this 的困扰,逻辑更内聚。 |
小洛的前端技术博客