深入理解React应用的完整生命周期流程,掌握函数组件Hooks的正确使用方式,从应用启动到组件运行,体验更简洁、可复用且逻辑集中化的状态管理和副作用处理方式。
当一个React应用启动时,从初始化到首次渲染,再到挂载完成,这一过程在函数组件中通过一系列Hooks实现。Hooks提供了一种更简洁、可复用且逻辑集中化的状态管理和副作用处理方式。
| 类组件阶段 | 函数组件Hooks | 执行时机 | 作用 |
|---|---|---|---|
| constructor | useState useRef useContext |
组件首次渲染时按顺序执行 | 初始化状态、绑定方法、获取上下文 |
| static getDerivedStateFromProps | useEffect useMemo |
每次渲染前执行 | 基于props派生state或缓存计算结果 |
| render | 组件函数返回JSX | 组件函数被调用时 | 生成虚拟DOM |
| componentDidMount | useEffect(() => {}, []) | DOM更新后异步执行 | 组件挂载后执行副作用操作 |
import React, { useState, useEffect } from 'react';
function Counter() {
// 初始化计数器状态
const [count, setCount] = useState(0);
// 组件挂载后执行副作用操作
useEffect(() => {
console.log('组件已挂载,当前计数:', count);
// 可在此处执行数据获取等操作
}, []); // 空依赖数组表示只在挂载时执行一次
return (
<div>
<p>点击次数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
组件挂载阶段是组件首次被创建并插入DOM的过程,对应到函数组件中主要通过useEffect和useLayoutEffect来处理副作用操作。
| Hooks函数 | 执行时机 | 特点 | 适用场景 |
|---|---|---|---|
| useEffect | DOM更新后 浏览器绘制后 |
异步执行 不阻塞渲染 |
数据获取 事件监听 订阅服务 |
| useLayoutEffect | DOM更新后 浏览器绘制前 |
同步执行 阻塞渲染 |
布局测量 避免视觉闪烁 |
useEffect在挂载阶段的应用:空依赖数组的用法完全替代了类组件的componentDidMount方法,是处理组件挂载后副作用的标准方式。
import React, { useState, useEffect } from 'react';
function DataDisplay() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
setData(responseData);
setError(null);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // 只在组件挂载时执行一次
if (loading) return <div>Loading...</div>;
if (error) return <div>错误:{error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
}
组件更新阶段是props或state变化导致组件重新渲染的过程,对应到函数组件中主要通过useEffect、useMemo和React.memo来处理。
在类组件中,getDerivedStateFromProps用于根据props派生state,而在函数组件中,可以通过useEffect实现相同功能。
React.memo与useMemo分别用于组件级和值级的渲染优化,避免不必要的重新渲染。
import React, { useState, useEffect, useMemo, memo } from 'react';
// 优化列表渲染性能
function Parent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3]);
// 使用useMemo缓存复杂计算结果
const formattedItems = useMemo(() => {
console.log('格式化数据...');
return items.map(item => (`Item ${item}`));
}, [items]); // 仅当items变化时重新计算
return (
<div>
<p>点击次数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setItems([...items, items.length + 1])}>
添加新项目
</button>
<MemoizedList items={formattedItems} />
</div>
);
}
// 使用React.memo优化子组件渲染
const MemoizedList = memo(function List({ items }) {
console.log('列表组件渲染');
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
});
组件卸载阶段是组件从DOM中移除的过程,对应到函数组件中主要通过useEffect的清理函数来处理。
在useEffect中返回一个函数,该函数会在组件卸载时或依赖项变化前执行,用于清理副作用,如清理定时器、取消订阅等。
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
const [intervalId, setIntervalId] = useState(null);
// 挂载时设置定时器
useEffect(() => {
const newIntervalId = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 清理函数:组件卸载或依赖项变化时执行
return () => {
clearInterval(newIntervalId);
setIntervalId(null);
};
}, []); // 空依赖数组表示只在挂载时执行一次
return (
<div>
<p>计时器已运行:{count}秒</p>
<button onClick={() => clearInterval(intervalId)}>
停止计时器
</button>
</div>
);
}
自定义Hooks是React Hooks的高级用法,允许将组件逻辑提取到可复用的函数中,提高代码复用性和维护性。
将数据获取、状态管理和错误处理封装到一个自定义Hook中,提供更简洁的API。
将表单验证逻辑提取到自定义Hook中,提高代码复用性和表单处理的一致性。
import React, { useState, useEffect, useRef } from 'react';
// 自定义Hook:封装数据请求
const useFetchData = (url, params) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const controllerRef = useRef(new AbortController());
// 数据获取函数
const fetchData = async () => {
try {
setError(null);
const signal = controllerRef.current.signal;
const response = await fetch(url, { signal, ...params });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
setData(responseData);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setError ? setError(null) : setError(null);
setLoading ? setLoading(false) : setLoading(false);
}
};
// 挂载时执行请求
useEffect(() => {
fetchData();
// 清理函数:取消请求
return () => {
controllerRef.current.abort();
};
}, [url, params]); // 当url或params变化时重新请求
return { data, loading, error, refetch: fetchData };
};
// 使用自定义Hook的组件
function DataComponent() {
const [keyword, setKeyword] = useState('');
const { data, loading, error, refetch } = useFetchData(
'/api/search',
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
// 根据keyword变化更新请求参数
useEffect(() => {
refetch({ params: { q: keyword } });
}, [keyword]); // 当keyword变化时触发重新请求
return (
<div>
<input
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
{loading && <div>Loading...</div>}
{error && <div>错误:{error.message}</div>}
{data && <div>{JSON.stringify(data)}</div>}
</div>
);
}
Hooks不仅简化了组件逻辑,还提供了更灵活的状态管理和副作用处理方式,是React开发的未来趋势。通过理解React应用的完整生命周期流程及其对应的Hooks实现,开发者可以更有效地利用Hooks构建复杂、高性能的React应用。
| 类组件生命周期 | 函数组件Hooks | 对应场景 | 特点 |
|---|---|---|---|
| constructor | useState useRef useContext |
初始化状态、绑定方法、获取上下文 | 在组件首次渲染时执行 |
| static getDerivedStateFromProps | useEffect useMemo |
基于props派生state或缓存计算结果 | 在每次渲染前执行 |
| componentDidMount | useEffect(() => {}, []) | 组件挂载后执行副作用操作 | 只在组件挂载时执行一次 |
| componentDidUpdate | useEffect useLayoutEffect |
响应props或state变化,执行副作用操作 | 可以监听特定依赖项的变化 |
| componentWillUnmount | useEffect返回的清理函数 | 清理副作用,如取消订阅、清除定时器 | 在组件卸载时执行 |