2.2生命周期与 Hooks (重点) React 生命周期与 Hooks:深入理解与实践 Class 组件的生命周期 在 React 16.8 引入 Hooks 之前,Class 组件是管理状态和处理副作用的主要方式。每个 Class 组件都经历一个特定的生命周期,这个周期可以分为三个阶段: 挂载 (Mounting): 组件被创建并插入 DOM 的阶段。 更新 (Updating): 组件因 props 或 state 改变而重新渲染的阶段。 卸载 (Unmounting): 组件从 DOM 中移除的阶段。 每个阶段都包含一些生命周期方法,允许我们在特定时刻执行代码。 1.1 挂载阶段 (Mounting) : 构造函数是 Class 组件中第一个被调用的方法。
在 React 16.8 引入 Hooks 之前,Class 组件是管理状态和处理副作用的主要方式。每个 Class 组件都经历一个特定的生命周期,这个周期可以分为三个阶段:
挂载 (Mounting): 组件被创建并插入 DOM 的阶段。
更新 (Updating): 组件因 props 或 state 改变而重新渲染的阶段。
卸载 (Unmounting): 组件从 DOM 中移除的阶段。
每个阶段都包含一些生命周期方法,允许我们在特定时刻执行代码。
1.1 挂载阶段 (Mounting)
constructor(props): 构造函数是 Class 组件中第一个被调用的方法。用于初始化 state 和绑定事件处理函数。
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.handleClick = this.handleClick.bind(this); // 绑定this } // ... }
static getDerivedStateFromProps(props, state): 静态方法,在每次渲染之前调用,接收 props 和 state 作为参数,返回一个对象来更新 state,如果不需要更新 state,则返回 null。 主要用于根据 props 派生 state。
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { name: props.initialName }; } static getDerivedStateFromProps(props, state) { if (props.initialName !== state.name) { return { name: props.initialName }; } return null; } render() { return ( <div> <p>Name: {this.state.name}</p> </div> ); } }
render(): 必须实现的方法,描述了组件应该渲染的内容。 这是一个纯函数,不应该直接修改 state 或与 DOM 交互。
class MyComponent extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> </div> ); } }
componentDidMount(): 在组件挂载到 DOM 后立即调用。 适合进行网络请求、订阅事件、直接操作 DOM 等副作用操作。
class MyComponent extends React.Component { componentDidMount() { // 发起网络请求 fetch('/api/data') .then(response => response.json()) .then(data => this.setState({ data })); } render() { return ( <div> {this.state.data ? <p>Data: {this.state.data}</p> : <p>Loading...</p>} </div> ); } }
1.2 更新阶段 (Updating)
static getDerivedStateFromProps(props, state): 与挂载阶段相同,在每次渲染之前调用。
shouldComponentUpdate(nextProps, nextState): 在渲染之前调用,接收下一个 props 和 state 作为参数。 允许我们决定组件是否需要重新渲染。 如果返回 false,则跳过渲染和 componentDidUpdate()。 可以用于性能优化。
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { // 只有当 count 改变时才重新渲染 return nextState.count !== this.state.count; } render() { console.log('Rendering...'); return ( <div> <p>Count: {this.state.count}</p> </div> ); } }
render(): 与挂载阶段相同。
getSnapshotBeforeUpdate(prevProps, prevState): 在 DOM 更新之前调用。 可以用于在更新之前捕获 DOM 的一些信息(例如滚动位置)。 返回的值将作为 componentDidUpdate() 的第三个参数。
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { messages: [] }; this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { // 捕获滚动位置 if (prevState.messages.length < this.state.messages.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 ( <ul ref={this.listRef}> {this.state.messages.map((message, index) => ( <li key={index}>{message}</li> ))} </ul> ); } }
componentDidUpdate(prevProps, prevState, snapshot): 在更新后立即调用。 可以用于执行副作用操作,例如基于更新后的 props 或 state 发起网络请求。 但需要注意避免无限循环。
class MyComponent extends React.Component { componentDidUpdate(prevProps) { // 只有当 props.userId 改变时才发起网络请求 if (this.props.userId !== prevProps.userId) { fetch(`/api/user/${this.props.userId}`) .then(response => response.json()) .then(data => this.setState({ userData: data })); } } render() { return ( <div> {this.state.userData ? <p>User Name: {this.state.userData.name}</p> : <p>Loading...</p>} </div> ); } }
1.3 卸载阶段 (Unmounting)
componentWillUnmount(): 在组件卸载之前立即调用。 适合进行清理工作,例如取消订阅、清除定时器、取消未完成的网络请求。
class MyComponent extends React.Component { componentDidMount() { this.intervalId = setInterval(() => { console.log('Tick'); }, 1000); } componentWillUnmount() { // 清除定时器 clearInterval(this.intervalId); } render() { return ( <div> <p>This component will log to the console every second.</p> </div> ); } }
1.4 错误处理 (Error Handling)
static getDerivedStateFromError(error): 在子组件抛出错误时调用。 可以用于更新 state 以显示一个回退 UI。
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // 你同样可以将错误日志上报给服务器 logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // 你可以自定义降级后的 UI 并渲染 return <h1>Something went wrong.</h1>; } return this.props.children; } }
componentDidCatch(error, info): 在子组件抛出错误时调用。 可以用于记录错误信息。
1.5 生命周期方法调用顺序
React Hooks 允许我们在不编写 Class 组件的情况下使用 state 和其他 React 特性。它们是在 React 16.8 中引入的,彻底改变了 React 组件的编写方式。
2.1 为什么使用 Hooks?
代码复用: Hooks 使得在组件之间复用状态逻辑变得更容易。
可读性: Hooks 可以将组件中相关的逻辑组织在一起,提高可读性。
简洁性: 函数组件通常比 Class 组件更简洁。
避免 this: Hooks 避免了 Class 组件中 this 带来的困扰。
2.2 常见的 Hooks
useState: 用于在函数组件中添加 state。
import React, { useState } from 'react'; function MyComponent() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }
useEffect: 用于在函数组件中执行副作用操作,例如网络请求、订阅事件、直接操作 DOM。 类似于 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
import React, { useState, useEffect } from 'react'; function MyComponent() { const [data, setData] = useState(null); useEffect(() => { // 发起网络请求 fetch('/api/data') .then(response => response.json()) .then(data => setData(data)); // 清理函数 (类似于 componentWillUnmount) return () => { // 例如:取消订阅 }; }, []); // 空数组表示只在组件挂载和卸载时执行 return ( <div> {data ? <p>Data: {data}</p> : <p>Loading...</p>} </div> ); }
useContext: 用于访问 context 的值。
import React, { useContext } from 'react'; const MyContext = React.createContext(null); function MyComponent() { const value = useContext(MyContext); return ( <div> <p>Context Value: {value}</p> </div> ); }
useReducer: 用于管理复杂的状态逻辑。 类似于 Redux 的 reducer。
import React, { useReducer } from 'react'; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function MyComponent() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> </div> ); }
useCallback: 用于记忆函数,防止函数组件重新渲染时重新创建函数。 可以用于优化性能。
import React, { useCallback } from 'react'; function MyComponent({ onClick }) { const handleClick = useCallback(() => { onClick(); }, [onClick]); // 只有当 onClick 改变时才重新创建函数 return ( <button onClick={handleClick}>Click Me</button> ); }
useMemo: 用于记忆计算结果,防止函数组件重新渲染时重新计算。 可以用于优化性能。
import React, { useMemo } from 'react'; function MyComponent({ items }) { const expensiveCalculation = useMemo(() => { // 执行耗时计算 return items.reduce((sum, item) => sum + item, 0); }, [items]); // 只有当 items 改变时才重新计算 return ( <div> <p>Sum: {expensiveCalculation}</p> </div> ); }
useRef: 用于在组件的整个生命周期中保存一个可变的引用。 可以用于访问 DOM 元素或保存不需要触发重新渲染的值。
import React, { useRef, useEffect } from 'react'; function MyComponent() { const inputRef = useRef(null); useEffect(() => { // 在组件挂载后自动聚焦到 input 元素 inputRef.current.focus(); }, []); return ( <input ref={inputRef} type="text" /> ); }
useImperativeHandle: 配合 forwardRef 使用,可以让你在使用 ref 时自定义暴露给父组件的实例值。
import React, { useRef, useImperativeHandle, forwardRef } from 'react'; const MyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, getValue: () => { return inputRef.current.value; } })); return <input ref={inputRef} {...props} />; }); function ParentComponent() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); }; const getValue = () => { alert(inputRef.current.getValue()); }; return ( <div> <MyInput ref={inputRef} /> <button onClick={handleFocus}>Focus Input</button> <button onClick={getValue}>Get Value</button> </div> ); }
自定义 Hooks: 允许我们提取可复用的状态逻辑到独立的函数中。 自定义 Hooks 必须以 use 开头。
import React, { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); fetch(url) .then(response => response.json()) .then(data => { setData(data); setLoading(false); }) .catch(error => { setError(error); setLoading(false); }); }, [url]); return { data, loading, error }; } function MyComponent() { const { data, loading, error } = useFetch('/api/data'); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return ( <div> <p>Data: {data}</p> </div> ); }
2.3 Hooks 的使用规则
只能在函数组件或自定义 Hooks 中调用 Hooks。
只能在组件的顶层调用 Hooks,不要在循环、条件语句或嵌套函数中调用 Hooks。
在自定义 Hooks 中,必须以 use 开头命名。
在 React 16.8 之后,我们有了 Class 组件和 Hooks 两种选择。 一般来说,Hooks 更加推荐,因为它们更简洁、可读性更高,并且更容易复用状态逻辑。 但是,在一些特殊情况下,Class 组件可能更适合,例如:
需要使用一些老的生命周期方法,例如 componentWillMount (虽然不推荐)。
需要进行一些复杂的性能优化,例如使用 shouldComponentUpdate 进行细粒度的控制。
总的来说,Hooks 是 React 的未来,建议尽可能使用 Hooks 来编写 React 组件。