React Hooks源码深度解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 doesnt like mixed types
[initialState, initialAction],
// $FlowFixMe: Flow doesnt 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源码深度解析的主要内容,如果未能解决你的问题,请参考以下文章

React Typescript with hooks:最大更新深度超出错误

react源码解析18事件系统

react源码解析19.手写迷你版react

react源码解析8.render阶段

react源码解析9.diff算法

react源码解析19.手写迷你版react