来来来,手摸手写一个hook

来来来,手摸手写一个hook hello,这里是潇晨,今天就带着大家一起来手写一个迷你版的hooks,方便大家理解hook在源码中的运行机制,配有图解,保姆级的教程,只求同学一个小小的,。
第一步:引入React和ReactDOM 因为我们要将jsx转变为virtual-dom,这一步分工作就交给babel吧,而jsxbabel进行词法解析之后会形成React.createElement()的调用,而React.createElement()执行之后的返回结果就是jsx对象或者叫virtual-dom
又因为我们要将我们的demo渲染到dom上,所以我们引入ReactDOM

import React from "react"; import ReactDOM from "react-dom";

第二步:我们来写一个小demo 我们定义两个状态countage,在点击的时候触发更新,让它们的值加1。
在源码中useState是保存在一个Dispatcher对象上面的,并且在mountupdate的时候取到的是不同的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 在看这部分前,先来捋清楚fiberhookupdate的关系,看图:
来来来,手摸手写一个hook
文章图片

Dispatcher是什么:Dispatcher在源码中就是一个对象,上面存放着各种各样的hooks,在mountupdate的时候会使用过不同的Dispatcher,来看看在源码中Dispatcher是什么样子:
在调用useState之后,会调用一个resolveDispatcher的函数,这个函数调用之后会返回一个dispatcher对象,这个对象上就有useState等钩子。
来来来,手摸手写一个hook
文章图片

那我们来看看这个函数做了啥事情,这个函数比较简单,直接从ReactCurrentDispatcher对象上拿到current,然后返回出来的这个current就是dispatcher,那这个ReactCurrentDispatcher又是个啥?别急,继续在源码中来找一下。
来来来,手摸手写一个hook
文章图片

在源码中有这样一段代码,如果是在正式环境中,分为两种情况
  1. 如果满足 current === null || current.memoizedState === null,说明我们处于首次渲染的时候,也就是mount的时候,其中current就是我们fiber节点,memoizedState保存了fiberhook,也就是说在应用首次渲染的时候,current fiber是不存在的,我们还没有创造出任何fiber节点,或者存在某些fiber,但是上面没有构建相应的hook,这个时候就可以认为是处于首次渲染的时候,我们取到的是HooksDispatcherOnMount
  2. 如果不满足 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; }

那我们就来看一下这个HooksDispatcherOnMountHooksDispatcherOnUpdate是个什么,好家伙,原来你包含了所有的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
文章图片

现在我们来手写一下dispatcherdispatcher是个对象,对象上存在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之前,首先来看看hookupdate的数据结构
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
    1. mount的时候:调用mountWorkInProgressHook创建一个初始的hook,赋值useState传进来的初始值initialState
    2. 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的返回 }

接下来定义mountWorkInProgressHookupdateWorkInProgressHook这两个函数
  • mountWorkInProgressHook:在mount的时候调用,新创建一个hook对象,
    1. 如果当前fiber不存在memoizedState,那当前hook就是这个fiber上的第一个hook,将hook赋值给fiber.memoizedState
    2. 如果当前fiber存在memoizedState,那将当前hook接在workInProgressHook.next后面。
    3. 将当前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
    1. 如果之前queue.pending不存在,那创建的这个update就是第一个,则update.next = update
    2. 如果之前queue.pending存在,则将创建的这个update加入queue.pending的环状链表中
    来来来,手摸手写一个hook
    文章图片

  • 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.总结&第一章的面试题解答

    推荐阅读