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

深圳网站设计建设百度投诉电话

深圳网站设计建设,百度投诉电话,wordpress子目录 多站点,大型门户网站建设一般多少钱前面的文章介绍了 hooks和commit流程,算是前置知识,这篇来讨论一下useEffect的原理。 useEffect用来处理副作用,比如网络请求,dom操作等等, 其本质也是个hooks,包含hooks的memorizedState, updateQueue, next Effec…

前面的文章介绍了 hooks和commit流程,算是前置知识,这篇来讨论一下useEffect的原理。

useEffect用来处理副作用,比如网络请求,dom操作等等, 其本质也是个hooks,包含hooks的memorizedState, updateQueue, next 

Effect对象

useEfffect这个hooks的memorizedState存储的是Effect对象,其ts定义为

/** 定义effect */
export interface Effect {tags: HookEffectTag;// 依赖数组deps: HookDeps;// 传入的创建effectcreate: EffectCallback | null;// 清除effectdestory: EffectCallback | null;// next 用来连接在updateQueu中的Effect对象,Effect存在hook.memorizedState和fiber.updateQueue上next: Effect;
}

其中,tags定义为HookEffectTag,其为数字类型,定义在hookEffectTag.ts

export const Passive = 0b0010;
// 当前hook存在effect需要处理
export const HookHasEffect = 0b0001;export type HookEffectTag = number;

其中,Passive表示当前存在useEffect,而HookHasEffect表示当前useEffect在本次更新中需要处理。

deps就是依赖数组,类比useMemo useCallback

create是useEffect传入的create函数

destory是create函数返回的销毁函数

next表示下一个Effect对象

虽然useEffect本身也是个Hook,但是其hook节点的updateQueue是没有被使用的。useEffect将其Effect对象保存在memorizedState,并且使用了当前渲染Fiber上的updateQueue来存储Effect链,结构如下:

FCUpdateQueue

其中,对于函数类型的Fiber,其updateQueue的作用就是存储当前函数组件挂载的Effect对象,其使用的是FCUpdateQueue 是专门给函数组件提供呢更新队列,其继承updateQueue,多了lastEffect指针,指向Effect链(其本质也是环)。如下:

/** 函数组件专用的UpdateQueue增加了lastEffect 指向当前收集到的Effect */
export class FCUpdateQueue<State> extends UpdateQueue<State> {public lastEffect: Effect | null = null;
}

useEffect工作原理

每次更新的时候,mountEffect或者updateEffect都会检查当前的Effect是否被执行

  •  如果需要被执行,则将其放到Fiber对象的updateQueue中,并且将其tag设置为Passive|HookHasEffect
  • 如果不需要被执行,也需要将其推入updateQueue,只是其tag设置为Passive 代表有副作用,但是不执行

给当前的Fiber节点,设置PassiveEffect的flag

当commitMutation阶段时,会检查节点的Flag,如果包含passiveEffect,就会将当前节点的updateQueue推入root.pendingPassiveEffect数组中,你当前可以直接的将其理解为一个数组

在commitPassive阶段,会读取root.pendingPassiveEffect,依次执行FCUpdateQueue的内容

下面我们来看mount/updateEffect函数做了什么

mountEffect

挂载阶段,useEffect本质上执行的是mountEffect,其步骤如下:

1. 获取当前hook对象

2. 给当前的currentRenderingFiber.flag 设置PassiveEffect 表示当前Fiber对象包含Effect

3. 创建Effect对象,保存进creat函数和tag,并且将其分别保存到memorizedState和fiber.updateQueue

实现如下:

/** 挂载Effect */
function mountEffect(create: EffectCallback,deps: HookDeps
): EffectCallback | void {/** effect 在hook中的存储方式是:*  hook:*     memorizedState = Effect*     updateQueue = null*     next = nextHook*  fiber:*     updateQueue -> Effect1 -next-> Effect2 -...*/// 获取到hookconst hook = mountWorkInProgressHook();// 给fiber设置PassiveEffect 表示存在被动副作用(currentRenderingFiber as FiberNode).flags |= PassiveEffect;hook.memorizedState = pushEffect(// 初始化状态下,所有的useEffect都执行,所以这里flag设置为   Passive|HookHasEffectPassive | HookHasEffect,create,null,deps);
}

为什么要设置 fiber.flags?

因为effect真正执行的阶段在commit的passive阶段,而其收集阶段在commit的mutation阶段,所以render阶段时,需要在fiber上设置PassiveMask标记,表示当前fiber节点有副作用需要处理,commit阶段才会收集!

需要注意的是,mount阶段的tags为Passive | HookHasEffect 代表挂载阶段的所有Effect都会线执行一次!

其中,创建Effect和挂载到fiber.updateQueue的操作是pushEffect完成的!

pushEffect

pushEffect函数创建一个Effect对象,并且为其设置mask create destory deps依赖这些参数,可以将其看成是Effect对象的构造器。

同时,pushEffect还会将创建的Effect对象挂载到当前渲染Fiber的updateQueue上 并且返回这个Effect对象,实现如下:

/** 创建Effect对象,把effect加入到fiber.updateQueue 并且返回创建的Effect */
function pushEffect(tags: Flags,create: EffectCallback | null,destory: EffectCallback | null,deps: HookDeps
) {const effect: Effect = {tags,create,destory,deps: deps === undefined ? null : deps,next: null,};const updateQueue = currentRenderingFiber.updateQueue;if (!updateQueue || !(updateQueue instanceof FCUpdateQueue)) {// 创建一个FCUpdateQueueconst fcUpdateQueue = new FCUpdateQueue<Effect>();effect.next = effect; // 构建环fcUpdateQueue.lastEffect = effect;currentRenderingFiber.updateQueue = fcUpdateQueue;} else {// 已经存在 FCUpdateQueue 添加 后加环const fcUpdateQueue =currentRenderingFiber.updateQueue as FCUpdateQueue<Effect>;if (fcUpdateQueue.lastEffect) {effect.next = fcUpdateQueue.lastEffect.next;fcUpdateQueue.lastEffect.next = effect;fcUpdateQueue.lastEffect = effect;}}return effect;
}

pushEffect会检查,如果currentRenderingFiber.updateQueue为null (在renderWithHook时,会将updateQueue置为空以重置Effect) 则先创建一个FCUpdateQueue再加入,如果已经存在就直接加入。

updateEffect

更新阶段,由于fiber.updateQueue被清空,所以需要根据hook.memorizedState存储的Effect对象信息,重新判断哪些Effect需要重新执行。

判断是否要重新加入的依据就是deps,Effect存储了上一次的deps,updateEffect比较当前的deps和上一次prevDeps是否相等来判断是否需要重新执行。

判断方式为areInputDepsEqual, 前面有讲过。

  • 如果需要执行,则把Effect的tags改成 Passive | HookHasEffect 推入updateQueue
  • 如果不需要执行,则把Effect的tags改成Passive并且推入updateQueue

同时,在mount阶段,由于create函数还没有运行,所以无法获得destory函数,但是子啊update阶段,create函数已经在Passive Commit阶段运行过了,commit阶段会将create的返回值destory存入Effect对象中,直接获取即可,

也就是说,不论是mount还是update阶段,所有的Effect都会被挂载fiber.updateQueue上,其是否执行通过Effect.tag是否包含HookHasEffect这个HookEffectTag判断!

注意,更新阶段如果有需要执行的effect,也需要吧fiber.flags merge passiveEffect

实现如下:

/** 更新Effect */
function updateEffect(create: EffectCallback,deps: HookDeps
): EffectCallback | void {// 获取当前hookconst hook = updateWorkInProgressHook();const prevDeps = hook.memorizedState.deps;const destory = hook.memorizedState.destory;if (areHookInputsEqual(prevDeps, deps)) {// 相等 pushEffect 并且设置tag为Passive 被动副作用hook.memorizedState = pushEffect(Passive,create,// 前一个副作用hook的destorydestory,deps);} else {/** 不等 表示hook有Effect */hook.memorizedState = pushEffect(Passive | HookHasEffect, // 注意这里是 Passive 是Effect的tag 区分fiber的tag PassiveEffectcreate,// 前一个副作用hook的destorydestory,deps);}(currentRenderingFiber as FiberNode).flags |= PassiveEffect;
}

Effect收集阶段

effect的收集在commit阶段完成,我们上篇说了,如果某个Fiber节点存在需要执行的Effect,则其PassiveEffect的flag就会在completeWork阶段被冒泡到root节点。

commitRoot阶段,通过判断root节点是否有PassiveEffect的flags或subTreeFlags来判断是否开启Passive流程!

  /** 设置调度 执行passiveEffect */ty/** 真正执行会在commit之后 不影响渲染 *//** commit阶段会收集effect到root.pendingPassiveEffect */// 有删除 或者收集到Passive 都运行if ((finishedWork.flags & PassiveMask) !== NoFlags ||(finishedWork.subTreeFlags & PassiveMask) !== NoFlags) {// 调度副作用scheduler.scheduleCallback(PriorityLevel.NORMAL_PRIORITY,flushPassiveEffect.bind(null, root.pendingPassiveEffects));}

此时root.pendingPassiveEffect为空,需要在Mutation阶段收集

export const commitMutationEffects = commitEffect("mutation",MutationMask | PassiveMask,commitMutationEffectsOnFiber
);

可以看到commitMutationEffects中 mash包含了PassiveMask

pendingPassiveEffect的结构

root.pendingPassiveEffect的结构如下:

export interface PendingPassiveEffect {// 更新的effectupdate: Effect[];// 卸载的effectunmount: Effect[];
}

可以看到,其包含update和unmount两个数组,分别存储本次更新收集到的需要更新的effect和需要卸载的effect,在每次Passive Commit阶段接诉后,pendingPassEffect都会被置空! 

收集的几个地方

通过pendingPassEffect的结构就能看出,Effect的收集阶段分别发生在

1. 函数组件的卸载阶段

2. 函数组件的更新阶段

副作用的收集由commitPassiveEffect完成,其定义如下:

/** 收集被动副作用,这个函数可能会在*  1. commitMutationEffectsOnFiber调用*  2.  在delection时调用*/
function commitPassiveEffect(fiber: FiberNode,root: FiberRootNode,type: "update" | "unmount"
) {if (fiber.tag !== FunctionComponent) return;if (type === "update" && (fiber.flags & PassiveEffect) === NoFlags) return;const fcUpdateQueue = fiber.updateQueue as FCUpdateQueue<Effect>;if (fcUpdateQueue && fcUpdateQueue.lastEffect) {// 收集effectroot.pendingPassiveEffects[type].push(fcUpdateQueue.lastEffect);}
}

可以看到,其逻辑就是把当前存在PassiveEffect的Fiber节点的FCUpdateQueue.lastEffect 存入对应的update unmount数组,对于卸载阶段,是允许其flag不存在PassiveEffect的,因为卸载函数组件要destory其所有的effect

在commitMutationEfffectOnFiber 中,调用次函数,用来收集update阶段的effect

  if ((flags & PassiveEffect) !== NoFlags) {// 存在被动副作用commitPassiveEffect(finishedWork, root, "update");}

在commitDeletion阶段中,调用次函数用来收集unmount阶段的effect

    if (childToDelete.tag === FunctionComponent) {/** 函数组件的情况下,需要收集Effect */commitPassiveEffect(childToDelete, root, "unmount");}

在完成了Mutation Commit阶段之后,root.pendingPassiveEffect 就分别收集到了更新和卸载阶段的FCUpdateQueue

Effect的执行

effect的执行发生在Passive Commit阶段,我们前面说了,在Mutation阶段开始之前,会把一个flushPassiveEffect函数交给scheduler调度,以便在Mutation之后异步执行,其实现如下

function flushPassiveEffect(pendingPassiveEffect: PendingPassiveEffect) {// 处理卸载 把所有的Passive flag的effect都执行destorpendingPassiveEffect.unmount.forEach((unmountEffect) => {commitHookEffectListUnmount(Passive, unmountEffect);});pendingPassiveEffect.unmount = [];// 处理update 的destory flag为Passive|HookHasEffectpendingPassiveEffect.update.forEach((updateEffect) => {commitHookEffectListDestory(Passive | HookHasEffect, updateEffect);});// 处理update的create flag为Passive| HookHasEffectpendingPassiveEffect.update.forEach((updateEffect) => {commitHookEffectListCreate(Passive | HookHasEffect, updateEffect);});pendingPassiveEffect.update = [];
}

其实本质就是按顺序处理pendingPasiveEffect

首先处理卸载的effect -> 再处理更新的effect的destory -> 再处理更新effect的create

最后清空pendingPassiveEffect 以方便下次收集

我们首先看更新阶段处理create 其定义在commitHookEffectListCreate

function commitHookEffectList(flags: HookEffectTag,lastEffect: Effect | null,callback: (effect: Effect) => void
) {let currentEffect = lastEffect.next;do {if ((flags & currentEffect.tags) === flags) {// flag必须完全相等 执行callbackcallback(currentEffect);}currentEffect = currentEffect.next;} while (currentEffect !== lastEffect.next);
}/** 执行创建的effect */
export function commitHookEffectListCreate(flags: HookEffectTag,lastEffect: Effect | null
) {commitHookEffectList(flags, lastEffect, (effect) => {const create = effect.create;if (typeof create === "function") {// 设置destoryeffect.destory = create() as EffectCallback;}});
}

可以看到,其本质就是遍历pendingPassiveEffect.update,找到哪些包含HookHasEffect的Effect对象,执行其create函数,并且把返回值作为destory存入effect对象。

commitHookEffectListDestory,同样是遍历,找到包含HookHasEffect tag的Effect对象,执行其destory函数 如下:

/** 执行destory的effect */
export function commitHookEffectListDestory(flags: HookEffectTag,lastEffect: Effect | null
) {commitHookEffectList(flags, lastEffect, (effect) => {const destory = effect.destory;if (typeof destory === "function") {destory();}});
}

 最后在看卸载的情况,即commitHookEffectListUnmount 把unmount中所有的effect都执行一遍,并且去除其tag上的HookHasEffect标记(如果有的话)

/** 执行卸载的effect */
export function commitHookEffectListUnmount(flags: HookEffectTag,lastEffect: Effect | null
) {commitHookEffectList(flags, lastEffect, (effect) => {const destory = effect.destory;if (typeof destory === "function") {destory();}effect.tags &= ~HookHasEffect;});
}

这样就完成了一次更新的Effect的运行,最后清空pendingPassiveEffect即可

所以,Effect的执行一定是从下到上的,因为commit Mutation是在归的阶段执行,收集的Effect自然也是从下到上的!

 

 

 

 

 

 

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

相关文章:

  • app要有网站做基础知识微信营销和微博营销的本质区别
  • wordpress如何写网站外贸建站与推广如何做
  • 美德的网站建设企业营销推广策划
  • 二级建造师怎么查询注册信息sem优化师是什么意思
  • 做的网站老被攻击优秀网站设计案例
  • 微信公众号线上商城怎么做网站文章优化技巧
  • 北京市网站维护公司天津seo优化排名
  • ipad网站开发百度引擎搜索入口
  • dw做网站后台产品怎样推广有效
  • 北京太阳宫网站建设成都新一轮疫情
  • ps免费素材网站有哪些今日军事新闻头条打仗
  • 做外贸的 需要什么样的网站周口网站制作
  • 行政机关单位网站建设规定sem竞价托管
  • 微信引流推广平台seo排名关键词
  • 怎样注册平台网站武汉seo优化公司
  • 做服饰的有哪些网站岳阳网站设计
  • 专业网站制作公司热狗seo优化外包
  • cpa自己做网站seo搜索引擎优化薪资水平
  • 网站选项卡代码百度软件安装
  • 深圳做网站推广公司中国十大seo公司
  • 网站建设具体实施方案电商运营工作内容
  • 怎样利用网站做推广的方法电子商务网站建设
  • 微信网站建设方案怎么免费制作网站
  • 常州酒店网站建设免费建网站哪家好
  • 郑州飞旗科技有限公司 www.71884.cn 郑州网站建设广告联盟自动挂机赚钱
  • 做宣传网站的公司重庆seo优化推广
  • 论坛的网站开发项目做网站价格
  • java有没有做项目的网站凡科建站代理登录
  • 做网站论坛电话销售外呼系统软件
  • 做网站用的什么编程语言网站查询平台官网