React Hooks源码深度解析
作者:京东零售 郑炳懿
前言
React Hooks
是React
16.8 引入的一个新特性,它允许函数组件中使用state
和其他 React 特性,而不必使用类组件。Hooks
是一个非常重要的概念,因为它们提供了更简单、更易于理解的React
开发体验。
React Hooks
的核心源码主要包括两个部分:React
内部的Hook
管理器和一系列预置的Hook
函数。
首先,让我们看一下React
内部的Hook
管理器。这个管理器是React
内部的一个重要机制,它负责管理组件中的所有Hook
,并确保它们在组件渲染期间以正确的顺序调用。
内部Hook管理器
示例:
const Hook = { queue: [], current: null, }; function useState(initialState) { const state = Hook.current[Hook.queue.length]; if (!state) { Hook.queue.push({ state: typeof initialState === 'function' ? initialState() : initialState, setState(value) { this.state = value; render(); }, }); } return [state.state, state.setState.bind(state)]; } function useHook(callback) { Hook.current = { __proto__: Hook.current, }; try { callback(); } finally { Hook.current = Hook.current.__proto__; } } function render() { useHook(() => { const [count, setCount] = useState(0); console.log('count:', count); setTimeout(() => { setCount(count + 1); }, 1000); }); } render();
在这个示例中,Hook
对象有两个重要属性:queue
和current
。queue
存储组件中所有Hook
的状态和更新函数,current
存储当前正在渲染的组件的Hook
链表。useState
和useHook
函数则分别负责创建新的Hook
状态和在组件中使用Hook
。
预置 Hook 函数
useState Hook
以下是useState Hook
的实现示例:
function useState(initialState) { const hook = updateWorkInProgressHook(); if (!hook.memoizedState) { hook.memoizedState = [ typeof initialState === 'function' ? initialState() : initialState, action => { hook.queue.pending = true; hook.queue.dispatch = action; scheduleWork(); }, ]; } return hook.memoizedState; }
上述代码实现了useState Hook
,其主要作用是返回一个state
和更新函数的数组,state 初始值为initialState
。
在这个实现中,updateWorkInProgressHook()
函数用来获取当前正在执行的函数组件的 fiber 对象并判断是否存在对应的hook
。它的实现如下:
function updateWorkInProgressHook() { const fiber = getWorkInProgressFiber(); let hook = fiber.memoizedState; if (hook) { fiber.memoizedState = hook.next; hook.next = null; } else { hook = { memoizedState: null, queue: { pending: null, dispatch: null, last: null, }, next: null, }; } workInProgressHook = hook; return hook; }
getWorkInProgressFiber()
函数用来获取当前正在执行的函数组件的fiber
对象,workInProgressHook
则用来存储当前正在执行的hook
对象。在函数组件中,每一个useState
调用都会创建一个新的 hook 对象,并将其添加到fiber
对象的hooks
链表中。这个hooks
链表是通过fiber
对象的memoizedState
属性来维护的。
我们还需要注意到在useState Hook
的实现中,每一个hook
对象都包含了一个queue
对象,用来存储待更新的状态以及更新函数。scheduleWork()
函数则用来通知React
调度器有任务需要执行。
在React
的源码中,useState
函数实际上是一个叫做useStateImpl
的内部函数。
下面是useStateImpl
的源码:
function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
可以看到,useStateImpl
函数的作用就是获取当前的dispatcher
并调用它的useState
方法,返回一个数组,第一个元素是状态的值,第二个元素是一个dispatch
函数,用来更新状态。这里的resolveDispatcher
函数用来获取当前的dispatcher
,其实现如下:
function resolveDispatcher(): Dispatcher { const dispatcher = currentlyRenderingFiber?.dispatcher; if (dispatcher === undefined) { throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)'); } return dispatcher; }
resolveDispatcher
函数首先尝试获取当前正在渲染的fiber
对象的dispatcher
属性,如果获取不到则说
明当前不在组件的渲染过程中,就会抛出一个错误。
最后,我们来看一下useState
方法在具体的dispatcher
实现中是如何实现的。我们以useReducer
的
dispatcher
为例,它的实现如下:
export function useReducer<S, A>( reducer: (prevState: S, action: A) => S, initialState: S, initialAction?: A, ): [S, Dispatch<A>] { const [dispatch, currentState] = updateReducer<S, A>( reducer, // $FlowFixMe: Flow doesn't like mixed types [initialState, initialAction], // $FlowFixMe: Flow doesn't like mixed types reducer === basicStateReducer ? basicStateReducer : updateStateReducer, ); return [currentState, dispatch]; }
可以看到,useReducer
方法实际上是调用了一个叫做updateReducer
的函数,返回了一个包含当前状态和dispatch
函数的数组。updateReducer
的实现比较复杂,涉及到了很多细节,这里不再展开介绍。
useEffect Hook
useEffect
是React
中常用的一个Hook
函数,用于在组件中执行副作用操作,例如访问远程数据、添加/移除事件监听器、手动操作DOM
等等。useEffect
的核心功能是在组件的渲染过程结束之后异步执行回调函数,它的实现方式涉及到 React 中的异步渲染机制。
以下是useEffect Hook的实现示例:
function useEffect(callback, dependencies) { // 通过调用 useLayoutEffect 或者 useEffect 方法来获取当前的渲染批次 const batch = useContext(BatchContext); // 根据当前的渲染批次判断是否需要执行回调函数 if (shouldFireEffect(batch, dependencies)) { callback(); } // 在组件被卸载时清除当前 effect 的状态信息 return () => clearEffect(batch); }
在这个示例中,useEffect
接收两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,
React
会在下一次渲染时重新执行useEffect
中传入的回调函数。
useEffect
函数的实现方式主要依赖于React
中的异步渲染机制。当一个组件需要重新渲染时,React
会将所有的state
更新操作加入到一个队列中,在当前渲染批次结束之后再异步执行这些更新操作,从而避免在同一个渲染批次中连续执行多次更新操作。
在useEffect
函数中,我们通过调用useContext(BatchContext)
方法来获取当前的渲染批次,并根据shouldFireEffect
方法判断是否需要执行回调函数。在回调函数执行完毕后,我们需要通过clearEffect
方法来清除当前effect
的状态信息,避免对后续的渲染批次产生影响。
总结
总的来说,React Hooks
的实现原理并不复杂,它主要依赖于React
内部的fiber
数据结构和调度系统,通过这些机制来实现对组件状态的管理和更新。Hooks
能够让我们在函数组件中使用状态和其他React
特性,使得函数组件的功能可以和类组件媲美。
除了useState
、useEffect
等hook
,React
还有useContext
等常用的Hook
。它们的实现原理也基本相似,都是利用fiber
架构来实现状态管理和生命周期钩子等功能。
以上是hook
简单实现示例,它们并不是React
中实际使用的代码,但是可以帮助我们更好地理解hook
的核心实现方式。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
ArchKeeper(开篇):架构守护平台的问题与理念
作者:京东科技 倪新明 在敏捷开发环境下,系统通过迭代增量的交付价值,系统架构也是如此。团队不可能在项目之初就建立完美的系统架构,系统架构应该随着系统迭代不断演进。架构演进和架构腐化是看待架构的不同视角:架构腐化着眼于现状,架构演进侧重于未来架构腐化不可避免,随着时间流转腐化现象必然发生。而我们需要做的是:通过某种方式及早发现和修正 1 面临的问题 把目光从宏观的演进和腐化视角聚焦在更加具体的问题和挑战层面,作为团队负责人或架构师,你是否面临以下问题: •团队已经制定的开发规范很难持续性的落地,并在应用中保持较高的健康状态 •系统制定的架构决策在系统迭代过程中逐渐弱化、打破,甚至随着时间的推移,团队中已经没有人关注决策的落地和遵守情况 •历史的架构决策早已 ”无迹可寻“,更何谈对系统架构演进的追溯 •如何快速的判断当前系统腐化程度或健康情况? •不论是架构升级、系统演进,系统缺失指导性的自动化约束,确保演进方向不会偏离 基于以上的问题,我相信团队都会做过一些实践,不管是流程的强约束,还是系统的自动化,都尝试解决如上所面临的所有或部分问题。典型的方式: •方式一:代码评审(强流程) •方...
- 下一篇
DolphinDB 机器学习在物联网行业的应用:实时数据异常率预警
数据异常率预警在工业安全生产中是一项重要工作,对于监控生产过程的稳定性,保障生产数据的有效性,维护生产设备的可靠性具有重要意义。随着大数据技术在生产领域的深入应用,基于机器学习的智能预警已经成为各大生产企业进行生产数据异常率预警的一种有效解决方案。 1. 概要 本教程基于一个典型的物联网企业生产场景,利用 DolphinDB 内置的机器学习框架中的 KNN(K-Nearest Neighbor,最经典和最简单的有监督学习方法之一)算法构建回归模型,对实时数据异常率预警过程进行模拟。 2. 机器学习在物联网中的应用场景 2.1 工业物联网智能运维 随着整个物联网系统数据规模的急剧膨胀,以及服务类型的日趋多样化、复杂化,基于人为指定规则的手工运维和在此基础上加入自动化脚本实现的自动化运维已无法满足大规模的运维需求。 为解决人为指定规则带来的弊端,随着智能时代的到来,智能运维的概念逐渐浮出水面。与人为指定规则不同的是,智能运维强调利用机器学习的算法从海量运维数据中总结规则,打破了人为指定规则的局限性。 简而言之,智能运维在自动化运维的基础上加入了机器学习分析数据的环节,监测系统采集运维日志数...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19