ReactJS - 组件生命周期
ReactJS - 使用 Hooks 的组件生命周期
Section titled “ReactJS - 使用 Hooks 的组件生命周期”在 React 中,组件会经历一个生命周期:挂载(mount,被添加到 DOM 中)、更新(update,由于 props 或 state 变化而重新渲染)和卸载(unmount,从 DOM 中移除)。过去,类组件使用特定的生命周期方法(例如 componentDidMount、componentWillUnmount)。在现代 React 中,函数组件主要使用 useEffect hook 来处理与这些生命周期阶段相关的 side effects(副作用)和操作。
useEffect Hook:管理 Side Effects
Section titled “useEffect Hook:管理 Side Effects”useEffect 允许您在函数组件中执行 side effects。常见的 side effects 包括数据获取、订阅或手动更改 DOM。默认情况下,它在每次渲染后运行,但您可以控制它何时运行。
其基本语法是 useEffect(setupFunction, dependenciesArray?):
setupFunction:包含 side effect 逻辑的函数。它可以选择返回一个cleanupFunction(清理函数)。dependenciesArray(可选):一个值数组(props 或 state),effect 依赖于这些值。只有当这些值在两次渲染之间发生变化时,effect 才会重新运行。如果省略,effect 在每次渲染后运行。如果提供一个空数组[],effect 只在初始渲染(挂载)后运行一次,并且清理函数在卸载时运行。
使用 useEffect 模拟生命周期阶段
Section titled “使用 useEffect 模拟生命周期阶段”- 初始化(类似
constructor):在函数组件中,state 的初始化使用useState完成,refs 的初始化使用useRef完成。这发生在组件函数首次被调用时。 - 挂载(类似
componentDidMount):要使 effect 仅在组件挂载后运行一次,请向useEffect提供一个空的依赖项数组[]。这非常适合初始数据获取或设置订阅。 - 更新(类似
componentDidUpdate):要在特定 props 或 state 改变时运行 effect,请将这些值包含在依赖项数组中。effect 将在初始渲染后以及任何依赖项发生变化的后续渲染后运行。 - 卸载(类似
componentWillUnmount):如果useEffect中的setupFunction返回另一个函数(即cleanupFunction),React 将在组件卸载之前,以及在由于依赖项改变而重新运行 effect 之前运行此清理函数。这对于清理订阅或计时器等资源以防止内存泄漏至关重要。 componentWillReceiveProps(已弃用):在函数组件中,由于 prop 改变引起的重新渲染是自动处理的。如果您需要响应 prop 改变执行 side effect,请使用useEffect并将该 prop 包含在其依赖项数组中。componentWillMount和componentWillUpdate(已弃用):这些较旧的、在渲染之前运行的类生命周期方法已被弃用。它们的大多数用例可以通过在渲染之后使用useEffect运行 effect,或者直接在组件体内初始化 state 来实现。
使用 React.memo 优化性能
Section titled “使用 React.memo 优化性能”与类组件中的 PureComponent 或 shouldComponentUpdate 类似,React.memo 是一个高阶组件(higher-order component),它会记忆(memoize)您的函数组件。如果您的组件在给定相同的 props 时渲染相同的结果,React 可以通过重用上次渲染的结果来跳过重新渲染该组件。这对于那些经常使用相同 props 渲染的组件来说是一个有用的性能优化手段。
示例:使用 useEffect 的生命周期
Section titled “示例:使用 useEffect 的生命周期”让我们改编原始示例。App 是一个父组件,它更新一个数字;Content 是一个子组件,它使用 useEffect 在其生命周期阶段记录消息。
App.jsx
Section titled “App.jsx”import React, { useState, useEffect } from 'react';
// Child Component// 子组件function Content({ myNumber }) { useEffect(() => { console.log('Content: Component did mount (or myNumber changed for the first time)'); // This effect runs on mount AND when myNumber changes. // 这个 effect 在挂载时和 myNumber 改变时都会运行。 // If you only want it on mount, the dependency array would be []. // 如果您只想让它在挂载时运行,依赖项数组应为 []。 // If you want it specifically for myNumber updates after mount, you might need a ref to track if it's the first render. // 如果您希望它专门针对挂载后 myNumber 的更新,您可能需要一个 ref 来跟踪是否是首次渲染。 console.log('Content: myNumber is now:', myNumber);
return () => { // This cleanup runs before the effect runs again (if myNumber changes) OR on unmount. // 这个清理函数在 effect 再次运行之前(如果 myNumber 改变)或在卸载时运行。 console.log('Content: Cleanup before next myNumber update or unmount. Current myNumber:', myNumber); }; }, [myNumber]); // Dependency: effect re-runs if myNumber changes // 依赖项:如果 myNumber 改变,effect 会重新运行
useEffect(() => { console.log('Content: Effect for MOUNT only'); // effect 只在挂载时运行 return () => { console.log('Content: Effect for UNMOUNT only - Component will unmount!'); // effect 只在卸载时运行 - 组件即将卸载! }; }, []); // Empty dependency array: runs only on mount and unmount // 空依赖项数组:只在挂载和卸载时运行
return ( <div> <h3>Received Number: {myNumber}</h3> </div> );}
// Parent Component// 父组件function App() { const [data, setData] = useState(0); const [showContent, setShowContent] = useState(true);
const setNewNumber = () => { setData(prevData => prevData + 1); };
// This simulates the unmounting from the original example // 这模拟了原始示例中的卸载逻辑 useEffect(() => { if (data > 2) { // For demonstration, unmount after a few increments // 为了演示,增加几次后卸载 console.log('App: Preparing to unmount Content component in 2 seconds'); // App:准备在 2 秒后卸载 Content 组件 setTimeout(() => { setShowContent(false); console.log('App: Content component unmounted.'); // App:Content 组件已卸载。 }, 2000); } }, [data]);
return ( <div> <button onClick={setNewNumber}>INCREMENT</button> {showContent ? <Content myNumber={data} /> : <p>Content has been unmounted.</p>} {/* 如果 showContent 为 true 则显示 Content 组件,否则显示文本 */} </div> );}
export default App;main.js(或 index.js)
Section titled “main.js(或 index.js)”import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.jsx';
const container = document.getElementById('app');const root = ReactDOM.createRoot(container);
root.render(<App />);
// The unmounting logic is now handled within App.jsx for this example's purpose.// 卸载逻辑在此示例中已移至 App.jsx 内处理。// Original example had: setTimeout(() => { ReactDOM.unmountComponentAtNode(document.getElementById('app')); }, 10000);// 原始示例:setTimeout(() => { ReactDOM.unmountComponentAtNode(document.getElementById('app')); }, 10000);// With React 18, it would be: setTimeout(() => { root.unmount(); }, 10000);// 在 React 18 中应为:setTimeout(() => { root.unmount(); }, 10000);预期的控制台输出行为:
Section titled “预期的控制台输出行为:”- 初始渲染:
Content: Component did mount (or myNumber changed for the first time)Content: myNumber is now: 0Content: Effect for MOUNT only
- 点击 INCREMENT(data 变为 1):
Content: Cleanup before next myNumber update or unmount. Current myNumber: 0Content: Component did mount (or myNumber changed for the first time)(Effect 由于myNumber改变而重新运行)Content: myNumber is now: 1
- 再次点击 INCREMENT(data 变为 2,然后 3 触发卸载逻辑):
myNumber改变为 2 时有类似的日志。当data变为 3 时:App: Preparing to unmount Content component in 2 seconds(2 秒后,Content卸载)Content: Cleanup before next myNumber update or unmount. Current myNumber: 3Content: Effect for UNMOUNT only - Component will unmount!App: Content component unmounted.
理解 useEffect 的依赖项数组至关重要。它控制着您的 effect 何时运行和清理,防止不必要的操作和 bug。始终确保您的依赖项数组准确地反映了 effect 中使用的所有来自组件作用域的值。