React Hooks原理

Hooks 原理(useState /useEffect)

前言:现如今,使用 React Hooks 函数式编程这种开发方式,早已成为 React 开发主流,相比类组件它更加灵活,扩展性更高的同时还解决了类组件的痛点。本文将核心围绕 Hooks 设计初衷、原理、模拟代码实现进行解析,帮助大家理解与学习 Hooks。


一、Hooks 设计初衷

  1. 解决类组件的痛点
  • 状态逻辑复用困难:类组件的状态逻辑耦合在组件内部,高阶组件(HOC)、Render Props 等复用方式会导致组件嵌套过深("嵌套地狱")。
  • 组件复杂度高:生命周期函数(如 componentDidMount/componentDidUpdate)会混杂不同逻辑(数据请求、事件监听、定时器),代码可读性差。
  • 类组件学习成本高:this 指向、绑定事件、继承等概念增加新手学习成本。
  1. Hooks 核心目标
  • 让函数组件拥有状态管理和生命周期能力。
  • 将组件中分散的逻辑拆分为独立的 Hooks,实现逻辑复用。
  • 简化代码结构,降低组件复杂度,让代码更易维护。

二、Hooks 规则与底层原因

  1. 核心规则
  • 只能在函数组件 / 自定义 Hooks 中调用 Hooks。
  • 只能在函数顶层调用 Hooks,不能在循环、条件、嵌套函数中调用。
  1. 规则底层原因
  • React 内部通过数组 + 索引存储 Hooks 状态,组件每次渲染时按调用顺序匹配状态。
  • 若在条件 / 循环中调用 Hooks,会导致每次渲染时 Hooks 调用顺序不一致,索引匹配错误,状态错乱。
  • 若在非函数组件中调用,React 无法关联组件实例,无法维护对应的 Hooks 状态容器。

三、useState 核心原理

  1. 核心逻辑
  • 用数组存储每个 useState 的状态,用索引标记当前 Hooks 位置。
  • 每次组件渲染时重置索引,按顺序读取 / 更新状态。
  • setState 触发状态更新后,重新执行组件函数,更新视图。
  1. 简化版 useState
js 复制代码
// 模拟React内部存储 Hooks 的容器
let hooks = [];
// 记录当前执行到第几个 Hook
let currentHookIndex = 0;

function useState(initialState){
    const currentIndex = currentHookIndex
    hooks[currentIndex] = hooks[currentIndex] || initialState
    console.log('currentIndex',hooks[currentIndex],currentIndex)

    const setState = (newValue) => {
        if(typeof newValue === 'function'){
            hooks[currentIndex] = newValue(hooks[currentIndex])
        }else {
            hooks[currentIndex] = newValue
        }
        renderComponent()
    }
    currentHookIndex++
    return [hooks[currentIndex], setState]
}

// 每次渲染前重置索引
function renderComponent(component) {
    // 每次渲染组件前,重置 Hook 索引为 0
    currentHookIndex = 0;
    // 模拟组件rerender,在39行打印能取到更新后的值。实际上react的更新还包括了批量更新操作
    // TestComponent();
}

function TestComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('initValue');

  // 模拟点击事件更新状态
  setTimeout(() => {
    setCount(10)
    setName('test-setState')
    console.log('update---------', count, name)
  }, 1000);
}

// 执行组件(模拟首次渲染)
TestComponent();
  1. 核心要点
  • hooks 数组:存储所有 useState 的状态,索引对应 Hooks 调用顺序。
  • currentHookIndex:标记当前执行到第几个 Hook,保证状态与 Hook 一一对应。
  • setState:修改 hooks 数组对应索引的值,并触发组件重新渲染(重置索引后执行组件)。

四、useEffect 核心原理

  1. 核心逻辑
    模拟生命周期:useEffect 分为 "执行回调" 和 "清理副作用" 两个阶段。
    依赖数组:通过对比依赖数组的前后值,判断是否执行回调。
    执行时机:组件渲染完成后执行(模拟浏览器 setTimeout 实现)。
  2. 简化版 useEffect
js 复制代码
// 扩展 Hooks 容器:存储 useEffect 的依赖和回调
let hooks = [];
let currentHookIndex = 0;
// 存储 useEffect 的依赖(用于对比)
let effectDeps = [];

/**
 * 简化版 useEffect
 * @param {Function} callback 副作用回调
 * @param {Array} deps 依赖数组
 */
function useEffect(callback, deps) {
    const currentIndex = currentHookIndex;
    // 获取上一次的依赖
    const prevDeps = effectDeps[currentIndex];
    // 判断是否需要执行回调:依赖不存在 / 依赖有变化
    const hasChanged = !prevDeps || !deps.every((dep, i) => dep === prevDeps[i]);

    if (hasChanged) {
        // 执行清理函数(上一次的回调返回值)
        const cleanUp = hooks[currentIndex];
        if (cleanUp && typeof cleanUp === 'function') {
            cleanUp();
        }
        // 模拟 useEffect 异步执行(浏览器渲染完成后)
        setTimeout(() => {
            // 保存清理函数到 hooks 数组
            hooks[currentIndex] = callback();
        });
        // 更新当前依赖
        effectDeps[currentIndex] = deps;
    }

    currentHookIndex++;
}

// 复用之前的 renderComponent 函数
function renderComponent(component) {
    currentHookIndex = 0;
    // component();
}

// 测试用例
function TestComponent() {
    const [count, setCount] = useState(0);

    // 模拟监听副作用
    useEffect(() => {
        console.log('副作用执行:count =', count);
        // 清理函数
        return () => {
            console.log('清理副作用:count =', count);
        };
    }, [count]); // 依赖 count,仅当 count 变化时执行

    // 模拟更新
    setTimeout(() => {
        setCount(1);
    }, 1000);
}

// 首次渲染
renderComponent(TestComponent);
  1. 依赖数组分析
依赖数组 执行时机 适用场景
无依赖数组 每次组件渲染后执行 监听所有状态变化,如全局事件监听
空数组 [] 仅组件首次渲染后执行(模拟 componentDidMount) 只执行一次的逻辑,如数据请求、定时器创建
有依赖 [a, b] 仅当 a/b 变化时执行 关联特定状态的副作用,如根据 ID 请求数据

五、核心原理总结

  1. useState 核心
  • 基于数组 + 索引存储状态,依赖调用顺序匹配状态。
  • setState 本质是修改状态容器的值,并触发组件重新渲染。
  • 每次渲染重置索引,保证 Hooks 顺序一致。
  1. useEffect 核心
  • 基于依赖对比决定是否执行副作用回调。
  • 支持清理函数,解决副作用泄漏问题(如事件解绑、定时器清除)。
  • 异步执行回调,避免阻塞浏览器渲染。
  1. Hooks 规则底层逻辑
  • 禁止条件 / 循环中调用:保证 Hooks 调用顺序不变,索引匹配正确。
  • 仅函数组件 / 自定义 Hooks 中调用:保证状态能关联到对应的组件实例。
相关推荐
00后程序员张2 小时前
前端可视化大屏制作全指南:需求分析、技术选型与性能优化
前端·ios·性能优化·小程序·uni-app·iphone·需求分析
kyriewen2 小时前
屎山代码拆不动?微前端来救场:一个应用变“乐高城堡”
前端·javascript·前端框架
@大迁世界2 小时前
3月 React 圈又变天了
前端·javascript·react.js·前端框架·ecmascript
忆江南2 小时前
# iOS 稳定性方向常见面试题与详解
前端
陆枫Larry2 小时前
一次讲清楚 `Promise.finally()`:为什么“无论成功失败都要执行”该用它
前端
Momo__2 小时前
被低估的 HTML 原生表单元素:dialog、datalist、meter、progress
前端
莹宝思密达2 小时前
【AI】chrome-dev-tools-mcp
前端·ai
用户69371750013842 小时前
2026 Android 开发,现在还能入行吗?
android·前端·ai编程
SuperEugene2 小时前
Vue3 配置驱动弹窗:JSON配置弹窗内容/按钮,避免重复开发弹窗|配置驱动开发实战篇
前端·javascript·vue.js·前端框架·json