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;//当前工作中的hooklet 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;//当前工作中的hooklet 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...