当前位置: 首页 > wzjs >正文

雄县网站建设公司百度app大全

雄县网站建设公司,百度app大全,app开发哪公司好,网页设计素材分析案例上篇我们说了updateQueue的实现原理,这篇我们说一下hooks, fiberHooks实现可以在react-reconciler/fiberHooks.ts 找到。 老生常谈的问题,为什么hooks有顺序,hook函数怎么知道你在哪运行的hooks? 下面我们逐一讨论。…

上篇我们说了updateQueue的实现原理,这篇我们说一下hooks,

fiberHooks实现可以在react-reconciler/fiberHooks.ts 找到。

老生常谈的问题,为什么hooks有顺序,hook函数怎么知道你在哪运行的hooks? 下面我们逐一讨论。

从入口开始

回忆一下,BeginWork.ts 会根据Fiber对象的tag属性,分配处理方法,其中,对于函数组件,会调用UpdateFunctionComponent 其实现如下:

/** 处理函数节点的比较 */
function updateFunctionComponent(wip: FiberNode,Component: Function,renderLane: Lane
): FiberNode {const nextChildElement = renderWithHooks(wip, Component, renderLane);reconcileChildren(wip, nextChildElement);return wip.child;
}

renderWithHooks就是用来运行函数体本身的, 其中也就包含了hooks的处理逻辑

一些全局变量

hooks的实现逻辑可以在 react-reconciler/fiberHook.ts中找到,其中包含了一些全局变量

  • currentRenderingFiber 当前正在渲染处理的Fiber对象
  • workInProgressHook 当前正在处理的hook
  • currentHook 当前正在处理hook对应的alternate FIber对象的hook (为了从currentHook获取属性)
  • renderLane 当前update对应的优先级lane

Hook数据结构

hook数据结构ts定义如下:

/** 定义Hook类型 */
export interface Hook {memorizedState: any;updateQueue: UpdateQueue<any>;next: Hook | null;
}

其中,包含当前hook的状态 memorizedState 这个对于不同的hook 作用是不同的,有的需要存储状态有的不需要,不需要存储状态的hook这个字段就是null

比如对于useState hook 其memorizedState对应的就是当前state的值

对于useCallback或者useMemo 其memorizedState对应的就是当前缓存的函数或者Memo值 等等

updateQueue就是当前hooks对应的更新队列,比如在useState中,就会吧每次调用setter的更新加入到这个队列中,但是在useMemo useCallback中 这个字段就不被使用

next指向下一个hook 也就是说 一个函数内的hook顺序,需要保持稳定,每次运行都会根据alternate Fiber重新创建新的hooks链,如果顺序变化会导致hook返回结果错乱

一个Fiber上的所有hooks都保存在其memorizedState上,和updateQueue不同的是,其是一个单项链表,不是环

function FnComp(){useState(100)useMemo(()=>{},[])useEffect(()=>{}),[]return <>...</>
}

如上函数组件,对应的hook结构如下: 

renderWithHooks 

renderWithHooks是运行函数组件,处理hooks的入口,其中初始化了函数运行的上下文,实现如下:

/** 运行函数组件以及hooks */
export function renderWithHooks(wip: FiberNode,Component: Function,lane: Lane
) {// 主要作用是,运行函数组件 并且在函数运行上下文挂载currentDispatcher 在运行之后 卸载Dispatcher// 保证hook只能在函数组件内运行// 设置当前正在渲染的fibercurrentRenderingFiber = wip;// 清空memoizedStatewip.memorizedState = null;// 重置 effect链表wip.updateQueue = null;// 当前已经渲染的fiberconst current = wip.alternate;renderLane = lane;if (current !== null) {// updatecurrentDispatcher.current = {useState: updateState,useEffect: updateEffect,useTransition: updateTransition,useRef: updateRef,useMemo: updateMemo,useCallback: updateCallback,};} else {// mountcurrentDispatcher.current = {useState: mountState,useEffect: mountEffect,useTransition: mountTransition,useRef: mountRef,useMemo: mountMemo,useCallback: mountCallback,};}// 运行函数const pendingProps = wip.pendingProps;const childrenElements = Component(pendingProps);// 恢复currentRenderingFiber = null;workInProgressHook = null;currentHook = null;currentDispatcher.current = null;renderLane = NoLane;return childrenElements;
}

其中可以看到,首先对currentRenderingFiber进行了初始化,设置为当前正在渲染的Fiber,这也就是为什么 hook知道自己在哪里被调用,就是在运行hook函数之前,currentRenderingFIber已经被设置了,hook函数内读取这个全局变量,就能获取当前正在处理的Fiber,在函数组件运行完之后,currentRenderingFiber会被恢复为null,任何在函数组件外调用的hook函数,由于没有这个全局变量,都会报 "无法在函数组件外部使用hooks" 

同时 会重置当前wip fiber的memorizedState和updateQueue 因为这些对象可能是旧的,在函数执行渲染的时候,会重新设置这些值。

renderLane也会通过传入的wipRenderedLane进行设置,在全局就可以直接获取当前正在渲染的lane

dispatcher 

dispatch是一个共享的对象,其被定义在react/currentDispatcher.ts 下,其中包含了React支持的所有hooks类型 ts定义如下

export interface Dispatcher {useState: <T>(initialState: T | (() => T)) => [T, Dispatch<T>];useEffect: (create: EffectCallback, deps: HookDeps) => EffectCallback | void;useTransition: () => [boolean, (callback: () => void) => void];useRef: <T>(initialValue: T) => { current: T };useMemo: <T>(nextCreate: () => T, deps: HookDeps) => T;useCallback: <T>(callback: T, deps: HookDeps) => T;
}

在currentDispatcher中 包含了一个共享的currentDisptacher对象,这个对象包含一个current属性,初始化为null, 同时包含一个resolveDispatcher函数,用来解析Dispatcher

/** 共享的 当前的Dispatcher */
export const currentDispatcher: { current: Dispatcher | null } = {current: null,
};/** 解析 返回dispatcher */
export function resolveDispatcher(): Dispatcher {const dispatcher = currentDispatcher.current;if (dispatcher === null) {throw new Error("无法在函数组件外部使用hooks");}return dispatcher;
}

在renderWithHook中,调用组件函数之前,会先引入这个currentDispatcher对象并且给其current属性设置hook函数

其会根据当前的Fiber是挂载状态mount 还是更新状态 update 来挂载不同的hook函数,是否为挂载还是更新由wip.alternate是否存在决定

// renderWithHookconst current = wip.alternate;if (current !== null) {// update 挂载更新hookcurrentDispatcher.current = {useState: updateState,useEffect: updateEffect,useTransition: updateTransition,useRef: updateRef,useMemo: updateMemo,useCallback: updateCallback,};} else {// mount 挂载mounthookcurrentDispatcher.current = {useState: mountState,useEffect: mountEffect,useTransition: mountTransition,useRef: mountRef,useMemo: mountMemo,useCallback: mountCallback,};}

在 react/index.ts中,暴露了所有hooks对应的接口, 部分如下:

export function useState<State>(initialState: (() => State) | State) {const dispatcher = resolveDispatcher();return dispatcher.useState(initialState);
}export function useEffect(create: EffectCallback, deps: HookDeps) {const dispatcher = resolveDispatcher();return dispatcher.useEffect(create, deps);
}export function useTransition() {const dispatcher = resolveDispatcher();return dispatcher.useTransition();
}export function useRef<T>(initialValue: T) {const dispatcher = resolveDispatcher();return dispatcher.useRef<T>(initialValue);
}... ... ...

其本质就是调用了resolveDispatcher函数获取当前对应的dispatcher对象,并且根据不同的hooks类型调用不同的处理函数。

当你在函数组件外部调用hook函数时,由于没有调用renderWithHook 所以没有给currentDispatcher.current赋dispatcher对象,所以在resolveDispatcher时,发现当前current属性为null,则报错 "无法在函数组件外部使用hooks"

在renderWithHook的组件函数运行完成之后,会重新把 currentDispatcher.current = null 同时也会把renderLane currentRenderingFiber 以及workInProgressHook currentHook都置空。并且返回child Element。

用这种方式 React就能识别到底在哪个Fiber中调用的hooks 以及判断是否在函数组件外部调用了hook

mount&update

对于挂载和更新阶段,虽然都是运行useXXX hook函数,但是由于通过wip.alternate判断挂载了不同的处理函数,其内部实现是不一样的,比如useState在挂载阶段对应的就是mountState处理函数,在更新阶段调用的就是updateState。

我们需要实现两个函数,获取当前的hook

我们上面说了 一个函数组件的Fiber的hook,以一个链表的形式挂载fiber对象的memorizedState上,我们就需要一个逻辑,能够返回每次待执行的hook对象

mountWorkInProgressHook

mountWorkInProgressHook 对应挂载阶段创建hooks链的过程,其实现如下

/** 挂载当前的workInProgressHook 并且返回 */
function mountWorkInProgressHook() {if (!currentRenderingFiber) {throw new Error("hooks必须在函数组件内部调用!");}const hook: Hook = {memorizedState: null,updateQueue: new UpdateQueue(),next: null,};// hook的挂载方式是 currentRenderdingFiber.memorizedState -> hook1 -next-> hook2 -next-> hook3 -next-> nullif (currentRenderingFiber.memorizedState === null) {// 第一次挂载currentRenderingFiber.memorizedState = hook;} else {// 非第一次挂载workInProgressHook.next = hook;}// 设置workInProgressHookworkInProgressHook = hook;return hook;
}

在初始化阶段,此时memorizedState为null,我们需要逐个创建hook对象

每次调用,意味着要创建一个hook对象,检查:

如果currentRenderingFiber的memorizedState如果为空,说明当前一个hook都没有,此时为函数组件的第一个hook,那么创建hook对象并赋在memorizedState属性和全局的workInProgressHoo

k上, 并且返回。

如果currentRenderingFiber.memorizedState不为空,那么说明前面已经有hook对象挂上去了,此时前面的对象是workInPrigressHook,那么在其next上挂上当前创建的hook对象即可,并且修改wipHook返回。

updateWorkInProgressHook

次函数主要作用在更新阶段,在更新阶段时,由于renderWithHook中已经将当前渲染Fiber的memorizedState置为null了。那么意味着hooks队列需要重新创建,但是此时的创建需要根据wip.alternate的Fiber对象来复用。实现如下:

/** 根据current 挂载当前的workInProgressHook 并且返回 */
function updateWorkInProgressHook() {if (!currentRenderingFiber) {throw new Error("hooks必须在函数组件内部调用!");}// 找到当前已经渲染的fiber -> currentconst current = currentRenderingFiber.alternate;// currentHook是指向current元素的hook指针if (currentHook === null) {// 当前还没有currentHook 第一个元素if (current) {currentHook = current.memorizedState;} else {currentHook = null;}} else {// 如果有currentHook 说明不是第一个hookcurrentHook = currentHook.next;}// 如果没找到currentHook 说明hook数量对不上if (currentHook === null) {throw new Error("render more hooks than previouse render!");}// 拿到currentHook了 需要根据其构建当前的workInProgrerssHookconst hook: Hook = {memorizedState: currentHook.memorizedState,updateQueue: currentHook.updateQueue,next: null,};if (currentRenderingFiber.memorizedState === null) {currentRenderingFiber.memorizedState = hook;} else {workInProgressHook.next = hook;}workInProgressHook = hook;return hook;
}

和mount阶段类似,多了一个在currentRenderingFiber.alternate.memorizedState上找currentHook的过程,并且根据找到的旧的currentHook,给当前创建的新的Hook对象的updateQueue memorizedState赋值,并挂载新的hook到当前wip.memorizedState链上!

需要注意的是,如果在更新阶段没找到currentHook,那么说明此次更新一定比上次更新渲染了更多的hook函数,此时就会因为hooks无法对应报错!

说完了最基础创建复用hook的逻辑,我们就逐个看一下主要hooks的原理

useState 

useState用来在函数组件内记录状态,虽然函数组件是纯函数,但是每次运行到hook函数时会返回上次记录的值。

这是代数效应的思想,通过引入useState这个代数符号把副作用拿到函数外部。

mountState

首先看mountState的逻辑

/** 挂载state */
function mountState<T>(initialState): [T, Dispatch<T>] {const hook = mountWorkInProgressHook();let memorizedState: T;// 计算初始值if (typeof initialState === "function") {memorizedState = initialState();} else {memorizedState = initialState;}// 挂载memorizedState到hook 注意别挂载错了 currentRenderingFiber 也有一样的memorizedStatehook.memorizedState = memorizedState;// 设置hook.taskQueue.dispatch 并且返回,注意dispatch是可以拿到函数组件外部使用的,所以这里需要绑定当前渲染fiber和updateQueuehook.updateQueue.dispatch = dispatchSetState.bind(null,currentRenderingFiber,hook.updateQueue);hook.updateQueue.baseState = memorizedState;// 保存上一次的值hook.updateQueue.lastRenderedState = memorizedState;return [memorizedState, hook.updateQueue.dispatch];
}

挂载阶段的逻辑很简单,就是

首先获取当前的hook对象,根据传入的initialState的类型获取初始值,这个和action的处理逻辑类似!

把初始值,保存在当前hook对象的memorizedState中

这里你需要区分Fiber对象的memorizedState和Hook对象的memorizedState 

给UpdateQueue的baseState赋初始值

给updateQueue的lastRenderdState赋初始值,这个逻辑在eagerState中用到 先忽略

给dispatchSetState函数绑定当前渲染的Fiber对象和当前hook的updateQueue,为什么要绑定,因为dispatchSetState就对应useState的第二个数组项setter,setter可以在任意位置调用,所以要保存当前的Fiber和updateQueue信息。

把绑定的函数赋给updateQueue.dispatch属性,作为更新队列的更新器

最后将memorizedState和hook.updateQueue.dispatch封装成数组返回

dispatchSetState

这个函数用来派发更新,前面也讲过,其逻辑如下:

/** 派发修改state */
function dispatchSetState<State>(fiber: FiberNode,updateQueue: UpdateQueue<State>,action: Action<State>
) {// 获取一个优先级 根据 dispatchSetState 执行所在的上下文const lane = requestUpdateLane();// 创建一个update对象const update = new Update(action, lane);// 入队 并且加入到fiber上updateQueue.enqueue(update, fiber, lane);// 开启调度时,也需要传入当前优先级scheduleUpdateOnFiber(fiber, lane);
}

其本质就是获取当前上下文的优先级Lane,创建更新,推入updateQueue,并且开启调度。

 updateState

updateState作用于更新阶段,其逻辑如下:

function updateState<T>(): [T, Dispatch<T>] {const hook = updateWorkInProgressHook();const { memorizedState } = hook.updateQueue.process(renderLane,(update) => {currentRenderingFiber.lanes = mergeLane(currentRenderingFiber.lanes,update.lane);});hook.memorizedState = memorizedState;hook.updateQueue.lastRenderedState = memorizedState;return [memorizedState, hook.updateQueue.dispatch];
}

其实现也很简单,就是调用当前hook的updateQueue中的process方法,处理更新,返回新的状态值

更新当前hook的memorizedState以及updateQueue.lastRenderState 返回状态和dispatch方法。

useMemo & useCallback 

这两个hook主要用于缓存变量和函数,大多情况下需要配合React.memo使用,其实现也很简单

mountMemo

挂载情况下,useMemo传入一个Create函数,一个deps数组,mount状态下的逻辑就是调用Create函数,把返回值和数组deps存入hook.memorizedState 如下

function mountMemo<T>(nextCreate, deps) {const hook = mountWorkInProgressHook();hook.memorizedState = [nextCreate(), deps];return hook.memorizedState[0];
}
updateMemo

updateMemo需要判断,两次传入的deps是否相等,如果相等则还返回上次保存的值,如果不等就重新运行Create函数,并且返回

function updateMemo<T>(nextCreate, deps) {const hook = updateWorkInProgressHook();const [prevValue, prevDeps] = hook.memorizedState;if (areHookInputsEqual(prevDeps, deps)) {hook.memorizedState = [prevValue, deps];} else {hook.memorizedState = [nextCreate(), deps];}return hook.memorizedState[0];
}

其中,deps为数组,比较两个数组是否相等,使用areHookInputsEqual 

areHookInputsEuqal 判读两个deps数组相等

areHookInputsEuqal用来判断数组每个对应位置上的值是否相等,其判断的方式不是 == 或者 === 而是Object.is 对于NaN +0 -0 有不同的处理

/** 潜比较Deps */
function areHookInputsEqual(prevDeps: HookDeps, curDeps: HookDeps) {if (prevDeps === null || curDeps === null) return false;if (prevDeps?.length !== curDeps?.length) return false;for (let i = 0; i < prevDeps.length; i++) {if (Object.is(prevDeps[i], curDeps[i])) {continue;}return false;}return true;
}
 mountCallback & updateCallback

对于useCallback 和 useMemo一样,只不过由于其缓存的是函数,对于传入的callback不运行,直接返回,如下:

/** useCallback */
function mountCallback<T>(callback, deps) {const hook = mountWorkInProgressHook();hook.memorizedState = [callback, deps];return hook.memorizedState[0];
}function updateCallback<T>(callback, deps) {const hook = updateWorkInProgressHook();const [prevCallback, prevDeps] = hook.memorizedState;if (areHookInputsEqual(prevDeps, deps)) {hook.memorizedState = [prevCallback, deps];} else {hook.memorizedState = [callback, deps];}return hook.memorizedState[0];
}

 useRef

useRef本质上就是保存一个值,这个值可以用作ref处理,也可以用来保存一些你希望保存但是不希望触发更新的值,如下:

/** 挂载Ref */
function mountRef<T>(initialValue: T): { current: T } {const hook = mountWorkInProgressHook();hook.memorizedState = { current: initialValue };return hook.memorizedState;
}/** 更新Ref 其实就是保存一个值 */
function updateRef<T>(): { current: T } {const hook = updateWorkInProgressHook();return hook.memorizedState;
}

useTransition

useTransition是React@18 引入的hooks 其返回一个[isPending, startTransition]

其中,startTranstion传入一个callback,callback内创建的更新会被赋低优先级 即 TranstionLane

在需要渲染一些大规模的更新时,建议将其放在startTranstion中,这样如果在渲染过程中触发用户事件,由于用户事件优先级更高,会中断render过程去处理用户事件对应的更新,达到页面不卡死的效果!

mountTranstion

mount阶段代码如下:

/** transition */
function mountTransition() {// 设置pending stateconst [isPending, setPending] = mountState<boolean>(false);// 获得hookconst hook = mountWorkInProgressHook();// 创建startTransitionconst start = startTransition.bind(null, setPending);// 记录starthook.memorizedState = start;// 返回pending和startreturn [isPending, start] as [boolean, (callback: () => void) => void];
}

mount阶段,首先需要在内部设置一个isPendingHooks ,相当于帮用户创建了一个isPending hook

获取当前hook 并且把startTranstion绑定上isPending的setter 存入memorizedState 并且和isPending一起返回

updateTranstion

 updateTranstion很简单,只需要将上次更新存储的start函数,以及本次更新计算出的isPending返回即可!

function updateTransition() {const [isPending] = updateState<boolean>();const hook = updateWorkInProgressHook();const start = hook.memorizedState;return [isPending, start] as [boolean, (callback: () => void) => void];
}

下面我们着重看一下startTranstion函数

startTranstion

startTranstion的逻辑如下

function startTransition(setPending: Dispatch<boolean>, callback: () => void) {// 开始transition 第一次更新 此时优先级高setPending(true);// transition过程,下面的优先级低const prevTransition = isTransition;// 设置标记 表示处于transition过程中,在fiberHook.ts/requestUpdateLane会判断这个变量,如果true则返回transtionLaneisTransition = true;// 设置标记 (在react原版中 这里是 1)// 第二次更新 优先级低callback();// 第三次更新 重新设置pending 优先级低setPending(false);// 恢复isTransitionisTransition = prevTransition;
}

 在FiberHooks中 导出了全局变量isTranstion 

// 导出共享变量
export let isTransition = false;

当startTransition运行的过程中,会先把isPending更新为true,此次更新对应的Update和调用startTranstion之前外部上下文的优先级一致,比如是SyncLane,是一个比较高的优先级

设置之后,会把全局的isTranstion设置为true 并且运行callback

callback中,如果触发更新,就一定要通过requestUpdateLane获取当前上下文的优先级,在其中

export function requestUpdateLane(): Lane {if (isTransition) {return TransitionLane}const currentUpdateLane = schedulerPriorityToLane(scheduler.getCurrentPriorityLevel());return currentUpdateLane;
}

会检查,如果当前isTranstion变量为true,就会直接返回一个低优先级的TranstionLane

 此时 callback内部的更新就是低优先级了

同时,startTranstion也会在isTranstion=true的范围内,设置isPending为false,这个更新也是低优先级的,这样就保证了在低优先级更新时,isPending才会变成false

最后恢复现场,把isTranstion改成之前的值,结束运行

用这样的方式,实现了过渡效果

useDeferedValue 

和useTranstion同理,本质上也是给一个低优先级的DeferedLane,只有当renderLane处理到这个DeferedLane时,才更新数据,代码如下,不过多赘述。

function updateDeferedValue<T>(value: T) {const hook = updateWorkInProgressHook();const prevValue = hook.memorizedState;// 相同 没变化,直接返回if (Object.is(value, prevValue)) return value;if (isSubsetOfLanes(renderLane, DeferredLane)) {// 低优先级DeferedLane时hook.memorizedState = value;return value;} else {// 优先级高于Deferedlane时currentRenderingFiber.lanes |= DeferredLanescheduleUpdateOnFiber(currentRenderingFiber, DeferredLane);return prevValue;}
}function mountDeferedValue<T>(value: T) {const hook = mountWorkInProgressHook();hook.memorizedState = value;return hook.memorizedState;
}

 useEffect 由于要涉及到commit 副作用收集的过程,在后面说!

 

 

http://www.dtcms.com/wzjs/134942.html

相关文章:

  • 品牌策划经典案例上海外贸seo
  • 上蔡县做彩票网站2022年最火的电商平台
  • 签证中心网站建设什么网站推广比较好
  • 眉山手机网站建设关键词竞价排名是什么意思
  • 网络商城推广网站seo专员
  • 长沙网站建设0731中国四大软件外包公司
  • 自己找厂家做代理卖货seo优化网络推广
  • 三合一网站建设平台上海宝山网站制作
  • 成人函授大专报名官网百度手机端排名如何优化
  • 新郑市住房建设局网站seo站群优化技术
  • python做网站网络推广的常用方法
  • 做微网站自己开发网站
  • 二手商品网站制作福州网络营销推广公司
  • 两学一做网站专栏怎么恶意点击对手竞价
  • 教做缝纫的网站上海百度推广电话客服
  • 中企动力 做网站 怎么样semir
  • 网络营销的网站分类有seo企业优化方案
  • 滨州企业做网站百度关键词排名神器
  • 网站与公众号的区别seo sem是啥
  • 石碣镇网站建设windows优化大师好用吗
  • 家居网站建设的背景及意义seo的优化流程
  • 别的网站可以做弹幕免费建站平台哪个好
  • 宣城建设网站工厂管理培训课程
  • 嘉兴代办公司注册公司seo站长工具综合查询
  • 免费学校网站建设网站快速排名推广软件
  • 西安招聘网站搜索引擎平台有哪些
  • 网站 怎么 做压力测试app推广项目从哪接一手
  • 政府网站建设管理计划网站建设有哪些公司
  • 松原公司做网站的流程灰色seo关键词排名
  • html5动态网站巨量算数关键词查询