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

面试之《react hooks在源码中是怎么实现的?》

要深入理解 React Hooks 在源码中的实现,可以从以下几个关键方面来剖析:

核心数据结构

在 React 内部,使用链表来管理每个函数组件的 Hooks。每个 Hook 对应一个节点,这些节点通过 next 指针相连。以下是简化后的 Hook 节点结构:

// 简化的 Hook 节点结构
function Hook() {
    this.memoizedState = null; // 存储当前 Hook 的状态
    this.baseState = null;
    this.baseUpdate = null;
    this.queue = null; // 存储更新队列
    this.next = null; // 指向下一个 Hook 节点
}

对于每个函数组件,React 会维护一个 fiber 对象,fiber 是 React 内部用于协调渲染的核心数据结构,其中 memoizedState 属性指向该组件 Hooks 链表的头部。

useState 的实现原理

useState 是最常用的 Hook 之一,下面是简化的 useState 实现逻辑:

// 全局变量,用于记录当前正在处理的 fiber
let currentlyRenderingFiber = null;
// 全局变量,用于记录当前正在处理的 Hook
let workInProgressHook = null;

function useState(initialState) {
    // 获取当前的 Hook 节点
    let hook;
    if (workInProgressHook === null) {
        // 如果是首次渲染,创建新的 Hook 节点
        hook = {
            memoizedState: initialState,
            queue: {
                pending: null
            },
            next: null
        };
        if (currentlyRenderingFiber.memoizedState === null) {
            // 如果是该组件的第一个 Hook,将其作为链表头部
            currentlyRenderingFiber.memoizedState = hook;
        } else {
            // 否则,将新 Hook 节点添加到链表末尾
            let lastHook = currentlyRenderingFiber.memoizedState;
            while (lastHook.next !== null) {
                lastHook = lastHook.next;
            }
            lastHook.next = hook;
        }
    } else {
        // 如果不是首次渲染,获取当前 Hook 节点
        hook = workInProgressHook;
        workInProgressHook = workInProgressHook.next;
    }

    // 处理更新队列
    let baseState = hook.memoizedState;
    let firstUpdate = hook.queue.pending;
    if (firstUpdate!== null) {
        let update = firstUpdate;
        do {
            const action = update.action;
            baseState = action(baseState);
            update = update.next;
        } while (update!== null && update!== firstUpdate);
        hook.queue.pending = null;
    }
    hook.memoizedState = baseState;

    // 返回状态和更新函数
    const setState = (action) => {
        const update = {
            action,
            next: null
        };
        if (hook.queue.pending === null) {
            update.next = update;
        } else {
            update.next = hook.queue.pending.next;
            hook.queue.pending.next = update;
        }
        hook.queue.pending = update;
        // 触发重新渲染
        scheduleUpdate();
    };

    return [baseState, setState];
}

上述代码中,首次调用 useState 时会创建一个新的 Hook 节点并添加到 Hooks 链表中,后续调用则直接获取对应的 Hook 节点。setState 函数会将更新添加到更新队列中,并触发重新渲染。

useEffect 的实现原理

useEffect 用于处理副作用,下面是简化的 useEffect 实现:

function useEffect(create, deps) {
    // 获取当前的 Hook 节点
    let hook;
    if (workInProgressHook === null) {
        // 如果是首次渲染,创建新的 Hook 节点
        hook = {
            memoizedState: null,
            next: null
        };
        if (currentlyRenderingFiber.memoizedState === null) {
            currentlyRenderingFiber.memoizedState = hook;
        } else {
            let lastHook = currentlyRenderingFiber.memoizedState;
            while (lastHook.next !== null) {
                lastHook = lastHook.next;
            }
            lastHook.next = hook;
        }
    } else {
        // 如果不是首次渲染,获取当前 Hook 节点
        hook = workInProgressHook;
        workInProgressHook = workInProgressHook.next;
    }

    // 获取上一次的依赖项
    const prevDeps = hook.memoizedState? hook.memoizedState[1] : null;
    let hasChanged = true;
    if (prevDeps!== null) {
        hasChanged = false;
        for (let i = 0; i < deps.length; i++) {
            if (prevDeps[i]!== deps[i]) {
                hasChanged = true;
                break;
            }
        }
    }

    if (hasChanged) {
        // 如果依赖项发生变化,添加副作用到待执行队列
        hook.memoizedState = [
            () => {
                const cleanUp = create();
                return cleanUp;
            },
            deps
        ];
        currentlyRenderingFiber.effects.push(hook.memoizedState[0]);
    }
}

在 useEffect 中,会比较前后两次的依赖项数组。如果依赖项发生变化,会将副作用函数添加到 fiber 的 effects 数组中,在渲染完成后执行这些副作用。

调用顺序的保证

React 严格依赖于 Hooks 的调用顺序。在每次渲染时,workInProgressHook 会依次指向 Hooks 链表中的每个节点。如果 Hooks 的调用顺序发生改变,会导致 workInProgressHook 无法正确获取对应的 Hook 节点,从而引发错误。

重新渲染和状态更新

当调用 setState 等更新函数时,会触发 scheduleUpdate 函数,该函数会标记当前 fiber 需要重新渲染。React 会重新执行函数组件,再次按顺序调用 Hooks,更新状态和副作用。
以上代码只是简化的实现,实际的 React 源码要复杂得多,包含了大量的错误处理、性能优化和兼容性处理等逻辑。但通过这些简化代码,可以理解 React Hooks 核心的实现原理。

相关文章:

  • 添加成对约束后的标签传播算法研究:使用Python语言编写算法,在空手道数据集下验证算法的准确性,在一定程度上解决非对齐问题
  • 【算法工程】大模型局限性新发现之解决能连github但无法clone项目的问题
  • 使用自制工具类实现安全的密码加密与校验
  • 实现Python+Django+Transformers库中的BertTokenizer和BertModel来进行BERT预训练,并将其应用于商品推荐功能
  • winfrom的progressBar 鼠标移上去显示 进度条的时间
  • LeetCode 15.三数之和
  • Imagination DXTP GPU IP:加速游戏AI应用,全天候畅玩无阻
  • Linux | Ubuntu 与 Windows 双系统安装 / 高频故障 / UEFI 安全引导禁用
  • 香橙派/树莓派 利用Wiring库 使用GPIO模拟PWM
  • WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口
  • 普中单片机-51TFT-LCD显示屏(1.8寸 STM32)
  • tableau之雷达图和凹凸图
  • 图数据库 | 23、如何评测图系统 — 评测内容?
  • 正则表达式用法及其示例:匹配、查找、替换文本中的模式,及QT下如何使用正则表达式。
  • Linux网络基础(协议 TCP/IP 网络传输基本流程 IP VS Mac Socket编程UDP)
  • RAG 阿里云
  • go 语言中的线程池
  • 四、Redis主从复制与读写分离
  • nvidia驱动更新,centos下安装openwebui+ollama(非docker)
  • 面试基础---ConcurrentHashMap vs HashMap
  • 灰色网站怎么做seo/优化网站软文
  • 高校专业建设五大要素/如何进行搜索引擎优化
  • 用php做一网站/学开网店哪个培训机构好正规
  • 电子政务网站建设出版社/推广方案策划
  • 荥阳网站建设/百度一下百度搜索入口
  • 北方网天津疫情/网络推广优化是干啥的