IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> react源码debugger-各个hooks的逻辑实现(useState和useEffect) -> 正文阅读

[JavaScript知识库]react源码debugger-各个hooks的逻辑实现(useState和useEffect)

了解react的整体流程,会有助于理解本文。

hooks是什么?

要了解hooks是什么,我们得先想知道react怎么执行函数组件。

先看看函数组件的fiber是什么?

const fiber = {
    type: f App(){}, //函数本身,
    memoziedState: {}, //hooks链表
	updateQueue: {}, //effects链表
    ....
}

对于函数组件,我们现在只需要关注他这几个属性就行了

首先看到renderWithHooks函数,他是执行函数组件的方法。

function renderWithHooks(current, workInProgress, Component, props,secondArg, nextRenderLanes){
    	
    
  // 将当前fiber赋值给currentlyRenderingFiber,hooks执行的时候通过这个获取当前的fiber对象
  currentlyRenderingFiber = workInProgress;
    
  // 清除fiber上面的memoizedState和updateQueue,为啥呢?因为即将执行函数组件,
  // 函数组件的hooks对象以链表形式存放在fiber.memoizedState
  // 函数组件的useLayoutEffect和useEffect以effects链表的形式存放在fiber.updateQueue上
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
    
 // 赋值hooks对象
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
    
  // 真正执行函数
  let children = Component(props, secondArg);

  // 重新赋值hooks对象。此时不能再调用hooks,会报错
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
    
  // 重新置空全局变量
  currentlyRenderingFiber = (null: any); //指向当前的函数fiber
  currentHook = null; //current树上的指向的当前调度的 hooks节点。
  workInProgressHook = null; //workInProgress树上指向的当前调度的 hooks节点。
    
    
  return children
}

如上,可以看出,renderWithHooks主要做了:

  • 将当前调度的函数fiber赋值currentlyRenderingFiber,从名字可以看出,currentlyRenderingFiber就是当前调度的fiber,hooks执行的时候,通过全局变量currentlyRenderingFiber获取到当前的fiber。
  • 清除fiber上面一些属性,如fiber.memoizedState和fiber.updateQueue。fiber.memoizedState存放着hooks链表,指向第一个hooks。而updateQueue存放着useEffect/useLayoutEffect产生的effects。为什么这里要清除呢?因为函数组件即将执行,每一次重新执行函数组件,都会重新生成effects链表和hooks链表。
  • 赋值ReactCurrentDispatcher.current属性,我们调用的hooks实际上就是通过ReactCurrentDispatcher.current获取到的,在一开始是null。所以在函数组件外部执行的时候会报null上没有useState等的错误。可以看到执行函数组件之后,又赋值了ContextOnlyDispatcher,这个其实就是用来抛出错误的,因为函数组件已经执行完毕了,不能再执行hooks了。
  • 执行函数组件,可能会执行各种hooks。
  • 执行完函数组件之后,需要重新赋值全局变量,比如currentlyRenderingFiber置为null。因为要轮到下一个函数fiber调度了。curretnHook置为null。这个全局变量其实就是用来指向current fiber上的hooks节点,通过currentHook,复制出一个新的workInprogress hook。workInporgressHook跟currentHook一样,不过它指向的是wokrInporgress hook。
  • 返回children

现在来看这个demo。

const App2 = () => {
  const [state, setState] = useState(1);
  const stateMemo = useMemo(() => {
    state + 1;
  }, [state]);
  const stateUseCallback = useCallback(() => {
    return state + 1;
  }, [state]);
  const stateRef = useRef(state);
  useEffect(
    function useEffect() {
      console.log("useEffect=====create");
      return () => {
        console.log("effect====destory");
      };
    },
    [state]
  );
  useLayoutEffect(
    function useLayoutEffect() {
      console.log("useLayoutEffect=====create");
      return () => {
        console.log("useLayoutEffect====destory");
      };
    },
    [state]
  );

  return (
    <div onClick={() => setState(3)}>
      <div>state: {state}</div>
      <div><>memo: {stateMemo}</></div>
      <div>callback: {stateUseCallback()}</div>
    </div>
  );
};

预知识:

每一个hooks都会创建一个hooks对象。结构基本一样

const Hook = {
    queue: {}, //挂载着更新的操作,类似于类fiber.updateQueue
	memoziedState: {}, //保存hook的状态,不同的hook保存的数据不同
    baseState: {}, // 如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
    baseQuuee: {}, //存放着因为优先级较低而跳过的hook
    next: {} ,//指针,指向下一个hook
}

上面我们说过,fiber.memoizedState存放着hooks链表。

从demo debugger上,发现函数fiber是这样的

{
    memoziedState: {
        baseQueue: null
        baseState: 1
        memoizedState: 1,
        queue: {},
        next: {
            baseQueue: null
            baseState: null
            memoizedState: (2) [2, Array(1)],
            queue: null,
                next: {
                    baseQueue: null
                    baseState: null
                    memoizedState: {current: 1}
                    next: {memoizedState: {…}, baseState: null, baseQueue: null, queue: null, next: {…}}
                    queue: null
                }
        }
    }
}

可以看到fiber.memozedState就存放着hooks链表,每一个hook通过next指针相连。

useState

useState的第一次执行的函数就是mountState

function mountState(initialState){
    // 创建hooks对象
    const hook = mountWorkInProgressHook();
    
    //初始值是函数就执行
     if (typeof initialState === 'function') {
    initialState = initialState();
  }
         
   // initState存放到hook对象上的memoizedState和baseState
  hook.memoizedState = hook.baseState = initialState;
         
  // 创建queue对象,每个hooks都有一个queue对象,用来存放当前hooks产生的update等信息。
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null, //存放update链表
    interleaved: null, 
    lanes: NoLanes, //优先级
    dispatch: null, 
    lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说				useState是特殊的useReducer
    // 上一次render时候的update
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
    
    // 派发action的函数,通过bind传入了当前的fiber,和Queue对象
  const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber, //函数的fiber
    queue,
  );
    
  // 返回初始化状态和dispatch
  return [hook.memoizedState, dispatch];
}

可以看到mountState做了

  • 调用mountWorkInProgressHook创建hooks对象。
  • 如果初始值是函数,就执行。
  • 更新hooks属性
  • 创建queue对象。
  • 创建dispatch函数,就是他来派发action。
先看看mountWorkInProgressHook
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null, // useState中 保存 state 信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和 deps | useRef 中保存的是 ref 对象。
    baseState: null,  //usestate和useReducer中,一次更新中 ,产生的最新state值。
    baseQueue: null, //usestate和useReducer中 保存最新的更新队列,存放着因为优先级比较低跳过的update链表
    queue: null,  //存放着本次更新相关的信息。
    next: null, //指针
  };

  // 函数组件第一个hooks的时候,为空,通过workInProgressHook指针连接起整个函数组件的hooks链表。
  if (workInProgressHook === null) {
    // hooks链表第一个,挂载在fiber.memoizedState
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 插在上一个hook对象后面,形成链表
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

可以看到,它创建了hooks,并且将hooks挂载到了fiber.memoizedState上,以链表的形式。workInProgressHook就是用来执行当前的hook。

queue对象

这个对象存放着更新的一系列值。

const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null, //存放update链表
    lanes: NoLanes, //优先级
    dispatch: null,  //存放着setState
    lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说useState是特殊的useReducer
    // 上一次render时候的update
    lastRenderedState: initialState, //用来给setState第一次调用的时候做优化。
  };

我们现在知道useState初始化会创建hooks,初始化hook.queue,挂载到fiber.memoizedState上,然后返回hook.memoizedState和dispatch。

更新的时候

更新调用的是dispatch。从mountState可以看到。

const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber, //函数的fiber
    queue,
  );

调用dispatchSetState,并且传入了fiber和queue。

function dispatchSetState( fiber, queue, action
 const lane = requestUpdateLane(fiber);

  // 创建update对象
  const update: Update<S, A> = {
    lane, //优先级
    action, //值,对于useReducer来说是一个action
    hasEagerState: false, 
    eagerState: null,
    next: (null: any), //指针
  };

 // 插入当前update
 enqueueUpdate(fiber, queue, update, lane);


  // 获取workInprogress fiber
  const alternate = fiber.alternate;
  
if( fiber.lanes === NoLanes  &&
      (alternate === null || alternate.lanes === NoLanes)){
   // 让如果当前的fiber没有处于调度状态,那么第一次调用setState就会走优化逻辑。
    
    const lastRenderedReducer = queue.lastRenderedReducer; //获取reducer
    
     const currentState: S = (queue.lastRenderedState: any); // 当前的state
     const eagerState = lastRenderedReducer(currentState, action); //获取最新的值
    
     update.hasEagerState = true; //打标机,表示这个update已经计算过值了。
     update.eagerState = eagerState;
    
    if (is(eagerState, currentState)) {
           // 如果state没变,不调度
            return;
          }
}

   const eventTime = requestEventTime(); //获取当前事件的执行时间
    // 开启新的一轮调度。
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}

可以看到dispatchSetState主要做了

  • 创建update,每一个更新都会创建对应的update对象。
  • 调用enqueueUpdate将update插入hook.queue.pending上,以链表的形式。
  • 判断是否处于调度状态,如果不是,当前的setState就可以直接计算值了。然后判断计算出来的值跟之前的state是否相等,相等则不调度,这也是函数的setState跟来组件的this.setState的区别。这里也不用担心说,万一计算出的值变了,那不还得继续调度,在这里如果计算值之后,会将update.hasEagerState置为true,表示已经计算过了,那么等调度完处理update的时候,就不会重新计算,而且直接取update.eagerState最后更新后的值。
  • 调用scheduleUpdateOnFiber开启新的一轮调度。

总结:现在知道了setState会创建update,插入到hook.queue.pending上。然后调用scheduleUpdateOnFiber开启新的一轮调度。

接着我们看update阶段执行的useState,执行的是updateState函数。

// useState的update函数
function updateState<S>(
  initialState: (() => S) | S,
): {
  // 跟useReducer调用同样的函数,不过第一个
  return updateReducer(basicStateReducer, (initialState: any));
}

这个updateState,其实也是调用updateReducer,从名字上可以看到,就是useReducer的更新函数,但是它默认传了basicStateReducer

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  // $FlowFixMe: Flow doesn't like mixed types
  return typeof action === 'function' ? action(state) : action;
}

这个就是一个处理,如果setState传入的是普通值,那么reducer就直接返回,如果传入的是函数,那么就将当前的state传入,将返回值作为返回。这里也解释了为什么setState可以获取最新的State。我们来看updateReducer怎么计算新的state的。

function(reducer, initialArg){
  // 通过current fiber的Hooks对象,一个一个复制对应的hooks返回
  const hook = updateWorkInProgressHook();
   // 获取更新队列
  const queue = hook.queue;
  queue.lastRenderedReducer = reducer; //赋值redcuer,每一次useReducer执行,都会重新赋值reducer
    
    // ----------- 拼接跳过的update-----------
    const current: Hook = (currentHook: any); //获取当前hooks的对应在current fiber上的hook
    let baseQueue = current.baseQueue; //获取因为优先级较低而跳过的update链表
    const pendingQueue = queue.pending; //获取当前的update任务链表
    if (pendingQueue !== null) {
          if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
    
   
    // -----------处理update链表--------------
    // 如果有跳过的Update,他们存放在了hooks.baseQueue上面,将他们取下来,然后插入到当前的update链表之前
   //存在更新的链表。
  if (baseQueue !== null) {
     // ----- 定义一些变量存储值---------
    // We have a queue to process.
    const first = baseQueue.next; //获取第一额update
    let newState = current.baseState; // 保存每一个update处理后得到的最新的state
 
   let newBaseState = null;   
   //保存着这次调度之后,fiber的最新state,跟newState不一样的是,有些update因为优先级较低被跳过,所以newBaseState的值是停留在被跳过的update的state,为的就是保证状态不丢失

    let newBaseQueueFirst = null; //新的需要跳过的update链表的第一个指针
    let newBaseQueueLast = null; //新的需要跳过的update链表的最后一个指针

    let update = first;
    
    // do-while循环处理update
    do {
      
        // -----------优先级判断-----------------
      const updateLane = update.lane; //优先级
        if(!isSubsetOfLanes(renderLanes, updateLane)){
            //如果当前的update优先级不够
            
         //clone一个Update
         const clone: Update<S, A> = { 
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
            
          // --- 将跳过的update以链表的形式放到newBaseQueueFirst------
         if (newBaseQueueLast === null) { //这是第一个跳过的update
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState; 
        } else {
          // 不是第一个跳过的,直接插在链表后面
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
            
          // 更新队列中的剩余优先级。
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
     }else {
         //如果这个update优先级够了
         
       //-----为了保持状态的连续性,若update之前有别的update被跳过了,那么当前的update也得clone一份存入跳过的链表
          if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
         
          // 开始处理这个update了,这里对照着上面setState做的优化,如果值已经计算了,直接取update.eagerState,
        if (update.hasEagerState) {
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          // 通过reducer计算最新的state,赋值给newState
          // 这里每一次调用reducer,都会将最新计算得到的NewState传入进去,所以这也是为什么,setState就可以获取到最新的state的原因了。因为上一个update处理过的state,已经赋值给了newState了。
          newState = reducer(newState, action);
        }

     }
        
      // 处理下一个update
      update = update.next;
      } while (update !== null && update !== first); //直到所有的Update处理完毕
        
        
        
      //----- 处理新的state了,如果有跳过的update,那么hook.memozedState就不能是最新的state。而是跳过的第一个update的时候的state
    if (newBaseQueueLast === null) {
      //如果没有跳过的update,当前hooks.baseState才是最新的state
      newBaseState = newState;
    } else {
      // 跳过的update链表首尾相连
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }  
      
      
    
      
    // 将状态更新到hook对象上
    hook.memoizedState = newState; //存放最新的state
    hook.baseState = newBaseState;  //如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
    hook.baseQueue = newBaseQueueLast; //baseQueue存在跳过的update链表

    queue.lastRenderedState = newState;
  }
    
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch]; //这里返回的状态并不是newState,而是newBaseState
}

如上,可以看到useState的update执行的函数做的事情还是很多的。

  • 调用updateWorkInProgressHook获取到hooks对象

  • 判断是否有跳过的update链表,有的话就跟现在的update链表拼接到一起处理。

  • 递归处理update。如果优先级不够的,就先放着,优先级够的,还得判断他前面有没有跳过的update,有的话那么当前的update也得clone一份放入跳过的链表之中。然后处理update

  • 处理的时候会判断当前update是否已经计算过了,不是的话,就会调用reducer将hook.baseState传入。注意,这里处理update链表的基础state是hook.baseState。而不是hook.memoizedState,这是因为,为了状态的连续性,不止优先级较低的update,后面的update都得存起来,而且计算update的state必须留在跳过的update的时候的hook.state才行。这里通过reducer函数处理action,也可以知道为什么setState传入函数就可以获取最新的state,因为上一个Update已经计算得到新的值,然后才传入reducer的。

  • 接着更新hook,可以看到,如果有跳过的update,那么hook.baseState赋值的是newBaseState。而且只当有没调过的update的时候,才会执行newBaseState=newState

  • 最后返回最新的到的hook.memoizedState。

这里我们需要注意的有两点,一个是updateWorkInProgressHook如何获取hooks。一个是优先级跳过update,react为了避免状态丢失做的处理。

updateWorkInProgressHook
function updateWorkInProgressHook(){
     let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    // 函数组件第一个hooks,为null。
    // 获取current fiber
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      // 从curentFiber上面获取mount时候创建的hook对象,现在nextCurrentHooks已经有一条完整的当前current fiber的hooks链表了
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    // 第二次,直接从current fiber上往下取第二个hooks
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook; //获取workInprogress fiber下一个hook
  if (workInProgressHook === null) {
    // 函数组件第一个hooks执行的时候,为null
    
    //update时候, 获取workInprogress fiber上面第一个hooks对象,应该为null
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    // 直接往workInprogress fiber的next下获取
    nextWorkInProgressHook = workInProgressHook.next; //null
  }

  if (nextWorkInProgressHook !== null) {
    // 已经有值了,将nextWorkInProgressHook赋值给workInProgressHook去返回,
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.
    // update 第一次进来应该为null
    // 第一次进来nextCurrentHooks不应该为null
    if (nextCurrentHook === null) {
      throw new Error('Rendered more hooks than during the previous render.');
    }

    // 指向current fiber对应的hooks对象
    currentHook = nextCurrentHook;

    // 从Curent fiber上面复制一个hooks给workInprogress fiber
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    // update第一次为null 
    if (workInProgressHook === null) {
      // This is the first hook in the list.
      // 将新创建的Hooks复制给wokrInprogress fiber的memoizedState,
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  // 返回新创建的hooks
  return workInProgressHook;
}

这里的逻辑看似复杂,其实就做了一件事情。就是沿着current fiber.memoizedState开始,clone hooks对象,新创建一个newHook,赋值给workInProgressHook,并且以链表的形式存放在workInporgress fiber.memoizedState上面。

 const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

总结:就是从当前的current fiber上面的hook将属性复制过来,避免状态的丢失,因为baseQueue存放着的是上一次更新的链表。而queue存放着的就是此次更新存放的对应信息。

接着是第二个点,因为update优先级的概念,为了避免丢失,主要做了什么?

    // do-while循环处理update
    do {
      
        // -----------优先级判断-----------------
      const updateLane = update.lane; //优先级
        if(!isSubsetOfLanes(renderLanes, updateLane)){
            //如果当前的update优先级不够
            
         //clone一个Update
         const clone: Update<S, A> = { 
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
            
          // --- 将跳过的update以链表的形式放到newBaseQueueFirst------
         if (newBaseQueueLast === null) { //这是第一个跳过的update
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState; 
        } else {
          // 不是第一个跳过的,直接插在链表后面
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
            
          // 更新队列中的剩余优先级。
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
     }else {
         //如果这个update优先级够了
         
       //-----为了保持状态的连续性,若update之前有别的update被跳过了,那么当前的update也得clone一份存入跳过的链表
          if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
         
          // 开始处理这个update了,这里对照着上面setState做的优化,如果值已经计算了,直接取update.eagerState,
        if (update.hasEagerState) {
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          // 通过reducer计算最新的state,赋值给newState
          // 这里每一次调用reducer,都会将最新计算得到的NewState传入进去,所以这也是为什么,setState就可以获取到最新的state的原因了。因为上一个update处理过的state,已经赋值给了newState了。
          newState = reducer(newState, action);
        }

     }
        
      // 处理下一个update
      update = update.next;
      } while (update !== null && update !== first); //直到所有的Update处理完毕

主要做了两步操作,

  • 第一步:如果是跳过的update,就要将其保存起来,并且他之后所有的update(即使是优先级高的)也必须clone一个保存起来,防止update之间有依赖性。
  • 第二步:如果有跳过的update,那么下次计算得state,不能是最新的state,必须是在哪里跳过,那会的state是如何得,下次就要用那会得state

这就是useState得一个整体流程了。

useEffect & useLayoutEffect

这两个hooks其实差不多,就是tag标识不同,执行时机不同而已。

直接看useEffect

function mountEffect(create, deps){
     return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect, 
      HookPassive,// useEffect得标识
      create,
      deps,
    );
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 创建Hooks
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags; //更新flags标记,commit阶段有用,表示有useEffect得副作用

  // useEffect/useLayoutEffect的hook.memoizedState = effect。并且effect通过链表的形式挂载到了fiber.updateQueue.lastEffect上面
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}

可以看到useEffect得mount做了:

  • 创建hooks
  • 将fiber.flags打上标记。
  • 调用pushEffect,他会返回effects对象,赋值给fiber.memoizedState

前置知识

useEffect得hooks对象:

{
    memoizedState: {effects},
    queue:null,
    next: {}
}

看看pushEffect

function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag, //layoutEffect和useEffect的标识
    create, //创建函数
    destroy, // 销毁函数
    deps, //依赖项
    next: (null: any), //effects会连接成为一个链表。
  };

  // 获取函数组件fiber.updateQueue
  let componentUpdateQueue = currentlyRenderingFiber.updateQueue //获取fiber.updateQueue

  if (componentUpdateQueue === null) {
    // 如果是第一个effects
    //创建一个有LastEffect的对象
    componentUpdateQueue = createFunctionComponentUpdateQueue(); //其实就是 { lastEffect: null,  stores: null,};
    // 将这个对象赋值到fiber.updateQueue上面
    currentlyRenderingFiber.updateQueue = componentUpdateQueue;
    // 接着将当前effect挂载到updateQueue.lastEffect上,以环状链表的形式
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
     // 第二个effects之后

     // 获取之前创建的updateQueue.lastEffect,他永远指向最后一个effects
    const lastEffect = componentUpdateQueue.lastEffect;

    if (lastEffect === null) {
      //如果是空,自己形成一个环状链表
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      //否则,插入到上一个effects后面
      const firstEffect = lastEffect.next; // lasEffect指向最后一个,那么.next就是第一个
      lastEffect.next = effect; //插到最后一个后面
      effect.next = firstEffect; //下一个指向第一个effects,形成环状
      componentUpdateQueue.lastEffect = effect; //updateQueue.lastEffect永远指向最后一个effect
    }
  }
  // 返回本次创建的effects
  return effect;
}

其实做的事也简单:

  • 创建effects对象。
  • 将effects对象插入到fiber.updateQueue.lastEffect之上,以环状链表的形式。

effects对象

 const effect: Effect = {
    tag, //layoutEffect和useEffect的标识
    create, //创建函数
    destroy, // 销毁函数
    deps, //依赖项
    next: (null: any), //effects会连接成为一个链表。
  };

effects对象,tag就是用来标识得,比如useEffect是9,而useLayoutEffect是5,其次就是保存了创建函数,依赖项等。next指针指向下一个effects。多个useEffect创建得effects也会以环状链表的形式,插入到fiber.updateQueue.lastEffect

这就是useEffect更新的时候做的事情。而useLayoutEffect是一样的操作,只不过标识换成了useLayoutEffect得标识。

function mountLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  let fiberFlags: Flags = UpdateEffect;
  return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}

fiber.flags是updateEffect,标识是HookLayout。

接着看啥时候执行呢?第一次useLayoutEffect和第一次useEffect执行。

useEffect执行

在before-mutation阶段之前,通过调度ScheduleCallback,以普通优先级,调度了useEffect执行函数。

function commitRootImp(){
    
   .....
     if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags || 
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
// 如果有useEffect得影响
    if (!rootDoesHavePassiveEffects) {
      // 赋值全局变量,表示有useEffect的副作用
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      scheduleCallback(NormalSchedulerPriority, () => { // 以普通优先级调度useEffect
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      });
    }
  }
}

最终执行的就是flushPassiveEffects函数。他是以普通优先级异步调度得。

export function flushPassiveEffects(): boolean {
  if (rootWithPendingPassiveEffects !== null) {
    try {
      setCurrentUpdatePriority(priority);
      return flushPassiveEffectsImpl();
    } finally {
    }
  }
  return false;
}

可以看到,他会判断rootWithPendingPassiveEffects是不是不为null。是的话才会往下执行flushPassiveEffectsImpl函数。

那么rootWithPendingPassiveEffects是在哪里被赋值的呢?答案就是layout阶段之后。

在异步调度flushPassiveEffect的时候

  rootDoesHavePassiveEffects = true;

执行了这个操作,然后接着执行,到了layout阶段之后,有这段代码

 const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

  if (rootDoesHavePassiveEffects) { // 有useEffect的effects
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsLanes = lanes;
  }

如果rootDoesHavePassiveEffects为true,那么就将FiberRoot赋值给rootWithPendingPassiveEffects。因为commit阶段是同步的,所以这一切都是在同一帧之内运行的。而等到flushPassiveEffectsImpl函数执行的时候,其实已经赋值了。

所以我们知道useEffect是在before-mutaiton阶段之前调度,在layout阶段之后赋值全局变量,在之后的某一帧执行。

接着看flushPassiveEffectsImpl函数是如何调用useEffect的
function flushPassiveEffectsImpl(){
     if (rootWithPendingPassiveEffects === null) {
    return false;
  }
    
    
  const root = rootWithPendingPassiveEffects; //获取FiberRoot
  // 重置全局变量
  rootWithPendingPassiveEffects = null;
  
  // 调用UseEffect的销毁函数
  commitPassiveUnmountEffects(root.current); //传入rootFiber
  // 调用useEffect函数
  commitPassiveMountEffects(root, root.current); //传入FiberRoot rootFiber
}

调用了commitPassiveUnmountEffects和commitPassiveMountEffects函数

看看commitPassiveMountEffects

export function commitPassiveMountEffects( 
root: FiberRoot, //FiberRoot
finishedWork: Fiber // rootFiber
) {
  nextEffect = finishedWork; //将rootFiber赋值给nextEffect
  commitPassiveMountEffects_begin(finishedWork, root);//调用commitPassiveMountEffects_begin
}
function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
  // while循环找到最下面一个有flags的fiber
  while (nextEffect !== null) {
    const fiber = nextEffect;
    const firstChild = fiber.child;
      // 有useEffect的标识
    if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
      ensureCorrectReturnPointer(firstChild, fiber);
      nextEffect = firstChild; //while循环找到最下面一个有flags的fiber
    } else {
      commitPassiveMountEffects_complete(subtreeRoot, root); //将rootFiber和root赋值进去。此时nextEffect就是最下面的一个fiber
    }
  }
}

function commitPassiveMountEffects_complete(
  subtreeRoot: Fiber,
  root: FiberRoot
) {
  // while往上递归,处理所有有flags的fiber
  while (nextEffect !== null) {
    const fiber = nextEffect;
    // 如果有passivede的flags,表示有useEffect的flags
    if ((fiber.flags & Passive) !== NoFlags) {
        commitPassiveMountOnFiber(root, fiber);
    }

    if (fiber === subtreeRoot) { //当循环到最上面一个rootFiber,直接退出
      nextEffect = null;
      return;
    }

    const sibling = fiber.sibling;
    if (sibling !== null) { 
      ensureCorrectReturnPointer(sibling, fiber.return);
        //有兄弟节点,退出,让兄弟节点执行commitPassiveMountEffects_begin的while循环,
        // 找到兄弟节点下面所有的有useEffect标识的fiber
      nextEffect = sibling;
      return;
    }

      // 下一个执行while的就是父节点
    nextEffect = fiber.return;
  }
}

可以看到,上面几个方法主要就从rootFiber开始往下找,找到最下面一层的有useEffect标识的fiber,然后慢慢递归上来,就像beginWOrk和completeWork的逻辑。接着执行commitPassiveMountOnFiber

function commitPassiveMountOnFiber(
  finishedRoot: FiberRoot, //FiberRoot
  finishedWork: Fiber //当前的要执行副作用函数的fiber
){
       switch (finishedWork.tag) {
            case FunctionComponent:
            case ForwardRef:
            case SimpleMemoComponent: {
             // 执行useEffect的函数,标识是HookPassive | HookHasEffect
			 commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork) //传入标识和当前的fiber
             break;
            }
               ...
       }
  }

commitHookEffectListMount就是用来执行effects的函数。

function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
  // 获取任务队列, effects对象保存在函数组件的fiber.updateQueue.lastEffect之上。
  const updateQueue: FunctionComponentUpdateQueue | null =
    (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; //获取effects链表
  if (lastEffect !== null) {
    // 环状链表,next才是第一个
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    // while循环,一个组件有多个useEffect,他们通过next指针连接。
    // 通过循环依次执行
    do {
      // 根据effect的tag,layoutEffect是5,useEffect是9,layout阶段同步运行layoutEffect,异步调用useEffect
      if ((effect.tag & flags) === flags) { 
        // Mount 执行useLayoutEffect/useEffect的create函数
        const create = effect.create;
        effect.destroy = create(); //将返回值赋值给effect.destroy属性
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

可以看到,

  • 首先从当前的fiber上面获取updateQueue.lastEffect,他存放着effects链表
  • 接着do while循环处理每一个effects,这里会做判断,因为effects链表有useEffect和useLayoutEffect的,他们之间通过tag来标识不同。这里我们传入的是useEffect的标识,所以只有useEffect的effects对象才会被执行。执行create函数后,将返回值赋值给destory函数。这样就完成了一个useEffect的执行了。
  • 这个commitHookEffectListMount其实也是执行useLayoutEffect的函数,只不过执行useLayoutEffect的时候传入的标识是layoutEffect的标识。

到这里我们就知道了useEffect是如何被执行的。那么销毁函数呢?

其实useEffect销毁函数的调用,跟useEffect的逻辑一样,只不过调用的是

  commitHookEffectListUnmount(
          HookPassive | HookHasEffect,
          finishedWork,
          finishedWork.return
        );

commitHookEffectListUnmount函数,传入useEffect标识。

看看逻辑

function commitHookEffectListUnmount(
  flags: HookFlags, //flags
  finishedWork: Fiber, // 当前fiber
  nearestMountedAncestor: Fiber | null //父亲fiber
) {
  // effects链表存放在fibe.updateQueue.lastEffect智商
  const updateQueue: FunctionComponentUpdateQueue | null =
    (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    // 获取第一个effect
    const firstEffect = lastEffect.next;
    let effect = firstEffect;

    // while循环处理所有的effect
    do {
      // 通过tag和flags判断当前执行哪种类型的effects。
      if ((effect.tag & flags) === flags) {
        // Unmount
        const destroy = effect.destroy; //获取销毁函数
        effect.destroy = undefined; //重置为undefined是因为useEffect更新时候重新执行create,会重新赋值给effects.destory

          //调用销毁函数
          safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
        
        }
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

可以看到逻辑是差不多的,都是从fiber.updateQueue.lastEffect上面获取effects链表,然后判断,最后执行destory函数,注意这有一个细节,每次destory都会置为undefined。是因为flushPassiveEffectsImpl函数,其实是调用销毁函数的执行的。

  // 调用UseEffect的销毁函数
  commitPassiveUnmountEffects(root.current); //传入rootFiber
  // 调用useEffect函数
  commitPassiveMountEffects(root, root.current); //传入FiberRoot

比如一次更新之后,销毁函数先执行,才会执行创建函数。而mount的时候,destory函数为undefined,所以就跳过了。

下一节我们继续看UseEffet更新的时候如何执行的。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-06-06 17:16:21  更:2022-06-06 17:17:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:13:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码