四、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, ); }
PassiveEffect和PassiveStaticEffect是二进制常数,用位运算的方式操作,用来标记是什么类型的副作用的。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仓库进行阅读~
五、总结
在我们阅读完官方文档后,我们一定会进行更深层次的学习,比如看下框架底层是如何运行的,以及源码的阅读。 这里广东靓仔给下一些小建议:
- 在看源码前,我们先去官方文档复习下框架设计理念、源码分层设计
- 阅读下框架官方开发人员写的相关文章
- 借助框架的调用栈来进行源码的阅读,通过这个执行流程,我们就完整的对源码进行了一个初步的了解
- 接下来再对源码执行过程中涉及的所有函数逻辑梳理一遍