React.useEffect源代码渣解

1、寻找hooks相关代码

index.js

文件路径:react-main/packages/react/index.js
/在这个文件我们能看到我们能 import {xxx} from 'react’的所有方法,它是react的入口文件

export{
  Component,
  PureComponent,
  ...,
  useEffect,
  useImperativeHandle,
  useInsertionEffect,
  useLayoutEffect,
  useMemo,
  ...,
}

ReactHooks.js

我们已经提前知道了create 和 deps 两个参数的作用,所以这个方法看起来并没有什么难懂的

调用了resolveDispatcher()返回的实例的useEffect方法

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

返回了ReactCurrentDispatcher.current

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  if (__DEV__) {
    if (dispatcher === null) {
      console.error('这里的error被我删了');
    }
  }
  return ((dispatcher: any): Dispatcher);
}

然鹅这里并没简化,并且线索断了,只知道这个Dispatcher.current的type类型
但是建立在我们对react的了解上知道它与Fiber紧密关联,以及发现这个文件是在ReactFiber里的,我们有理由相信它在ReactFiberHooks相关文件里,我们去找找看
我们应该试图找到一行给ReactCurrentDispatcher.current赋值的代码

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

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

// 平时写代码的对照

useEffect(() => {
  console.log('do actions')
  // return () => UnmountAction
}, [userId, userName]);

ReactFiberHooks.new.js

果然让我们找到了
renderWithHooks这个方法名其实很好懂,大概就是渲染的时候调用的
current我们在这里脑补成Fiber的一个实例对象,那么这个赋值就很好解释了
如果还没有创建Fiber实例,那就铁走挂载方法,或者它某个状态被置为null时,也是走挂载方法,否则的话就执行更新

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  if (__DEV__) {
    // 开发环境的代码直接干掉
  } else {
  	// 当前将要触发的(行为)? memoizedState存储的effect
  	// 下文会提到
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
}

总结

搁这绕了半天其实就这

const HooksDispatcherOnMount: Dispatcher = {
  ...,
  useEffect: mountEffect,
}

const HooksDispatcherOnUpdate: Dispatcher = {
  ...,
  useEffect: updateEffect,
}

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if(Fiber === null || Fiber.memoizedState === null) {
    // HooksDispatcherOnMount.useEffect
  	return mountEffect(create, deps)
  } else{
    // HooksDispatcherOnUpdate.useEffect
  	return updateEffect(create, deps)
  }
}

2、代码解读

mountEffect、updateEffect

PassiveEffect、PassiveStaticEffect、HookPassive 这三个参数是常量,一堆奇奇怪怪的数字,应该是与运算有关跟逻辑没啥关系,这里我们先不管

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if (__DEV__ && xxxxxxxxxxxx) {
    // 跟dev环境有关代码直接干掉
  } else {
    return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
  }
}

function updateEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}

mountEffectImpl

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // hook被赋予了一个类链表结构
  const hook = mountWorkInProgressHook();
  // deps就是useEffect的第二个数组对象,假如传入了[userId, userName]
  const nextDeps = deps === undefined ? null : deps;
  // 看样子是传了个数字标识,不用管
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}

mountEffectImpl 首先创建了一个hook对象,很明显这返回了一个名字叫做workInProgressHook的链表
初始化链表 或者 append一个初始化的值到尾端

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

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

    next: null,
  };

  if (workInProgressHook === null) {
    // 这里的memoizedState 和 上边那个不一样
    // 这是顾名思义 是当前正在渲染的fiber
    // 这里很有意思的是,对象是按址索引的,那么下面更改workInProgressHook,currentlyRenderingFiber.memoizedState 是否会同步更改呢?
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

经过了以上的操作,这时候当前的workInProgressHook 或者说 currentlyRenderingFiber.memoizedState大概长这样,然后去看看pushEffect干了啥
到这里我们可以大胆猜测一下,所有的Effects都按照顺序都存储在workInProgressHook
currentlyRenderingFiber 是一个Fiber类型

type Fiber {
  memoizedState: any,
  
  // A queue of state updates and callbacks.
  updateQueue: any
}

{
  memoizedState: pushEffect(常量, () => console.log('do actions'), undefined, [userId, userName]),
  baseState,
  baseQueue,
  queue,
  next: {
     memoizedState: null,
     baseState: null,
     baseQueue: null,
     queue: null,
     next: null
  }
}

pushEffect

这神tm又是个链表

function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    // 这里的注释是循环?意思是这个链表尾部指向头部么
    next: (null: any),
  };
  // 从当前正在渲染的fiber列表中取出组件更新队列
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  // 如果队列为空,就创建一个队列
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    // 上边说了要形成闭环,并且这是初始化,所以effect.next 指向自己,然后赋给lastEffect
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    // 如果不是空 
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}

// 如果为空的情况下,componentUpdateQueue长这样
{
  lastEffect: {
    tag: 常量,
    create : () => console.log('do actions'),
    destroy: undefined,
    deps: [userId, userName],
    next: 下一个还是自己
  },
  stores: null,
};
// 如果不为空,
{
  lastEffect: {
    tag: 常量,
    create : () => console.log('do actions'),
    destroy: undefined,
    deps: [userId, userName],
    next: {
      tag: 常量,
      create : () => console.log('do actions 2'),
      destroy: undefined,
      deps: [userId2, userName2],
      next: 指向链表头部
    }
  },
  stores: null,
}

updateEffectImpl


// Represents the phase in which the effect (not the clean-up) fires.
export const Insertion = /*  */ 0b0010;
export const Layout = /*    */ 0b0100;
export const Passive = /*   */ 0b1000;


function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;

  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    // 销毁的function
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 如果相同的话,hookFlags = Passive 
        hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}

updateWorkInProgressHook

currentlyRenderingFiber.alternate,在Fiber接口中是着么定义的
This is a pooled version of a Fiber. Every fiber that gets updated will eventually have a pair. There are cases when we can clean up pairs to save memory if we need to

这个函数看起来麻烦,其实就是把hook的那个双链表结构给搞过来

function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base. When we reach the end of the base list, we must switch to
  // the dispatcher used for mounts.
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    // 其实就是在更新的时候,拿了上一轮(Mount)时候生成的 memoizedState 副本
    // currentHook = nextCurrentHook;
    // 便于 updateWorkInProgressHook 后的比对
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.

    if (nextCurrentHook === null) {
      throw new Error('Rendered more hooks than during the previous render.');
    }

    currentHook = nextCurrentHook;

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

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

      next: null,
    };

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

commitHookEffectListMount

ReactFiberCommitWork中可以找到此方法
都是在Fiber的最后,依据以上构造的数据结构来处理

function commitHookEffectListUnmount() {
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
 	  // 猜测
 	  // 所以说 effect 链表是一个环,lastEffect是最后一个,lastEffect.next是第一个
 	  // 而 currentlyRenderingFiber.memoizedState 可以直接取到,所以不必是环
 	  // 至此 在一个循环中,这个闭包双链表结构结束了他的使命
      const firstEffect = lastEffect.next;
      let effect = firstEffect;
      do {
        // create 就是useEffect传入的第一个参数,他可能有返回值,给了destroy
      	const create = effect.create;
        effect.destroy = create();
        effect = effect.next;
      }while (effect !== firstEffect)
  }
}

一张令人生草的数据图

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值