Skip to content

Latest commit

 

History

History
240 lines (213 loc) · 7.59 KB

12.React源码学习-beginWork.md

File metadata and controls

240 lines (213 loc) · 7.59 KB

beginWork

执行对整棵树的每一个节点进行更新的操作(performUnitOfWork内调用beginWork)

源码在 react-reconciler 下的 ReactFiberBeginWork.js 内:

beginWork()方法里通过 switch (workInProgress.tag)对不同的组件做不同的更新处理:

下面看看不同组件的更新:

出现频率很高的一个判断 current === null 用于判断组件是否是第一次渲染。

1.FunctionComponent

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  // 此处去掉__DEV__代码
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);
  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToUseHooks(current, workInProgress, renderExpirationTime);
  if (__DEV__) {
    // 此处去掉__DEV__代码
  } else {
    nextChildren = Component(nextProps, context);
  }
  nextChildren = finishHooks(Component, nextProps, nextChildren, context);
  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

调用Component时传入了两个值nextPropscontext, 那么函数组件可以直接通过第二个参数拿到context,官方文档没有说明过这点。

2.ClassComponent

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  if (__DEV__) {
    // 去掉__DEV__代码
  }
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderExpirationTime);

  const instance = workInProgress.stateNode;
  let shouldUpdate;
  if (instance === null) {
    if (current !== null) {
      // An class component without an instance only mounts if it suspended
      // inside a non- concurrent tree, in an inconsistent state. We want to
      // tree it like a new mount, even though an empty version of it already
      // committed. Disconnect the alternate pointers.
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.effectTag |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    constructClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    mountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
  if (__DEV__) {
    // 去掉__DEV__代码
  }
  return nextUnitOfWork;
}

开始是对context的操作,因为ClassComponent可以成为context provider。

current === null只会出现在第一次渲染的时候,因为会先创建workInProcess,在渲染结束之后才会把workInProcess拷贝成current,代表着第一次渲染结束。而后面也会出现根据current === null来判断是否需要调用componentDidMount的代码

在这里如果current === null就行要进行实例的构建工作,如果不是,直接updateClassInstance

如果是还要判断实例是否已经创建workInProgress.stateNode === null,如果是的话要创建这个实例,通过constructClassInstance(代码在同级的ReactFiberClassComponent.js内,这个方法调用adoptClassInstance给 ClassComponent 的实例挂载一个updater对象,里面包含我们常用的方法:forceUpdate, replaceState, setState),并且挂载实例mountClassInstance(初始化 props、state 等实例属性,如果有updateQueue就更新之,一般来说第一次渲染是没有的。)

如果已经有current则调用updateClassInstance

最后调用finishClassComponent

3.IndeterminateComponent

FunctionalComponent在含有render方法时会被当做ClassComponent来处理

export default ()=>{
  return {
    componentDidMount() {
      console.log('Indeterminate')
    },
    render() {
      return <div>Indeterminate</div>
    },
  }
}

4.HostComponent

原生的html标签(completeWork阶段就是从HostComponent开始逆着Fiber输往回return Fiber,并在HostComponent上进行虚拟DOM的diff判断比较props)

reconcileChildren

不同类型的组件更新最后都会调用此方法

  • 根据props.children生成Fiber子树
  • 判断Fiber对象是否可以复用
  • 列表根据key优化
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}

查看上面两种不同调用的方法引入:

import {
  mountChildFibers,
  reconcileChildFibers,
  cloneChildFibers,
} from './ReactChildFiber';

然后去ReactChildFiber.js内查看方法定义: mountChildFibers和reconcileChildFibers方法是一样的,唯一的区别是生成这个方法的时候的一个参数不同

export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

这个参数叫shouldTrackSideEffects,他的作用是判断是否要增加一些effectTag,主要是用来优化初次渲染的

if (shouldTrackSideEffects && newFiber.alternate === null) {
  newFiber.effectTag = Placement
}

ChildReconciler最终调用的是reconcileChildFibers:

function ChildReconciler(shouldTrackSideEffects) {
  // 省略其他代码
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {}
  return reconcileChildFibers;
}

reconcileChildFibers会根据newChild的不同类型进行对应的处理,最终的返回是当前节点的第一个孩子节点,会在performUnitWork中 return 并赋值给nextUnitOfWork。

children的合法类型:

  • ReactElement,通过createElement和ReactDOM.createPortal创建,$$typeof不同
  • string或者number,
    abc
    中div的children就是"abc"
  • [// renderAble]数组,每一项都可以是其他合法类型,不能嵌套
  • Iterator,跟数组类似,只是遍历方式不同
  • React.ConcurrentMode这些内置组件,最终会转换成ReactElement,不同的是ReactElement.type
  • reconcileSingleElement & reconcileSinglePortal & reconcileSingleTextNode
  • reconcileChildrenArray & reconcileChildrenArray