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
的核心实现方式。
React Hooks源码深度解析的更多相关文章
- 源码深度解析SpringMvc请求运行机制(转)
源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请 ...
- SpringMVC 源码深度解析<context:component-scan>(扫描和注冊的注解Bean)
我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比較经经常使用XML配置.控制层依赖的service比較经经常使用注解等(在部署时比較不会改变的) ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
- 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析
在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- VueRouter 源码深度解析
VueRouter 源码深度解析 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还 ...
- Spring源码深度解析系列-----------org.springframework.aop-3.0.6.RELEASE
Spring源码深度解析系列-----------org.springframework.aop-3.0.6.RELEASE
- spring源码深度解析— IOC 之 容器的基本实现
概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...
- spring源码深度解析— IOC 之 默认标签解析(上)
概述 接前两篇文章 spring源码深度解析—Spring的整体架构和环境搭建 和 spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...
- spring源码深度解析— IOC 之 默认标签解析(下)
在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...
随机推荐
- 揭秘GES超大规模图计算引擎HyG:图切分
摘要:GES大规模图计算引擎HyG通过实现不同的点边分区算法,可以灵活地供用户选择多种多样的切分策略,进而达到更好的运算性能. 本文分享自华为云社区<GES超大规模图计算引擎HyG揭秘之图切分& ...
- 解析鸿蒙内核消息队列QueueMail接口的哼哈二将
摘要:本文带领大家一起剖析了鸿蒙轻内核的队列模块的QueueMail两个接口的源代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十三(续) 消息队列QueueMail接口>,作者: ...
- 前端资源共享方案对比-笔记:iframe/JS-SDK/微前端
前端页面资源如何分享,常见的有iframe,其次是js-sdk.这两类的在地图类工具经常用.微前端是最佳比较火的方式.本篇是他们的对比分析. 下一篇讲 BK-VISION如何在让用户自由选择 ifra ...
- 开心档之C++ 数据封装
C++ 数据封装 所有的 C++ 程序都有以下两个基本要素: **程序语句(代码):**这是程序中执行动作的部分,它们被称为函数. **程序数据:**数据是程序的信息,会受到程序函数的影响. 封装是面 ...
- Seal 软件供应链防火墙 v0.2 发布,提供依赖项全局洞察
Seal 软件供应链防火墙 v0.2 已于近日发布.这款产品旨在为企业提供代码安全.构建安全.依赖项安全及运行环境安全等4大防护,通过全链路扫描.问题关联及风险组织的方式保护企业软件供应链安全,降低企 ...
- 1024程序员节献礼,火山引擎ByteHouse带来三重产品福利
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流. 随着信息技术飞速发展,互联网.Web3.物联网.人工智能相继出现. 在这近三十年的高速发展中,"程序 ...
- SpringBoot 接口输出文件流 & Vue 下载文件流,获取 Header 中的文件名
SpringBoot 接口输出文件流 & Vue 下载文件流,获取 Header 中的文件名 @GetMapping("/download") public Respons ...
- Treap(平衡树)
Treap 前置芝士 二叉搜索树(BST),见 BST. 平衡二叉树(AVL). 先来介绍一下平衡二叉树. 背景 BST 出现以后,人们很快发现一个问题,当其维护一个有序序列时,很可能会退化成链.如图 ...
- Hadoop面试题总结(二)——HDFS
1. HDFS 中的 block 默认保存几份? 默认保存3份 2.HDFS 默认 BlockSize 是多大? 默认64MB 3.负责HDFS数据存储的是哪一部分? DataNode负责数据存储 4 ...
- k8s-部署 Kubernetes 集群(kubeadm方式)
一 机器准备 IP 主机名 角色 配置 安装组件 192.168.198.150 master-1 master 2U2G master组件 etcd kubectl 192.168.198.151 ...