zoukankan      html  css  js  c++  java
  • 【react hooks】--初渲染和更新阶段

    hook组件初渲染

    hooks组件在初次渲染时,

    1. 解析组件类型,判断是Function还是class类型,然后执行对应类型的处理方法
    2. 判断到当前是Function类型组件后,首先在当前组件,也就是fiberNode上进行hook的创建和挂载,将所有的hook api都挂载到全局变量dispatcher
    3. 顺序执行当前组件,每遇到一个hook api都通过next将它连接到当前fiberNodehook链表上

    hooks api 挂载

    在初始渲染时,currentDispatcher为空,就会先将所有hook api挂载到当前fiberNodedispatcher上。也就是将HooksDispatcherOnMountInDEV赋值给dispatcher

      {
        //  首次执行currentDispatcher = null,所以进入else分支;在更新阶段会进入if分支
        if (currentDispatcher !== null) {
          currentDispatcher = HooksDispatcherOnUpdateInDEV;
        } else {
          currentDispatcher = HooksDispatcherOnMountInDEV;
        }
      }
    

    而在HooksDispatcherOnMountInDEV中,包含各种原生hook,内部调用mountXXX方法。

    HooksDispatcherOnMountInDEV = {
        useCallback: function (callback, deps) {
          return mountCallback(callback, deps);
        },
        useEffect: function (create, deps) {
          return mountEffect(create, deps);
        },
        useMemo: function (create, deps) {
            return mountMemo(create, deps);
        },
        useState: function (initialState) {
            return mountState(initialState);
        }
      }
    

    useState -- mountState

    1. 首先创建一个hook节点,其中的memoizedState是最终返回的初始值;queue是更新队列,当我们多次更新某一状态时需要通过queue队列存取和遍历;next用来连接下一个hook;
    2. 将当前的hook连接到当前的fiberNodehook链表上
    3. 绑定状态更新方法dispatchAction,并返回[state, dispatchAction]
    function mountState(initailState){
        var hook = {
            moemoizedState: null,
            queue: {
                pending: null,
                dispatch: null,
            },
            next: null
        }
        ...
        var dispatch = queue.dispatch = dispatchAction.bind(null, currentFiber,queue);
        return [hook.memoizedState,dispatch];
    }
    

    useEffect -- mountEffectImpl

    1. mountEffect中,首先将当前的hook挂载到当前fiberNodehook链表上

    2. 由于useEffect是异步执行的,会产生专属于useEffecthook。此时会将产生的useEffect产生的hook存入componentUpdateQueue更新队列中。在某一次页面渲染结束后会去遍历这个更新队列,执行传入的effect hook

    function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
        // 创建并获取当前hook节点信息
        var hook = mountWorkInProgressHook();
    
        hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
      }
    
    function mountWorkInProgressHook() {
        // 将当前hook连接到我们的hook链表中
        var hook = {
          memoizedState: null,
          queue: null,
          next: null
        };
    
        if (workInProgressHook === null) {
          currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
        } else {
          workInProgressHook = workInProgressHook.next = hook;
        }
    
        return workInProgressHook;
      }
    
    function pushEffect(tag, create, destroy, deps) {
        var effect = {
          tag: tag,  // 更新标识
          create: create, // 传入的回调,也就是我们开发时的第一个参数
          destroy: destroy, // return 的函数,组件销毁时执行的函数
          deps: deps, // 依赖项数组
          next: null
        };
        var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
        
        // 这里做的就是把每个useEffect hook单独以链式结构存到了componentUpdateQueue这个全局变量中
        if (componentUpdateQueue === null) {
          componentUpdateQueue = createFunctionComponentUpdateQueue();
          componentUpdateQueue.lastEffect = effect.next = effect;
        } else {
          var lastEffect = componentUpdateQueue.lastEffect;
    
          var firstEffect = lastEffect.next;
          lastEffect.next = effect;
          effect.next = firstEffect;
          componentUpdateQueue.lastEffect = effect;
        }
        
        return effect;
      }
    

    初始化渲染至此结束,此时fiberNodehook链式结构是

    currentFiber: {
    	...
    	memoizedState: {
    		memoizedState: xxx,
    		...
    		next: {
    			memoizedState: xxx,
    			...
    			next: {
    				memoizedState: xxx,
    				...
    				next:hook4
    			}
    		}
    	}
    }
    

    hook组件更新阶段

    在组件更新时,于初始化类似,将更新的对应hook进行挂载,根据链表依次执行hook。在此时,需要执行状态更新和useEffect更新,最后更新完成。

    useState

    在初始化阶段有提到,mountState阶段会绑定dispatchAction并作为参数返回,其实也就是使用useState时返回的setXXX

    而在dispatchAction中实际上,做了两件事

    1. 创建update节点,并连接到useStatequeue后面,这样每次调用dispatchAction都会在后面连接一个update节点,从而生成一个更新队列。
    2. 然后根据更新任务的优先级排列任务,最后遍历整个fiber树执行更新操作。

    执行setXXX后,发生了什么?

    在初始化阶段讲到的,根据是否存在dispatcher,赋值。

      {
        //  更新阶段dispatcher已存在,执行else部分
        if (currentDispatcher !== null) {
          currentDispatcher = HooksDispatcherOnUpdateInDEV;
        } else {
          currentDispatcher = HooksDispatcherOnMountInDEV;
        }
      }
    

    而在HooksDispatcherOnMountInDEV中各种hooks对应的是update方法,useState对应的是updateState方法。

    function updateState(initialState){    return updateReducer(basicStateReducer);}
    

    可以看到,updateState实际上是返回了一个reducer

    1. updateReducer中,获取到hook的更新队列,比如执行了三次setCount,则队列中就会存在三项。

    2. 拿到更新队列后,会对其进行循环遍历,计算赋值,最终会将最新的state值复制到hook的memoizedState上并返回。

    function updateReducer(reducer, initialArg, init) {  // 获取到当前hook,其实也就是直接.next就可以  var hook = updateWorkInProgressHook();  var queue = hook.queue;  // 取到待更新的队列  var pendingQueue = queue.pending;    // 如果待更新队列不为空,那么遍历处理  if (pendingQueue !== null) {    var first = pendingQueue.next;    var newState = null;    var update = first;    queue.pending = null;        // 循环遍历,是更新阶段的核心和关键,    do {      var action = update.action;      newState = reducer(newState, action);      update = update.next;    } while (update !== null && update !== first);    // 最新的状态值赋值给memoizedState    hook.memoizedState = newState;  }  // 将状态值和更新方法返回,就和初次渲染一样的流程  var dispatch = queue.dispatch;  return [hook.memoizedState, dispatch];}
    

    如何形成更新队列?

    而在updateReducer之前,会执行dispatchAction,将每个update加入到更新队列中。

    // dispatchAction核心代码var pending = queue.pending;// 这里是链表创建和连接的核心if (pending === null) {  update.next = update;} else {  update.next = pending.next;  pending.next = update;}queue.pending = update;
    

    每次dispatch时,当目前更新队列为空时,将当前update加入,next指向自己

    当不为空时,将当前update放到更新队列最后一项的next上。

    需要注意的是,更新队列中的first指向第一个执行的updatequeue.pending指向最后一个。

    所以在更新队列循环执行时,结束循环的条件为while (update !== null && update !== first);也就是当前update为更新队列中的第一个也就是唯一一个update

    useEffect

    useState相似,在更新过程中会执行updateEffect,在其中执行updateEffectImpl

    updateEffectImpl中,无论依赖项是否更改,都会调用pushEffect方法。

    function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {  // 获取到当前hook  var hook = updateWorkInProgressHook();  // 比较依赖项是否发生了变化  if (areHookInputsEqual(nextDeps, prevDeps)) {    // 如果相同则不对当前hook的属性进行更新    pushEffect(hookEffectTag, create, destroy, nextDeps);    return;  }  // 如果依赖项发生了变化,更新当前hook的memoizedState,这里的赋值只是做一个记录,并没有实际意义  currentlyRenderingFiber$1.effectTag |= fiberEffectTag;  hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, destroy, nextDeps);}
    

    而在pushEffect方法中,

    1. 创建一个effect对象,并返回
    2. 创建/更新componentUpdateQueue队列,其中存储了useEffect产生的回调,componentUpdateQueue队列不存在的话会进行创建,如果存在,会和mountState阶段一样创建一个effect的循环链表。effect对象中的tag就是用来判断这个useEffect回调是否需要被执行。

    一些可以被解释了的问题

    hook必须按照固定顺序调用,不能在条件判断中调用

    由于每一个hook都是通过next指针在链表中按顺序连接的,如果在某个条件判断的情况下,某个hook不存在,就会导致整个hook链表中断,没法继续正常遍历hook链表,产生问题。

  • 相关阅读:
    怎样解决:未找到路径“……”的控制器或该控制器未实现 IController?
    错误:org.springframework.jdbc.support.SQLErrorCodesFactory
    springbean的生命周期
    注解到处excel
    nio读取文件,输出文件
    AtomicReference
    唯一id
    hashmap1.7的死锁模拟
    数组模拟stack
    环形队列
  • 原文地址:https://www.cnblogs.com/ashen1999/p/14894738.html
Copyright © 2011-2022 走看看