来来来,手摸手写一个hook
来来来,手摸手写一个hook
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 (
<>Clicked {count} times
Age is {age}
>
);
}function schedule() {//每次调用会重新渲染组件
ReactDOM.render(, document.querySelector("#root"));
}schedule();
第三步:定义Dispatcher 在看这部分前,先来捋清楚
fiber
、hook
、update
的关系,看图:![来来来,手摸手写一个hook](https://img.it610.com/image/info9/d317c4c9f17348fcb69b710158dc49be.jpg)
文章图片
Dispatcher
是什么:Dispatcher
在源码中就是一个对象,上面存放着各种各样的hooks
,在mount
和update
的时候会使用过不同的Dispatcher
,来看看在源码中Dispatcher
是什么样子:在调用
useState
之后,会调用一个resolveDispatcher
的函数,这个函数调用之后会返回一个dispatcher
对象,这个对象上就有useState
等钩子。![来来来,手摸手写一个hook](https://img.it610.com/image/info9/16f00baf12b043af980f25fbcdf15dd9.jpg)
文章图片
那我们来看看这个函数做了啥事情,这个函数比较简单,直接从
ReactCurrentDispatcher
对象上拿到current
,然后返回出来的这个current
就是dispatcher
,那这个ReactCurrentDispatcher
又是个啥?别急,继续在源码中来找一下。![来来来,手摸手写一个hook](https://img.it610.com/image/info9/952c691e19df49faa379c858cfd09e3b.jpg)
文章图片
在源码中有这样一段代码,如果是在正式环境中,分为两种情况
- 如果满足
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
。![来来来,手摸手写一个hook](https://img.it610.com/image/info9/17dae41bdaf440fc99a331c6c0fca1c1.jpg)
文章图片
现在我们来手写一下
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;
//第一个updatedo {
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.pendingisMount = 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;
//第一个updatedo {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
//循环update链表
} while (firstUpdate !== hook.queue.pending);
//通过update的action计算statehook.queue.pending = null;
//重置update链表
}
hook.memoizedState = baseState;
//赋值新的statereturn [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.pendingisMount = false;
//标志mount结束
workInProgressHook = fiber.memoizedState;
//更新workInProgressHook
schedule();
//调度更新
}function App() {
let [count, setCount] = Dispatcher.useState(1);
let [age, setAge] = Dispatcher.useState(10);
return (
<>Clicked {count} times
Age is {age}
>
);
}function schedule() {
ReactDOM.render(, document.querySelector("#root"));
}schedule();
预览效果:https://codesandbox.io/s/cust...
视频讲解(高效学习):点击学习 往期react源码解析文章: 1.开篇介绍和面试题
2.react的设计理念
3.react源码架构
4.源码目录结构和调试
5.jsx&核心api
6.legacy和concurrent模式入口函数
7.Fiber架构
8.render阶段
9.diff算法
10.commit阶段
【来来来,手摸手写一个hook】11.生命周期
12.状态更新流程
13.hooks源码
14.手写hooks
15.scheduler&Lane
16.concurrent模式
17.context
18事件系统
19.手写迷你版react
20.总结&第一章的面试题解答
推荐阅读
- 9班|9班 刘志雪
- 一个人的碎碎念
- 第326天
- 猎杀IP
- 我从来不做坏事
- 年味真的是越来越淡了么
- 感恩之旅第75天
- 参保人员因患病来不及到指定的医疗机构就医,能否报销医疗费用()
- 雅集
- 我执意要等,是因为我相信你一定会来