当前位置: 首页 > news >正文

react生命周期及hooks等效实现

目录

  • 一、React生命周期图例
    • 1. 挂载/创建阶段(组件首次渲染)4步
    • 2. 更新阶段(props/state变更)5步
    • 3. 卸载阶段(组件从DOM移除)
    • 4. 错误处理(React 16+新增)
  • 二、Hooks 对生命周期的替代
    • 1. 状态管理(替代 this.state)
    • 2. 副作用管理(替代生命周期钩子)
    • 3. 性能优化(替代 shouldComponentUpdate)
    • 4. 其他常用数据保存 Hooks
  • 三、hooks其他问题
    • 1. useEffect 与 useLayoutEffect 的区别?
    • 2. 如何避免 useEffect 无限循环?
    • 3. constructor 和 getDerivedStateFromProps 的区别
    • 4. useState和useReducer
    • 5. 多个 useEffect 的执行顺序
    • 6. 如何避免 useEffect 无限循环
    • 7. useMemo和 React.memo
    • 8. 自定义React hooks
    • 9. setState和useState是同步还是异步?区别是什么?

一、React生命周期图例

在这里插入图片描述

函数何时调用其他典型场景hooks等效实现
constructor仅在组件实例化时执行一次唯一可以直接修改state,很少使用初始化内部状态相当于useState/useReducer 初始化状态
getDerivedStateFromProps外部传入属性发生变化,例如:setState(),forceUpdate()state需要从props初始化时使用,每次render都会调用。维护两者状态一致会增加复杂度,尽量不要使用表单控件获取默认值useMemo、useCallback 、useEffect+useState
shouldComponentUpdate属性变化时,除了forceUpdate()决定Virtual DOM是否要重绘性能优化React.memo,用于浅比较 props 来决定是否重新渲染组件
render渲染UI描述UI,必须写的方法函数组件本身就是渲染逻辑
getSnapshotBeforeUpdate页面render之前调用,state已更新获取render之前DOM状态使用 useLayoutEffect 在 DOM 更新前读取值,并通过 useRef 存储快照
componentDIdMountUI渲染完成后调用只执行一次发起ajax等外部请求useEffect(…, [])
componentDitUpdate每次UI更新时调用页面需要根据props变化重新获取数据useEffect(…, [dependency])
componentWillUnmount组件被移除时调用资源释放useEffect 返回的清理函数return

1. 挂载/创建阶段(组件首次渲染)4步

  • 执行顺序:constructor → getDerivedStateFromProps → render → componentDidMount

2. 更新阶段(props/state变更)5步

  • 触发条件:props 变化、state 变化、forceUpdate()
  • 执行顺序:getDerivedStateFromProps → shouldComponentUpdate → render →
    getSnapshotBeforeUpdatecomponentDidUpdate

3. 卸载阶段(组件从DOM移除)

  • componentWillUnmount:用于清理副作用(如定时器、订阅)

4. 错误处理(React 16+新增)

  • componentDidCatch

二、Hooks 对生命周期的替代

1. 状态管理(替代 this.state)

  • useState:基础状态管理。
const [count, setCount] = useState(0);
  • useReducer:复杂状态逻辑(类似 Redux 的 reducer)
const [state, dispatch] = useReducer(reducer, initialState);

2. 副作用管理(替代生命周期钩子)

  • useEffect:相当于 componentDidMount + componentDidUpdate + componentWillUnmount 的组合
// 依赖项为空数组:仅挂载时执行(类似 componentDidMount)
useEffect(() => {// 初始化逻辑return () => {// 组件卸载时执行:清理逻辑(类似 componentWillUnmount)};
}, []);// 无依赖项:每次渲染后执行(类似 componentDidMount + componentDidUpdate)
useEffect(() => {// 副作用逻辑
});// 特定依赖变化时执行
useEffect(() => {// 仅当 count 变化时执行(初始也执行一次?)
}, [count]);

3. 性能优化(替代 shouldComponentUpdate)

  • React.memo:浅比较 props,阻止重复渲染(函数组件版 PureComponent)。
const MyComponent = React.memo((props) => { ... });
  • useMemo:缓存计算结果,避免重复计算。
const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useCallback:缓存函数引用,避免子组件不必要的更新。
const handleClick = useCallback(() => {doSomething(a);
}, [a]);

4. 其他常用数据保存 Hooks

  • useRef:保存可变值(类似类组件的实例属性);获取 DOM 节点(替代 createRef)。
  • useContext:跨层级传递数据(替代 Context.Consumer)。

三、hooks其他问题

1. useEffect 与 useLayoutEffect 的区别?

对比useEffectuseLayoutEffect
执行时机异步执行,在浏览器渲染后触发(不阻塞页面绘制)。同步执行,在 DOM 更新后、浏览器绘制前触发(可能阻塞渲染)。
适用场景数据获取、订阅等不影响渲染的操作需要读取 DOM 布局并立即更新的场景(如测量元素尺寸、滚动位置恢复)。

2. 如何避免 useEffect 无限循环?

  • 检查依赖项:确保依赖项数组中不包含引用类型(如对象、函数),或使用 useCallback/useMemo 缓存。
  • 过滤不必要的更新:在 useEffect 内部通过条件判断过滤重复操作。
  • 使用 useRef 存储可变值:避免依赖项变化触发无限循环。

3. constructor 和 getDerivedStateFromProps 的区别

方法constructorgetDerivedStateFromProps
调用时机组件实例化时调用(初始化阶段)每次渲染前调用(初始化 + 更新阶段)
是否可以更新 state可以(通过 this.state = { … })必须返回新 state 或 null(纯函数)
是否可以访问 this可以(需先调用 super(props))不可以(静态方法,无 this)
典型用途初始化 state、绑定事件处理函数根据 props 动态更新 state(如受控组件)

注意:不能在constructor里调用 setState(此时组件尚未挂载)。用this.state = { … }赋值

4. useState和useReducer

特性useStateuseReducer
状态结构基础类型(number、string、boolean)或对象复杂对象或数组
更新逻辑直接修改(setState(newValue))通过 reducer 函数处理(纯函数)
适用场景状态逻辑简单,状态更新相互独立(如计数器、表单值)复杂状态逻辑,更新逻辑分散,需要撤销 / 重做、时间旅行调试(如多值联动、状态历史记录)
组件间共享需逐层传递 state 和 setState可结合 Context 全局共享 dispatch
调试与测试较简单更易预测(纯函数),便于测试和时间旅行调试
多次调用多次调用 setState 可能触发多次渲染(React 18 中自动批处理已优化)。单个 dispatch 触发一次更新,更适合复杂状态变更。

useReducer使用

const initialState = {loading: false,data: null,error: null
};const reducer = (state, action) => {switch (action.type) {case 'FETCH_START':return { ...state, loading: true }; // 仅更新 loadingcase 'FETCH_SUCCESS':return { ...state, loading: false, data: action.payload }; // 更新 loading 和 datacase 'FETCH_ERROR':return { ...state, loading: false, error: action.error }; // 更新 loading 和 errordefault:return state;}
};const DataFetcher = () => {const [state, dispatch] = useReducer(reducer, initialState);return (<div><button onClick={() => dispatch({ type: 'FETCH_START', payload: { id: 1 } })}>start</button>{/* 其他操作... */}</div>);
};

5. 多个 useEffect 的执行顺序

useEffect(() => {console.log(1)return () => {console.log(2)};
}, []);useEffect(() => {console.log(3);return () => {console.log(4)}
});useEffect(() => {console.log(5)return () => {console.log(6)}
}, [count]);

以上代码挂载时、卸载时、count变化时的执行顺序是什么?

  • 挂载时(首次渲染)
    输出:1 → 3 → 5

    • 第一个 useEffect(依赖项为空数组 []):仅在挂载时执行。
    • 第二个useEffect(无依赖项):在每次渲染后执行,包括首次渲染。
    • 第三个 useEffect(依赖项为 count):在首次渲染时,由于count 存在初始值(假设为 0),也会执行。
  • 卸载时
    输出 6 → 4 → 2

    • 第三个 useEffect的return(依赖 count):最先注册,最后清理(6)
    • 第二个 useEffect的return(无依赖):其次注册,其次清理(4)。
    • 第一个 useEffect的return(空依赖):最后注册,最先清理(2)。
  • count 变化时
    输出:6 → 5 → 4 → 3

    • 第三个 useEffect:
      • 清理上一次的副作用(6)。
      • 执行新的副作用(5)。
    • 第二个 useEffect:
      • 清理上一次的副作用(4)。
      • 执行新的副作用(3)。

useEffect 的清理函数(return 返回的函数)遵循 “后进先出”(LIFO) 的栈结构规则,useEffect顺序执行,遇到return后压入栈中,所以第一个useEffect的return先入栈,第三个useEffect的return最后入栈;卸载时,第三个useEffect的return先出栈执行

6. 如何避免 useEffect 无限循环

  • 无限循环的常见原因
    当 useEffect 的依赖项包含引用类型(如对象、函数)时,如果每次渲染都生成新的引用,会导致 useEffect 不断触发
  • 解决
    • ① useCallback 缓存函数引用,仅在依赖项变化时才重新创建函数

      const [count, setCount] = useState(0);// 仅当依赖项 [] 变化时才重新创建函数(这里永远不变)
      const fetchData = useCallback(() => {console.log('Fetching data...');
      }, []); // ✅ 只创建一次函数useEffect(() => {fetchData();
      }, [fetchData]); // ✅ 仅触发一次
      
    • ② useMemo 缓存计算结果,避免重复计算复杂值

      const [count, setCount] = useState(0);// 仅当 count 变化时才重新计算 expensiveValue
      const expensiveValue = useMemo(() => {return performExpensiveCalculation(count);
      }, [count]);useEffect(() => {console.log(expensiveValue);
      }, [expensiveValue]); // ✅ 仅在 count 变化时触发
      
    • ③ useRef 存储可变值,修改 ref 不会导致组件重新渲染

      const [count, setCount] = useState(0);
      const prevCountRef = useRef(0);useEffect(() => {prevCountRef.current = count; // 保存当前值
      }, [count]);const prevCount = prevCountRef.current; // 获取上一次的值
      

7. useMemo和 React.memo

关于 useMemo 和 React.memo 的区别,这是 React 性能优化中的常见问题。虽然名字相似,但它们的功能和应用场景完全不同

特性useMemoReact.memo
类型Hook(函数组件内部使用)高阶组件(包裹函数组件)
作用对象值(计算结果、函数等)组件本身
触发条件依赖项变化时重新计算props 浅比较不相等时重新渲染
应用场景避免重复计算复杂值、缓存函数引用避免组件因相同 props 重复渲染
默认行为不自动执行,需手动包裹 自动执行 props 浅比较
  • React.memo 的浅比较限制
    • 仅浅比较 props:如果 props 包含引用类型(如对象、数组),需确保引用不变。
    • 函数 props:需配合 useCallback 或 useMemo 使用,避免父组件每次渲染时创建新函数。

8. 自定义React hooks

  • 定义:自定义 Hooks 是一个函数,其名称以 use 开头,内部可以调用其他 Hooks。
  • 核心价值:复用有状态的逻辑(如订阅、动画、表单验证),同时不污染组件结构。
  • 与组件的区别:组件返回 UI,而自定义 Hooks 返回数据或副作用。

自定义 Hook 示例:

import { useState, useEffect } from 'react';
// 使用 localStorage 持久化状态
function useLocalStorage(key, initialValue) {// 1. 内部使用 useState 管理状态const [value, setValue] = useState(() => {try {const storedValue = localStorage.getItem(key);return storedValue ? JSON.parse(storedValue) : initialValue;} catch (error) {return initialValue;}});// 2. 使用 useEffect 添加副作用useEffect(() => {localStorage.setItem(key, JSON.stringify(value));}, [key, value]); // 3. 指定依赖项,控制副作用触发时机// 4. 返回状态和修改状态的函数(类似 useState),也可以返回对象或函数return [value, setValue];
}

9. setState和useState是同步还是异步?区别是什么?

特性class 组件的 setState函数组件的 useState
执行时机批量异步(多数情况下)批量异步(多数情况下)
强制同步场景1. 原生事件回调 2. setTimeout/setInterval1. 原生事件回调 2. setTimeout/setInterval
状态更新方式合并对象(this.state 是累积的)完全替换(需手动合并 setState(prev => ({…prev, key})))
多次调用行为合并为一次更新(对象浅合并)按顺序执行(函数式更新)
  • 异步机制:React 为了优化性能,会批量处理多个状态更新,因此在同一个事件循环中多次调用 setState 或 useState 可能不会立即生效。

    // Class 组件
    handleClick() {this.setState({ count: this.state.count + 1 });console.log(this.state.count); // 输出旧值(异步更新)
    }// 函数组件
    const handleClick = () => {setCount(count + 1);console.log(count); // 输出旧值(异步更新)
    };
    
  • 同步场景

    • 原生事件回调

      // Class 组件
      componentDidMount() {document.getElementById('btn').addEventListener('click', () => {this.setState({ count: this.state.count + 1 });console.log(this.state.count); // 输出新值(同步更新)});
      }// 函数组件
      useEffect(() => {document.getElementById('btn').addEventListener('click', () => {setCount(count + 1);console.log(count); // 输出新值(同步更新)});
      }, []);
      
    • setTimeout/setInterval 回调

      场景Class 组件的 setState函数组件的 useState
      状态更新时机立即更新(同步)不立即更新(异步)
      闭包捕获问题无(通过 this.state 获取最新值)有(闭包捕获初始值)可通过 useRef 存储可变值,使用最新状态
      多次调用合并合并为一次更新(对象浅合并)按顺序执行(函数式更新)
      渲染触发次数一次(批量处理)一次(React 18 自动批处理)

      Class 组件的setState在setTimeout中:

      class Counter extends React.Component {state = { count: 0 };handleClick = () => {setTimeout(() => {this.setState({ count: this.state.count + 1 });console.log(this.state.count); // 输出 1(状态立即更新)this.setState({ count: this.state.count + 1 });console.log(this.state.count); // 输出 2(状态立即更新)}, 1000);};render() {console.log('Rendering...'); // 仅触发一次渲染return <button onClick={this.handleClick}>{this.state.count}</button>;}
      }
      

      函数组件的useState在setTimeout中:

      const Counter = () => {const [count, setCount] = useState(0);const handleClick = () => {setTimeout(() => {setCount(prev => prev + 1); // 使用函数式更新,获取最新状态console.log(count); // 输出 0(状态未立即更新,但更新逻辑正确)setCount(prev => prev + 1); // 基于最新状态更新,最终 count 变为 2console.log(count); // 输出 0}, 1000);};return <button onClick={handleClick}>{count}</button>;
      };
      
  • 批量更新机制的差异

    • setState:多次调用会合并对象,仅触发一次渲染。
    • useState:多次调用函数式更新会按顺序执行,触发多次渲染(React 18 中自动批处理优化为一次)。
      // Class 组件
      handleClick() {this.setState({ count: this.state.count + 1 });this.setState({ count: this.state.count + 1 }); // 合并为一次更新,最终 count 只加 1
      }
      // 函数组件(React 18 之前)
      const handleClick = () => {setCount(prev => prev + 1); // 第一次更新setCount(prev => prev + 1); // 第二次更新,最终 count 加 2
      };
      
  • 获取更新后的状态

    • Class 组件:setState使用回调函数

    • 函数组件:useState使用useEffect

      this.setState({ count: this.state.count + 1 },() => {console.log(this.state.count); // 输出新值}
      );
      const [count, setCount] = useState(0);useEffect(() => {console.log(count); // 每次 count 变化时执行
      }, [count]);const handleClick = () => {setCount(count + 1);
      };
      

相关文章:

  • vip影视网站怎么做的深圳网站推广
  • 公司做的网站如何开启伪静态猪肉价格最新消息
  • 哪个网站可以直接做ppt线下推广方法有哪些
  • 做网站容易 但运营难湖南网站设计外包费用
  • 全球跨境电商平台排行榜前十名上海seo排名
  • 昆明双鼎网站制作谷歌seo优化排名
  • Windows 创建并激活 Python 虚拟环境venv
  • 华为云Flexus+DeepSeek征文 | 基于CCE容器的AI Agent高可用部署架构与弹性扩容实践
  • 解决Fedora21下无法使用NWJS网页透明效果的问题
  • OSS监控体系搭建:Prometheus+Grafana实时监控流量、错误码、存储量(开源方案替代云监控自定义视图)
  • 学习threejs,使用kokomi、gsap实现图片环效果
  • 独家战略!谷子科技“芯”技术联姻浙江卫视
  • 跟着Carl学算法--哈希表
  • Kafka如何保证消息可靠?
  • 构建你的 AI 模块宇宙:Spring AI MCP Server 深度定制指南
  • 哈希表理论与算法总结
  • TCP/UDP协议深度解析(一):UDP特性与TCP确认应答以及重传机制
  • Leaking GAN
  • Netty内存池核心PoolArena源码解析
  • 搭建智能问答系统,有哪些解决方案,比如使用Dify,LangChain4j+RAG等
  • 《C++初阶之类和对象》【初始化列表 + 自定义类型转换 + static成员】
  • Python光学玻璃库opticalglass
  • IP证书在网络安全中的作用
  • Windows驱动开发最新教程笔记2025(一)名词解释
  • Label Studio安装和使用
  • ABP VNext + BFF(Backend for Frontend)模式:Angular/React 专用聚合层