Source of interpretation React useEffect

Foreword

Interpretation of the source code is conducive to figure out what to do Hooks in the end, if you feel useEffect very "magic", this article may help you some.

This blog space is limited, look useEffect, simple and succinct, take you to look deep React Hooks

Hook related to what you want to find the source code (you can jump directly)

First we get the source code from Github react, and then react folder can be found in the packages in which the index.js is our entrance.

The code is simple, it is two lines:

const React = require('./src/React');

module.exports = React.default || React;

So let's go and see 'react / src / React', the code a bit more, we simplify it:

import ReactVersion from 'shared/ReactVersion';

// ...

import {
  useEffect,
} from './ReactHooks';

const React = {
  useEffect
};

//...
export default React;

Well, at least now we know why Hooks of reference are:

import {useEffect} from 'react'

Next, we continue to see 'react / src / ReactHooks'.

ReactHooks file (you can jump directly)

Before saying good look useEffect, so the same need to simplify the code.

And considering that some people are not familiar with TypeScript grammar, syntax also removed TypeScript, it will simplify the code after the case.

Now we look to simplify the code:

import invariant from 'shared/invariant';

import ReactCurrentDispatcher from './ReactCurrentDispatcher';

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  // React版本不对或者Hook使用有误什么的就报错
  // ...
  return dispatcher;
}

export function useEffect(create,inputs) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, inputs);
}

Here you can see, our useEffect actually ReactCurrentDispatcher.current.useEffect.

ReactCurrentDispatcher file (you can jump directly)

Look ReactCurrentDispatcher file, there is no simplification:

import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';

const ReactCurrentDispatcher = {
  current: (null: null | Dispatcher),
};

export default ReactCurrentDispatcher;

He found that the type of current is null or Dispatcher, so here we are it's easy to guess that the source of this thing in 'react-reconciler / src / ReactFiberHooks'.

ReactFiberHooks file

Thousands of lines of code, a large head. But Mo panic, we are not writing to react, to see the principle of it.

We already know useState actually ReactCurrentDispatcher.current.useState before.

Obviously ReactCurrentDispatcher.current no matter what it is listed separately, we need to know who is assigned to him on the line.

After streamlining the code, remove the distinction with __DEV__ development code, we find that the entire file assigned to ReactCurrentDispatcher.current few.

The only exception to a determination is independent renderWithHooks function of this block of code:

export function renderWithHooks(
  current,
  workInProgress,
  Component,
  props,
  secondArg,
  nextRenderExpirationTime
){
  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  let children = Component(props, secondArg);
  return children;
}

We do not know this code is doing, but he certainly used when rendering component.

And here it is clear that ReactCurrentDispatcher.current value can only be HooksDispatcherOnMount and HooksDispatcherOnUpdate.

Obviously both a time for loading and one for the update.

Then we have found some relevant code:

const HooksDispatcherOnMount = {
  useEffect: mountEffect
};

const HooksDispatcherOnUpdate = {
  useEffect: updateEffect
};

In other words, when the assembly is loaded, useEffect calls mountEffect, calls updateEffect when updated components.

Let us continue to look at these two functions:

function mountEffect(create, deps) {
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}

function updateEffect(create, deps) {
  return updateEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}

Here UpdateEffect constant and PassiveEffect are binary, bit manipulation operations mode.

First you do not know the specific meaning, to know that is a constant.

Next we look at the specific mountEffectImpl:

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps){
  const hook = mountWorkInProgressHook();
  // useEffect不传依赖,那么就为null
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.effectTag |= fiberEffectTag;
  // 链表尾部hook对象的memoizedState为pushEffect的返回值
  hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
}

We see the first line of code calls mountWorkInProgressHook built a hook objects, let's look at mountWorkInProgressHook:

function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

Obviously here is a list structure workInProgressHook, if workInProgressHook list is null objects will be assigned to the new hook it, if not null, then added to the tail of the list.

Here it is necessary to explain:

Hooks stored as a linked list of memoizedState the fiber.
currentHook current fiber linked list.
workInProgressHook is about to be added to the work-in-progress fiber of the list.

Then we look at pushEffect:

function pushEffect(tag, create, destroy, deps) {
  // 新建一个effect,很明显又是个链表结构
  const effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: null,
  };
  // 从currentlyRenderingFiber.updateQueue获取组件更新队列
  let componentUpdateQueue= currentlyRenderingFiber.updateQueue;
  // 判断组件更新队列是否为空,每次在调用renderWithHooks都会将这个componentUpdateQueue置为null
  // 这样的话每次update这个组件时,就会创建一个新的effect链表
  if (componentUpdateQueue === null) {
    // 为空就创建一个组件更新队列
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    // 并赋值给currentlyRenderingFiber.updateQueue
    currentlyRenderingFiber.updateQueue = componentUpdateQueue;
    // 组件更新队列最新的effect为我们新建的effect
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    // 如果组件更新队列已经存在,获取它最新的Effect
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      // 如果最新的Effect为null,那么组件更新队列最新的Effect为我们新建的effect
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      // 否则将我们的effect加入到链表结构中最末尾,然后他的next为链表结构的第一个effect
      // 这里的effect链表是个闭环
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}

Let us look at updateEffectImpl called when the update:

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  //  这里 updateWorkInProgressHook 
  //  workInProgressHook = workInProgressHook.next;
  //  currentHook = currentHook.next;
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;


  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 对比两个依赖数组的各个值之间是否有变动,如果没变动,那么就设置标志位为NoHookEffect
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        pushEffect(NoHookEffect, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.effectTag |= fiberEffectTag;

  hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
}

We can see updateEffectImpl and mountEffectImpl like, the most important thing is we have to string together two functions, look at what they achieved in the end.

Data structure diagram Hook

Here I draw a diagram, which will help to understand:

This configuration is a configuration diagram of a component at a time.

FIG yellow Fiber node, the node Hook green, blue Effect node.

Fiber node, in fact, our virtual DOM node, react will generate a Fiber node tree, each component has a corresponding Fiber Fiber tree node.

CurrentlyRenderingFiber node which represents our ongoing rendered, it comes from workInProgress, current node that represents already rendered.

Component is loaded , the execution will each useEffect, then it will create a list Hook, and workInProgress of memoizedState field points to the node Hook Hook tail of the list.

Hook while constructing each node, the node will also construct a Effect Similarly, memoizedState Hook field points to the node corresponding to the node Effect.

Effect node and each in turn joined to form a linked list, then the workInProgress field points to the tail updateQueue Effect Effect node list.

Component updates , the array will depend currentHook sequentially compare different points of the new dependencies Effect array, as if it was set Effect node effectTag NoHookEffect.

However, regardless of the value changes dependent on whether the array, will construct a new node Effect, as memoizedState Hook node field value.

Then ready to render time, it will go directly to lastEffect updateQueue of Fiber node, that is, directly to the tail node Effect Effect list.

因为effect链表是闭环的,这里通过lastEffect的next找到第一个Effect。

然后循环遍历effect链表,当effectTag为NoHookEffect则不做操作,否则会去先执行effect的destroy操作,然后再执行create操作。

对,你没看错,总结起来就是每次更新后,只要依赖项改变,那么就会执行useEffect的卸载函数,再执行第一个参数create函数。

这一部分代码比较远:

function commitHookEffectList(
  unmountTag,
  mountTag,
  finishedWork,
) {
  const updateQueue = finishedWork.updateQueue;
  let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if (destroy !== undefined) {
          destroy();
        }
      }
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;
        effect.destroy = create();
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

这里的位运算大家可能有点看不懂,因为NoHookEffect的值是0,所以只要effect.tag被设置为NoHookEffect,那么

effect.tag & unmountTag

就必然为NoHookEffect。

我们还记得,我们之前的玩法,依赖数组各个值不变时,就设置Effect节点的effectTag为NoHookEffect。

此时是绝对不会执行先destroy Effect节点,再执行Effect函数create的操作。

而如果effect.tag的值不为NoHookEffect,那也得需要effect.tag与unmountTag至少有一个位相同才能执行destroy。

让我们看看之前无论是mountEffectImpl还是updateEffectImpl都默认传的是:UnmountPassive | MountPassive,也就是说effect.tag为UnmountPassive | MountPassive。

而很明显这个设计的目的在于,当mountTag为MountPassive时执行create函数,而unmountTag为UnmountPassive时创建执行destroy函数。

而只有下面这个地方会做这个Passive操作:

export function commitPassiveHookEffects(finishedWork: Fiber): void {
  if ((finishedWork.effectTag & Passive) !== NoEffect) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
      case Chunk: {
        commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
        commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
        break;
      }
      default:
        break;
    }
  }
}

这里的意思很明显,先遍历一遍effect链表,每个依赖项变了的hook都destroy一下,然后再遍历一遍effect链表,每个依赖项变了的,都执行create函数一下。

也就是说每次都会按照useEffect的调用顺序,先执行所有useEffect的卸载函数,再执行所有useEffect的create函数。

而commitPassiveHookEffects又是只有flushPassiveEffects这个函数最终能调用到。

而每次 React 在检测到数据变化时,flushPassiveEffects就会执行。

不论是props还是state的变化都会如此。

所以如果您真的有需要去模拟一个像之前的componentDidMount和componentWillUnmount的生命周期,那么最好用上一个单独的Effect:

useEffect(()=>{
  // 加载时的逻辑
  return ()=>{
    // 卸载时的逻辑
  }
},[])

这里用[]作为依赖数组,是因为这样依赖就不会变动,也就是只在加载时执行一次加载逻辑,卸载时执行一次卸载逻辑。

不加依赖数组时,那么每次渲染都会执行一次加载和卸载。

总结

希望我的这篇文章让您有一些收获。

这里主要想说一下自己在源码阅读中的感想。

读这部分的源码,其实更像是在盲人摸象。

为了追求效率,需要避开一些比较复杂的东西,比如我们提到的Fiber节点树,又比如其实useEffect替换effect和具体执行effect并不是同步的。

否则解读很多东西要花很多时间,怕是真的要写一个系列了。

源码阅读略显艰涩,还好关键的地方都是有注释的,在阅读的过程中也能收获不少东西。

我并不是react的开发者,如果在解读的过程中有什么疏漏和误解,还望诸位批评指正。

Guess you like

Origin www.cnblogs.com/vvjiang/p/12160791.html