React深入useEffect(下)

简介: 本文适合熟悉React、以及在用useEffect遇到难题的小伙伴进行阅读。

四、useEffrct源码解析


在react源码中,我们找到react.js中如下代码,篇幅有限,广东靓仔进行了简化,方便小伙伴阅读:


4.1 useEffect引入与导出

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


// ReactHooks.js
export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  if (__DEV__) {
    if (dispatcher === null) {
     // React版本不对或者Hook使用有误什么的就报错...
    }
  }
  return ((dispatcher: any): Dispatcher);
}


上面的代码就是引入与导出过程,不难看出useEffect实际上是


ReactCurrentDispatcher.current.useEffect橙色的代码


import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
const ReactCurrentDispatcher = {
  current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;


current的类型是null或者Dispatcher,不难看出接下来我们要找类型定义


// ReactInternalTypes.js
export type Dispatcher = {|
  useEffect(
    create: () => (() => void) | void,
    deps: Array<mixed> | void | null,
  ): void,
|};


4.2 组件加载调用mountEffect


函数组件加载时,useEffect会调用mountEffect,接下来我们来看看mountEffect


// ReactFiberHooks.new.js
function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
    return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
  }


PassiveEffectPassiveStaticEffect是二进制常数,用位运算的方式操作,用来标记是什么类型的副作用的。mountEffect走了mountEffectImpl方法


function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}


上面代码中,往hook链表里追加一个hook,把hook存到链表中以后还把pushEffect的返回值存了下来。


function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,
    create,
    destroy, // mountEffectImpl传过来的是undefined
    deps,
    next: (null: any),
  };
  // 一个全局变量,在renderWithHooks里初始化一下,存储全局最新的副作用
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    // 维护了一个副作用的链表,还是环形链表
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      // 最后一个副作用的next指针指向了自身
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}


最后返回了一个effect对象。


Tips: mountEffect就是把useEffect加入了hook链表中,并且单独维护了一个useEffect的链表。


4.3 组件更新时调用updateEffect


函数组件加载时,useEffect会调用updateEffect,接下来我们来看看updateEffect


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


function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 获取当前正在工作的hook
  const hook = updateWorkInProgressHook();
  // 最新的依赖项
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;
  if (currentHook !== null) {
    // 上一次的hook的effect
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 比较依赖项是否发生变化
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 如果两次依赖项相同,componentUpdateQueue增加一个tag为NoHookEffect = 0 的effect,
        hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }
  // 两次依赖项不同,componentUpdateQueue上增加一个effect,并且更新当前hook的memoizedState值
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}


从上面代码中我们看到areHookInputsEqual用来比较依赖项是否发生变化。下面我们看看这个areHookInputsEqual函数


function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
) {
  if (prevDeps === null) {
    ...
    return false;
  }
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}


上面代码中,广东靓仔删掉了一些dev处理的代码,不影响阅读。


其实就是遍历deps数组,对每一项执行Object.is()方法,判断两个值是否为同一个值。


以上内容是源码中的一部分,如果感兴趣的小伙伴可以到react仓库进行阅读~


五、总结


   在我们阅读完官方文档后,我们一定会进行更深层次的学习,比如看下框架底层是如何运行的,以及源码的阅读。    这里广东靓仔给下一些小建议:

  • 在看源码前,我们先去官方文档复习下框架设计理念、源码分层设计
  • 阅读下框架官方开发人员写的相关文章
  • 借助框架的调用栈来进行源码的阅读,通过这个执行流程,我们就完整的对源码进行了一个初步的了解
  • 接下来再对源码执行过程中涉及的所有函数逻辑梳理一遍
相关文章
|
6天前
|
前端开发 JavaScript
深入理解并实践React Hooks —— useEffect与useState
深入理解并实践React Hooks —— useEffect与useState
27 1
|
4月前
|
前端开发
React中useEffect的简单使用
React中useEffect的简单使用
|
10月前
|
前端开发 JavaScript
深入理解React中的useEffect钩子函数
深入理解React中的useEffect钩子函数
78 0
|
4月前
|
前端开发
React中useEffect、useCallBack、useLayoutEffect
React中useEffect、useCallBack、useLayoutEffect
|
1月前
|
前端开发 JavaScript
介绍React中的useEffect
【8月更文挑战第6天】介绍React中的useEffect
28 2
|
2月前
|
存储 前端开发 API
useEffect问题之React提供了什么来帮助确保useEffect的依赖被正确指定
useEffect问题之React提供了什么来帮助确保useEffect的依赖被正确指定
|
2月前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
56 1
|
2月前
|
前端开发
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
40 0
|
2月前
|
JavaScript
react18【系列实用教程】useEffect —— 副作用操作 (2024最新版)
react18【系列实用教程】useEffect —— 副作用操作 (2024最新版)
21 0
|
4月前
|
前端开发 JavaScript
react中的useEffect的使用
react中的useEffect的使用
32 2