React Hooks 源码深度解析
来源 | OSCHINA 社区
作者 | 京东云开发者-京东零售 郑炳懿
原文链接:https://my.oschina.net/u/4090830/blog/8527395
前言
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 中的异步渲染机制。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
的核心实现方式。
END
这里有最新开源资讯、软件更新、技术干货等内容
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦
微信扫码关注该文公众号作者
戳这里提交新闻线索和高质量文章给我们。
来源: qq
点击查看作者最近其他文章