React 作為當今最流行的前端框架之一,其組件生命周期是每個 React 開發者必須掌握的核心概念。本文將全面剖析 React 組件的生命周期,包括類組件的各個生命周期方法和函數組件如何使用 Hooks 模擬生命周期行為,幫助開發者編寫更高效、更健壯的 React 應用。
一、React 組件生命周期概述
React 組件的生命周期指的是一個組件從創建、更新到銷毀的整個過程。在這個過程中,React 提供了許多"生命周期方法",允許開發者在特定的階段執行自定義代碼。理解這些生命周期方法對于控制組件行為、優化性能以及管理副作用至關重要。
React 的生命周期可以分為三個主要階段:
-
掛載階段(Mounting):組件被創建并插入到 DOM 中
-
更新階段(Updating):組件的 props 或 state 發生變化時的重新渲染過程
-
卸載階段(Unmounting):組件從 DOM 中移除
此外,React 16 還引入了錯誤處理生命周期方法,用于捕獲和處理組件樹中的 JavaScript 錯誤。
二、類組件的生命周期詳解
1. 掛載階段(Mounting)
掛載階段是組件第一次被創建并插入到 DOM 中的過程,包含以下生命周期方法:
constructor()
constructor(props) {super(props);this.state = { count: 0 };this.handleClick = this.handleClick.bind(this);
}
-
最先執行的生命周期方法
-
必須調用?
super(props)
,否則?this.props
?將會是 undefined -
唯一可以直接修改?
this.state
?的地方 -
用于初始化 state 和綁定事件處理方法
static getDerivedStateFromProps()
static getDerivedStateFromProps(props, state) {if (props.value !== state.prevValue) {return {value: props.value,prevValue: props.value};}return null;
}
-
在 render 方法之前調用,無論是初始掛載還是后續更新
-
應返回一個對象來更新 state,或返回 null 不更新
-
用于 state 依賴于 props 變化的罕見情況
-
此方法無權訪問組件實例(即不能使用 this)
render()
render() {return <div>{this.state.count}</div>;
}
-
類組件中唯一必須實現的方法
-
應該是一個純函數,不修改組件狀態,不與瀏覽器交互
-
返回以下類型之一:
-
React 元素(JSX)
-
數組或 fragments
-
Portals
-
字符串或數值(渲染為文本節點)
-
布爾值或 null(不渲染任何內容)
-
componentDidMount()
componentDidMount() {// 典型用法:fetchData().then(data => this.setState({ data }));// 或this.subscription = dataSource.subscribe(this.handleDataChange);
}
-
組件掛載(插入 DOM 樹)后立即調用
-
適合執行有副作用的操作:
-
網絡請求
-
設置訂閱
-
手動操作 DOM
-
-
可以在此處直接調用 setState(),但會觸發額外渲染
2. 更新階段(Updating)
當組件的 props 或 state 發生變化時,會觸發更新階段的生命周期方法:
static getDerivedStateFromProps()
-
同掛載階段,在每次渲染前調用
shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState) {// 只有當count變化時才重新渲染return nextState.count !== this.state.count;
}
-
決定組件是否應該更新
-
返回 false 可以阻止組件重新渲染
-
主要用于性能優化
-
不建議深層比較或使用 JSON.stringify(),影響性能
-
考慮使用 PureComponent 替代手動實現
render()
-
同掛載階段
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState) {if (prevProps.items.length < this.props.items.length) {const list = this.listRef.current;return list.scrollHeight - list.scrollTop;}return null;
}
-
在最近一次渲染輸出(提交到 DOM 節點)之前調用
-
使得組件能在 DOM 變化前捕獲一些信息(如滾動位置)
-
返回值將作為 componentDidUpdate() 的第三個參數
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot) {if (this.props.userID !== prevProps.userID) {this.fetchData(this.props.userID);}if (snapshot !== null) {const list = this.listRef.current;list.scrollTop = list.scrollHeight - snapshot;}
}
-
更新完成后調用(首次渲染不會執行)
-
適合執行有副作用的操作:
-
網絡請求(需比較 props)
-
DOM 操作
-
-
可以調用 setState(),但必須包裹在條件語句中,否則會導致無限循環
3. 卸載階段(Unmounting)
componentWillUnmount()
componentWillUnmount() {clearInterval(this.timerID);this.subscription.unsubscribe();
}
-
組件卸載及銷毀前調用
-
用于執行必要的清理操作:
-
清除定時器
-
取消網絡請求
-
清理訂閱
-
-
不應調用 setState(),因為組件永遠不會重新渲染
4. 錯誤處理
React 16 引入了錯誤邊界的概念,用于捕獲子組件樹中的 JavaScript 錯誤。
static getDerivedStateFromError()
static getDerivedStateFromError(error) {return { hasError: true };
}
-
在后代組件拋出錯誤后被調用
-
接收錯誤作為參數
-
應返回一個狀態對象以更新 state,用于渲染備用 UI
componentDidCatch()
componentDidCatch(error, info) {logErrorToService(error, info.componentStack);
}
-
在后代組件拋出錯誤后被調用
-
接收兩個參數:
-
error - 拋出的錯誤
-
info - 包含 componentStack 鍵的對象
-
-
用于記錄錯誤信息
三、函數組件的"生命周期"
隨著 React Hooks 的引入,函數組件現在也能實現類組件的生命周期功能。以下是常用 Hooks 與生命周期方法的對應關系:
useState - 狀態管理
const [count, setCount] = useState(0);
-
相當于類組件中的 this.state 和 this.setState
-
可以在函數組件中添加局部 state
useEffect - 副作用管理
useEffect(() => {// 相當于 componentDidMount 和 componentDidUpdatedocument.title = `You clicked ${count} times`;return () => {// 相當于 componentWillUnmount// 清理函數};
}, [count]); // 僅在 count 更改時更新
-
組合了 componentDidMount, componentDidUpdate 和 componentWillUnmount
-
第一個參數是 effect 函數,第二個參數是依賴數組
-
返回的函數是清理函數,在組件卸載時執行
useLayoutEffect
useLayoutEffect(() => {// 在 DOM 更新后同步執行const { width } = node.getBoundingClientRect();setWidth(width);
});
-
與 useEffect 簽名相同,但調用時機不同
-
在 DOM 變更后同步觸發
-
適用于需要讀取 DOM 布局并同步重新渲染的情況
useMemo 和 useCallback - 性能優化
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
-
相當于 shouldComponentUpdate 的優化
-
用于避免不必要的計算和渲染
四、新舊生命周期對比與最佳實踐
React 16.3 對生命周期方法進行了重大調整,廢棄了一些不安全的生命周期方法:
廢棄的方法:
-
componentWillMount
-
componentWillReceiveProps
-
componentWillUpdate
這些方法被標記為不安全主要是因為:
-
它們經常被誤用和濫用
-
在異步渲染中可能導致問題
-
容易引入副作用和錯誤
最佳實踐建議:
-
將數據獲取移到 componentDidMount 或 useEffect 中
-
使用 getDerivedStateFromProps 替代 componentWillReceiveProps
-
使用 getSnapshotBeforeUpdate 替代 componentWillUpdate
-
考慮使用函數組件和 Hooks 替代類組件
-
謹慎使用派生 state,多數情況下可以通過提升 state 或受控組件解決
五、實際應用場景示例
場景1:數據獲取
class UserProfile extends React.Component {state = { user: null, loading: true };async componentDidMount() {const user = await fetchUser(this.props.userId);this.setState({ user, loading: false });}async componentDidUpdate(prevProps) {if (this.props.userId !== prevProps.userId) {this.setState({ loading: true });const user = await fetchUser(this.props.userId);this.setState({ user, loading: false });}}componentWillUnmount() {// 取消可能的未完成請求}render() {// 渲染用戶信息}
}
場景2:滾動位置恢復
class ScrollList extends React.Component {getSnapshotBeforeUpdate(prevProps) {if (prevProps.items.length < this.props.items.length) {const list = this.listRef.current;return list.scrollHeight - list.scrollTop;}return null;}componentDidUpdate(prevProps, prevState, snapshot) {if (snapshot !== null) {const list = this.listRef.current;list.scrollTop = list.scrollHeight - snapshot;}}render() {return (<div ref={this.listRef}>{/* 列表內容 */}</div>);}
}
場景3:錯誤邊界
class ErrorBoundary extends React.Component {state = { hasError: false };static getDerivedStateFromError(error) {return { hasError: true };}componentDidCatch(error, info) {logErrorToService(error, info.componentStack);}render() {if (this.state.hasError) {return <h1>Something went wrong.</h1>;}return this.props.children; }
}
總結
React 組件的生命周期是 React 應用的核心機制,理解這些生命周期方法對于構建高效、可靠的 React 應用至關重要。隨著 React 的發展,生命周期方法也在不斷演進,從類組件的各種生命周期方法到函數組件的 Hooks,React 提供了更靈活、更簡潔的方式來管理組件的生命周期。
對于新項目,建議優先考慮使用函數組件和 Hooks,它們提供了更簡潔的代碼組織和更強大的組合能力。對于現有項目,了解類組件的生命周期仍然很重要,特別是在維護老代碼時。
記住,生命周期方法是你控制組件行為的工具,正確使用它們可以:
-
優化性能
-
管理副作用
-
處理錯誤
-
保持代碼整潔和可維護
通過掌握 React 組件的生命周期,你將能夠構建更強大、更可靠的 React 應用程序。