生命周期不是八股文,而是性能卡点、Bug 源头、优化入口 。
本文用「类组件」+「Hooks」两条主线,把生命周期放到 真实业务场景 里讲透:首屏加载、长列表、弹窗动画、热更新、异常兜底......看完就能直接落地。
一、总览:两条生命周期路线
版本 | 生命周期形式 | 2025 推荐度 |
---|---|---|
类组件 | constructor → render → componentDidMount/Update/Unmount |
⚠️ 老项目维护 |
函数组件 | useState → useEffect → useLayoutEffect → useInsertionEffect |
✅ 新项目首选 |
记住口诀:函数组件优先,类组件救命。
二、类组件生命周期 × 4 大场景
场景 1:首屏骨架屏 + 数据并行加载
jsx
class ArticlePage extends React.Component {
state = { article: null, loading: true };
// 1️⃣ 构造函数:只做一次初始化
constructor(props) {
super(props);
this.id = props.match.params.id;
}
// 2️⃣ 挂载阶段:并行拉文章 & 推荐列表
async componentDidMount() {
// 并行 Promise.all,骨架屏已占位
const [article, recommends] = await Promise.all([
fetchArticle(this.id),
fetchRecommends(this.id)
]);
this.setState({ article, recommends, loading: false });
}
// 3️⃣ 更新阶段:路由变化重新拉数据
async componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.setState({ loading: true });
const article = await fetchArticle(this.props.match.params.id);
this.setState({ article, loading: false });
}
}
// 4️⃣ 卸载阶段:取消未完成的请求
componentWillUnmount() {
this.controller?.abort();
}
render() {
const { article, loading } = this.state;
return loading ? <Skeleton /> : <ArticleDetail {...article} />;
}
}
要点:
componentDidMount
做首屏并行请求,骨架屏兜底。componentWillUnmount
清掉请求,防止内存泄漏。
场景 2:长列表滚动加载 + 防抖
jsx
class ScrollList extends React.Component {
state = { list: [], page: 1, loading: false };
componentDidMount() {
this.loadMore();
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll = throttle(() => {
if (this.isBottom()) this.loadMore();
}, 200);
loadMore = async () => {
this.setState({ loading: true });
const newData = await fetchList(this.state.page);
this.setState(prev => ({
list: [...prev.list, ...newData],
page: prev.page + 1,
loading: false
}));
};
render() {
return (
<div>
{this.state.list.map(item => <Card key={item.id} {...item} />)}
{this.state.loading && <Spinner />}
</div>
);
}
}
要点 :
在
componentDidMount
加滚动事件,在componentWillUnmount
移除,避免内存泄漏。
三、函数组件 Hooks × 4 大场景
场景 3:弹窗动画 + 焦点管理(useLayoutEffect)
jsx
function Modal({ visible, children }) {
const ref = useRef(null);
// 1️⃣ 同步阶段:测量 DOM → 立即计算动画起点
useLayoutEffect(() => {
if (visible) {
// 读取布局 → 写回 transform
const { height } = ref.current.getBoundingClientRect();
ref.current.style.transform = `translateY(${-height}px)`;
}
}, [visible]);
// 2️⃣ 异步阶段:动画结束后清理
useEffect(() => {
if (visible) {
ref.current?.focus();
return () => ref.current?.blur();
}
}, [visible]);
return visible ? <div ref={ref}>{children}</div> : null;
}
要点:
useLayoutEffect
在浏览器绘制前执行,防止动画抖动。useEffect
负责副作用(聚焦、清理)。
场景 4:热更新保留滚动位置(useInsertionEffect)
jsx
function ChatWindow() {
const [messages, setMessages] = useState([]);
const listRef = useRef(null);
// 只在 Fast-Refresh 期间插入样式,不影响渲染
useInsertionEffect(() => {
document.head.insertAdjacentHTML('beforeend', `
<style>.chat-scroll { scroll-behavior: smooth }</style>
`);
}, []);
useEffect(() => {
listRef.current.scrollTop = listRef.current.scrollHeight;
}, [messages]);
return (
<ul ref={listRef} className="chat-scroll">
{messages.map(m => <li key={m.id}>{m.text}</li>)}
</ul>
);
}
四、异常兜底:Error Boundary & useErrorBoundary
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
// 上报 Sentry
logError(error, info);
}
render() {
return this.state.hasError ? <Fallback /> : this.props.children;
}
}
场景:路由级、模块级异常兜底,生产环境必备。
五、2025 选型建议
场景 | 推荐 |
---|---|
新项目 | 函数组件 + Hooks(useEffect/LayoutEffect) |
老代码 | 类组件生命周期维护,逐步迁移 |
性能敏感 | 使用 React.memo 、useMemo 、useCallback 减少重渲染 |
🏁 一页速查表
生命周期 | 类组件 | 函数组件 | 典型用途 |
---|---|---|---|
挂载 | componentDidMount |
useEffect(..., []) |
数据请求、事件绑定 |
更新 | componentDidUpdate |
useEffect(..., [deps]) |
数据刷新 |
卸载 | componentWillUnmount |
useEffect return 函数 |
清理、取消订阅 |
动画 | 无 | useLayoutEffect |
同步 DOM 读/写 |
异常 | componentDidCatch |
ErrorBoundary |
全局兜底 |
把这张表贴在工位,React 生命周期不再迷路!