共计 8718 个字符,预计需要花费 22 分钟才能阅读完成。
hello,这里是潇晨,明天就带着大家一起来手写一个迷你版的 hooks
,不便大家了解hook
在源码中的运行机制,配有图解,保姆级的教程,只求同学一个小小的👍,🐶。
第一步:引入 React 和 ReactDOM
因为咱们要将 jsx
转变为 virtual-dom
,这一步分工作就交给babel
吧,而 jsx
被babel
进行词法解析之后会造成 React.createElement()
的调用,而 React.createElement()
执行之后的返回后果就是 jsx
对象或者叫virtual-dom
。
又因为咱们要将咱们的 demo 渲染到 dom
上,所以咱们引入ReactDOM
。
import React from "react";
import ReactDOM from "react-dom";
第二步:咱们来写一个小 demo
咱们定义两个状态 count
和age
,在点击的时候触发更新,让它们的值加 1。
在源码中 useState
是保留在一个 Dispatcher
对象下面的,并且在 mount
和update
的时候取到的是不同的 hooks
,所以咱们先临时从Dispatcher
上拿到useState
,等下在来定义Dispatcher
。
接下来定义一个 schedule
函数,每次调用的时候会从新渲染组件。
function App() {let [count, setCount] = Dispatcher.useState(1);
let [age, setAge] = Dispatcher.useState(10);
return (
<>
<p>Clicked {count} times</p>
<button onClick={() => setCount(() => count + 1)}> Add count</button>
<p>Age is {age}</p>
<button onClick={() => setAge(() => age + 1)}> Add age</button>
</>
);
}
function schedule() { // 每次调用会从新渲染组件
ReactDOM.render(<App />, document.querySelector("#root"));
}
schedule();
第三步:定义 Dispatcher
在看这部分前,先来捋分明 fiber
、hook
、update
的关系,看图:
Dispatcher
是什么:Dispatcher
在源码中就是一个对象,下面寄存着各种各样的 hooks
,在mount
和update
的时候会应用过不同的 Dispatcher
,来看看在源码中Dispatcher
是什么样子:
在调用 useState
之后,会调用一个 resolveDispatcher
的函数,这个函数调用之后会返回一个 dispatcher
对象,这个对象上就有 useState
等钩子。
那咱们来看看这个函数做了啥事件,这个函数比较简单,间接从 ReactCurrentDispatcher
对象上拿到 current
,而后返回进去的这个current
就是 dispatcher
,那这个ReactCurrentDispatcher
又是个啥?别急,持续在源码中来找一下。
在源码中有这样一段代码,如果是在正式环境中,分为两种状况
- 如果满足
current === null || current.memoizedState === null
,阐明咱们处于首次渲染的时候,也就是mount
的时候,其中current
就是咱们fiber
节点,memoizedState
保留了fiber
上hook
,也就是说在利用首次渲染的时候,current fiber
是不存在的,咱们还没有发明出任何fiber
节点,或者存在某些fiber
,然而下面没有构建相应的hook
,这个时候就能够认为是处于首次渲染的时候,咱们取到的是HooksDispatcherOnMount
- 如果不满足
current === null || current.memoizedState === null
,就阐明咱们处于更新阶段,也就是update
的时候,咱们取到的是HooksDispatcherOnUpdate
if (__DEV__) {if (current !== null && current.memoizedState !== null) {ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;} else if (hookTypesDev !== null) {ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;} else {ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;}
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
那咱们就来看一下这个 HooksDispatcherOnMount
和HooksDispatcherOnUpdate
是个什么,好家伙,原来你蕴含了所有的 hooks 啊。
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useOpaqueIdentifier: mountOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useOpaqueIdentifier: updateOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
所以 dispatcher
就是个对象,外面蕴含了所有的 hooks
,在首次渲染和更新的时候拿到的是不同的dispatcher
,在调用hooks
的时候就会调用到不同的函数,比方如果应用了 useState
,在mount
的时候调用到的就是 mountState
,在update
的时候调用到的就是updateState
。
当初咱们来手写一下 dispatcher
,dispatcher
是个对象,对象上存在useState
,咱们用一个自执行函数来示意,此外还须要用到两个变量和一个常量fiber
workInProgressHook
示意遍历到的hook
(因为hook
会保留在链表上,须要遍历链表计算hook
上保留的状态)- 为了简略起见,定义一个
isMount=true
示意mount
的时候,在update
的时候将它设置成false
, - 为简略起见,
fiber
就定义成一个对象,memoizedState
示意这个fiber
节点上寄存的hook
链表,stateNode
就是第二步的 demo。
相干参考视频解说:进入学习
let workInProgressHook;// 当前工作中的 hook
let isMount = true;// 是否时 mount 时
const fiber = {//fiber 节点
memoizedState: null,//hook 链表
stateNode: App
};
const Dispatcher = (() => {//Dispatcher 对象
function useState(){//。。。}
return {useState};
})();
在定义 useState
之前,首先来看看 hook
和update
的数据结构
hook:
queue
:下面有pending
属性,pending
也是一条环状链表,下面寄存了未被更新的update
,也就是说这些update
会以next
指针连接成环状链表。memoizedState
示意以后的状态next
:指向下一个hook
,造成一条链表
const hook = {// 构建 hook
queue: {pending: null// 未执行的 update 链表},
memoizedState: null,// 以后 state
next: null// 下一个 hook
};
update:
action
:是登程更新的函数next
:连贯下一个update
,造成一条环状链表
const update = {// 构建 update
action,
next: null
};
那接下来定义 useState
吧,分三个局部:
-
创立
hook
或取到hook
:- 在
mount
的时候:调用mountWorkInProgressHook
创立一个初始的hook
,赋值useState
传进来的初始值initialState
- 在
update
的时候:调用updateWorkInProgressHook
,拿到以后正在工作的hook
- 在
- 计算
hook
上未更新的状态:遍历hook
上的pending
链表,调用链表节点上的action
函数,生成一个新的状态,而后更新hook
上的状态。 - 返回新的状态和
dispatchAction
传入queue
参数
function useState(initialState) {
// 第 1 步:创立 hook 或取到 hook
let hook;
if (isMount) {hook = mountWorkInProgressHook();
hook.memoizedState = initialState;// 初始状态
} else {hook = updateWorkInProgressHook();
}
// 第 2 步:计算 hook 上未更新的状态
let baseState = hook.memoizedState;// 初始状态
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;// 第一个 update
do {
const action = firstUpdate.action;
baseState = action(baseState);// 调用 action 计算新的状态
firstUpdate = firstUpdate.next;// 通过 update 的 action 计算 state
} while (firstUpdate !== hook.queue.pending);// 当链表还没遍历完时 进行循环
hook.queue.pending = null;// 重置 update 链表
}
hook.memoizedState = baseState;// 赋值新的 state
// 第 3 步:返回新的状态和 dispatchAction 传入 queue 参数
return [baseState, dispatchAction.bind(null, hook.queue)];//useState 的返回
}
接下来定义 mountWorkInProgressHook
和updateWorkInProgressHook
这两个函数
-
mountWorkInProgressHook
:在mount
的时候调用,新创建一个hook
对象,- 如果以后
fiber
不存在memoizedState
,那以后hook
就是这个fiber
上的第一个hook
,将hook
赋值给fiber.memoizedState
- 如果以后
fiber
存在memoizedState
,那将以后hook
接在workInProgressHook.next
前面。 - 将以后
hook
赋值给workInProgressHook
- 如果以后
updateWorkInProgressHook
:在update
的时候调用,返回以后的hook
,也就是workInProgressHook
,并且将workInProgressHook
指向hook
链表的下一个。
function mountWorkInProgressHook() {//mount 时调用
const hook = {// 构建 hook
queue: {pending: null// 未执行的 update 链表},
memoizedState: null,// 以后 state
next: null// 下一个 hook
};
if (!fiber.memoizedState) {fiber.memoizedState = hook;// 第一个 hook 的话间接赋值给 fiber.memoizedState} else {workInProgressHook.next = hook;// 不是第一个的话就加在上一个 hook 的前面,造成链表}
workInProgressHook = hook;// 记录当前工作的 hook
return workInProgressHook;
}
function updateWorkInProgressHook() {//update 时调用
let curHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;// 下一个 hook
return curHook;
}
第四步:定义 dispatchAction
-
创立
update
,挂载载queue.pending
上- 如果之前
queue.pending
不存在,那创立的这个update
就是第一个,则update.next = update
- 如果之前
queue.pending
存在,则将创立的这个update
退出queue.pending
的环状链表中
- 如果之前
- 让
isMount=false
,并且赋值workInProgressHook
,调用schedule
进行更新渲染
function dispatchAction(queue, action) {// 触发更新
const update = {// 构建 update
action,
next: null
};
if (queue.pending === null) {update.next = update;//update 的环状链表} else {
update.next = queue.pending.next;// 新的 update 的 next 指向前一个 update
queue.pending.next = update;// 前一个 update 的 next 指向新的 update
}
queue.pending = update;// 更新 queue.pending
isMount = false;// 标记 mount 完结
workInProgressHook = fiber.memoizedState;// 更新 workInProgressHook
schedule();// 调度更新}
最终代码
import React from "react";
import ReactDOM from "react-dom";
let workInProgressHook;// 当前工作中的 hook
let isMount = true;// 是否时 mount 时
const fiber = {//fiber 节点
memoizedState: null,//hook 链表
stateNode: App//dom
};
const Dispatcher = (() => {//Dispatcher 对象
function mountWorkInProgressHook() {//mount 时调用
const hook = {// 构建 hook
queue: {pending: null// 未执行的 update 链表},
memoizedState: null,// 以后 state
next: null// 下一个 hook
};
if (!fiber.memoizedState) {fiber.memoizedState = hook;// 第一个 hook 的话间接赋值给 fiber.memoizedState} else {workInProgressHook.next = hook;// 不是第一个的话就加在上一个 hook 的前面,造成链表}
workInProgressHook = hook;// 记录当前工作的 hook
return workInProgressHook;
}
function updateWorkInProgressHook() {//update 时调用
let curHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;// 下一个 hook
return curHook;
}
function useState(initialState) {
let hook;
if (isMount) {hook = mountWorkInProgressHook();
hook.memoizedState = initialState;// 初始状态
} else {hook = updateWorkInProgressHook();
}
let baseState = hook.memoizedState;// 初始状态
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;// 第一个 update
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;// 循环 update 链表
} while (firstUpdate !== hook.queue.pending);// 通过 update 的 action 计算 state
hook.queue.pending = null;// 重置 update 链表
}
hook.memoizedState = baseState;// 赋值新的 state
return [baseState, dispatchAction.bind(null, hook.queue)];//useState 的返回
}
return {useState};
})();
function dispatchAction(queue, action) {// 触发更新
const update = {// 构建 update
action,
next: null
};
if (queue.pending === null) {update.next = update;//update 的环状链表} else {
update.next = queue.pending.next;// 新的 update 的 next 指向前一个 update
queue.pending.next = update;// 前一个 update 的 next 指向新的 update
}
queue.pending = update;// 更新 queue.pending
isMount = false;// 标记 mount 完结
workInProgressHook = fiber.memoizedState;// 更新 workInProgressHook
schedule();// 调度更新}
function App() {let [count, setCount] = Dispatcher.useState(1);
let [age, setAge] = Dispatcher.useState(10);
return (
<>
<p>Clicked {count} times</p>
<button onClick={() => setCount(() => count + 1)}> Add count</button>
<p>Age is {age}</p>
<button onClick={() => setAge(() => age + 1)}> Add age</button>
</>
);
}
function schedule() {ReactDOM.render(<App />, document.querySelector("#root"));
}
schedule();
预览成果:https://codesandbox.io/s/cust…