前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之updateClassComponent(上)

React源码解析之updateClassComponent(上)

作者头像
进击的小进进
发布2020-02-13 10:32:02
7890
发布2020-02-13 10:32:02
举报
文章被收录于专栏:前端干货和生活感悟

前言: 本篇文章给大家带来的是updateClassComponent ()的讲解,即 ClassComponet 的更新流程:

代码语言:javascript
复制
case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }

一、updateClassComponent 作用: 更新ClassComponent

源码:

代码语言:javascript
复制
//更新ClassComponent
function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  //删除了 dev 代码
  //=============context 相关代码,可跳过=========================================================
  // 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);
  //=====================================================================
  // 此处的stateNode指的是ClassComponent对应的Class实例。
  // FunctionComponent没有实例,所以stateNode值为null
  const instance = workInProgress.stateNode;
  let shouldUpdate;
  //当未创建实例的时候
  if (instance === null) {
    //current和workInProgress是doubleBuffer的关系,
    //React会先创建workInProgress,在渲染结束后,会把workInProgress复制给 current,此时渲染结束

    //渲染了但是没有实例的情况,比如报错时
    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.
    //构建 class 实例
    constructClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    //在未render的 class 实例上调用挂载生命周期
    mountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
    shouldUpdate = true;
  }
  //第一次渲染
  else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    //复用 class 实例,更新 props/state,
    // 调用生命周期(componentWillMount,componentDidMount),返回 shouldUpdate
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  //instance!==null&&current!==null
  //当已经创建实例并且不是第一次渲染的话,调用更新的生命周期方法为componentWillUpdate,componentDidUpdate(),
  else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  //判断是否执行 render,并返回 render 下的第一个 child
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
  //删除了 dev 代码

  return nextUnitOfWork;
}

解析:

ClassComponet的更新流程图如上,主要分三种情况:

(1) 类实例(class instance)未被创建的情况 (2) 类实例存在,但 current 为 null,即第一次渲染的情况 (3) 类实例存在,并且是多次渲染的情况

上述的三种情况返回的结果会赋值给变量shouldUpdate

(4) 执行finishClassComponent()方法,判断ClassComponent是否需要执行render()方法

接下来看第一种情况:instance = null

二、constructClassInstance 作用: 构建 Class Instance

源码:

代码语言:javascript
复制
function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  let isLegacyContextConsumer = false;
  let unmaskedContext = emptyContextObject;
  let context = null;
  const contextType = ctor.contextType;
  //删除了 dev 代码
  //=============context 部分可跳过====================================================
  if (typeof contextType === 'object' && contextType !== null) {
    context = readContext((contextType: any));
  } else {
    unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    const contextTypes = ctor.contextTypes;
    isLegacyContextConsumer =
      contextTypes !== null && contextTypes !== undefined;
    context = isLegacyContextConsumer
      ? getMaskedContext(workInProgress, unmaskedContext)
      : emptyContextObject;
  }
  //==========================================================================

  // Instantiate twice to help detect side-effects.
  //删除了 dev 代码
  //ctor即workInProgress.type,也就是定义classComponet的类
  const instance = new ctor(props, context);
  // instance.state 即开发层面的 this.state
  // 注意这个写法,连等赋值
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
  // 初始化 class 实例,即初始化workInProgress和instance
  adoptClassInstance(workInProgress, instance);

  //删除了 dev 代码

  // Cache unmasked context so we can avoid recreating masked context unless necessary.
  // ReactFiberContext usually updates this cache but can't for newly-created instances.
  //context 相关,可跳过
  if (isLegacyContextConsumer) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return instance;
}

解析: (1) 注意下ctorworkInProgress.type,也就是 ClassComponent 的类 (2) 该方法主要是执行了adoptClassInstance()方法,接下来讲下这方法

三、adoptClassInstance 作用: 初始化 class instance

源码:

代码语言:javascript
复制
//初始化 class 实例,即初始化workInProgress和instance
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // The instance needs access to the fiber so that it can schedule updates
  //ReactInstanceMap.set(instance, workInProgress)
  setInstance(instance, workInProgress);
  if (__DEV__) {
    instance._reactInternalInstance = fakeInternalInstance;
  }
}

解析: adoptClassInstance主要做了三件事:

(1) 将classComponent初始化的时候拿到的update对象赋值给instance.updater (2) 将新的 ClassComponent 实例赋值给workInProgress.stateNode (3) 执行setInstance()方法,将workInProgress赋值给instance._reactInternalFiber,这样就能通过instancethis找到workInProgress

代码语言:javascript
复制
// setInstance 即 set
export function set(key, value) {
  //即 instance._reactInternalFiber=workInProgress
  key._reactInternalFiber = value;
}

注意: 关于classComponentUpdater对象的讲解, 请看: React源码解析之setState和forceUpdate

四、mountClassInstance 作用: 在未renderclass实例上调用挂载生命周期

源码:

代码语言:javascript
复制
// Invokes the mount life-cycles on a previously never rendered instance.
//在未 render 的 class 实例上调用挂载生命周期
function mountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  //nextProps,待更新的 props
  newProps: any,
  renderExpirationTime: ExpirationTime,
): void {
  if (__DEV__) {
    checkClassInstance(workInProgress, ctor, newProps);
  }
  //更新 props/state
  const instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;
  //=========context 相关,可跳过==================================
  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    instance.context = readContext(contextType);
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }
  //=============================================

  //删除了 dev 分支


  let updateQueue = workInProgress.updateQueue;
  //执行更新 update队列
  if (updateQueue !== null) {
    //里面还更新了 state
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    //因为 state 更新了,所以instance的state也要更新
    instance.state = workInProgress.memoizedState;
  }
  //getDerivedStateFromProps是 React 新的生命周期的方法
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    instance.state = workInProgress.memoizedState;
  }

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  //判断是否要调用 componentWillMount
  //第一次渲染的话,是要调用componentWillMount的
  if (
    typeof ctor.getDerivedStateFromProps !== 'function' &&
    typeof instance.getSnapshotBeforeUpdate !== 'function' &&
    (typeof instance.UNSAFE_componentWillMount === 'function' ||
      typeof instance.componentWillMount === 'function')
  ) {
    callComponentWillMount(workInProgress, instance);
    // If we had additional state updates during this life-cycle, let's
    // process them now.
    //在ComponentWillMount中是有可能执行 setState 的,
    // 所以 React 也要及时更新state 并更新到instance上
    updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null) {
      processUpdateQueue(
        workInProgress,
        updateQueue,
        newProps,
        instance,
        renderExpirationTime,
      );
      instance.state = workInProgress.memoizedState;
    }
  }
  //等到真正渲染到 DOM 上去的时候,再去调用componentDidMount
  if (typeof instance.componentDidMount === 'function') {
    workInProgress.effectTag |= Update;
  }
}

解析: mountClassInstance()的逻辑如下: (1) 初始化 props 和 state (2) 如果有更新队列的话,执行processUpdateQueue()并更新 state 从开发角度看应该是根据constructor(){ }里面的内容,将新的 update push 进 updateQueue,并更新一次 props 和 state

代码语言:javascript
复制
constructor(props) {
    super(props);
    this.state = {
    };
}

(3) 如果开发代码中有执行getDerivedStateFromProps()的话,则调用对应的applyDerivedStateFromProps()API,更新 state

关于getDerivedStateFromProps()的作用,请查看: https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromprops

(4) 如果开发代码中有执行componentWillMount()的话,则调用对应的callComponentWillMount()API,并且如果开发代码中的componentWillMount(){ }里面有setState()方法,导致updateQueue里有更新时,执行processUpdateQueue,更新 props 和 state

(5) 最后,等到要真正渲染到 DOM 上去的时候,再去调用componentDidMount()

接下来讲解下processUpdateQueue(),看看 ClassComponent 是如何更新 updateQueue 的

五、processUpdateQueue 作用: 更新 update 队列,并更新 state

源码:

代码语言:javascript
复制
//更新 update 队列,并更新 state
export function processUpdateQueue<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  props: any,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  //并不是强制的更新
  hasForceUpdate = false;
   //保证workInProgress上的update队列是 queue 的副本
  queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);

  if (__DEV__) {
    currentlyProcessingQueue = queue;
  }

  // These values may change as we process the queue.
  //当执行更新队列的时候,这些属性可能会动态改变,所以先创建副本变量
  let newBaseState = queue.baseState;
  let newFirstUpdate = null;
  let newExpirationTime = NoWork;

  // Iterate through the list of updates to compute the result.
  //获取队列中的第一个 update元素,来判断它是否需要更新
  let update = queue.firstUpdate;
  let resultState = newBaseState;
  //如果有update的话
  while (update !== null) {
    //获取该 update 的优先级,判断是否需要执行 update
    const updateExpirationTime = update.expirationTime;
    //当更新队列的第一个 update元素 的更新优先级低于renderExpirationTime的时候
    if (updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      //不执行该 update元素 的更新
      if (newFirstUpdate === null) {
        // This is the first skipped update. It will be the first update in
        // the new list.
        //本次没有更新的 update元素,会优先放到下一次去判断要不要更新
        newFirstUpdate = update;
        // Since this is the first update that was skipped, the current result
        // is the new base state.
        //如果 update元素 被跳过的话,base state也会改变,所以要及时更新newBaseState
        newBaseState = resultState;
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      //该 update元素 被跳过,仍留在队列中,所以它仍有expirationTime,需要被更新
      if (newExpirationTime < updateExpirationTime) {
        newExpirationTime = updateExpirationTime;
      }
    }
    //该update元素 会被执行更新的话
    else {
      // This update does have sufficient priority.

      // Mark the event time of this update as relevant to this render pass.
      // TODO: This should ideally use the true event time of this update rather than
      // its priority which is a derived and not reverseable value.
      // TODO: We should skip this update if it was already committed but currently
      // we have no way of detecting the difference between a committed and suspended
      // update here.
      //可跳过
      markRenderEventTimeAndConfig(updateExpirationTime, update.suspenseConfig);

      // Process it and compute a new result.
      //执行 update 并计算出一个新的结果
      //获取最新的 state
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      //callback 也就是 this.setState({xx:yy},()=>{})的回调函数()=>{}
      const callback = update.callback;
      //当 callback 不为 null 时,在 setState 更新完后,是要执行 callback 的
      //所以要设置相关的属性来“提醒”
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        update.nextEffect = null;
        //链表的插入操作
        if (queue.lastEffect === null) {
          queue.firstEffect = queue.lastEffect = update;
        } else {
          queue.lastEffect.nextEffect = update;
          queue.lastEffect = update;
        }
      }
    }
    // Continue to the next update.
    //跳到下一个 update 元素,循环
    update = update.next;
  }

  //======逻辑同上,不再赘述===============================================
  // Separately, iterate though the list of captured updates.
  //应该是捕获错误阶段的更新
  let newFirstCapturedUpdate = null;
  update = queue.firstCapturedUpdate;
  while (update !== null) {
    const updateExpirationTime = update.expirationTime;
    if (updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      if (newFirstCapturedUpdate === null) {
        // This is the first skipped captured update. It will be the first
        // update in the new list.
        newFirstCapturedUpdate = update;
        // If this is the first update that was skipped, the current result is
        // the new base state.
        if (newFirstUpdate === null) {
          newBaseState = resultState;
        }
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      if (newExpirationTime < updateExpirationTime) {
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      const callback = update.callback;
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        update.nextEffect = null;
        if (queue.lastCapturedEffect === null) {
          queue.firstCapturedEffect = queue.lastCapturedEffect = update;
        } else {
          queue.lastCapturedEffect.nextEffect = update;
          queue.lastCapturedEffect = update;
        }
      }
    }
    update = update.next;
  }
  //======================================================
  //这边主要是执行完 update 后,更新 queue 上的相关属性
  if (newFirstUpdate === null) {
    queue.lastUpdate = null;
  }
  if (newFirstCapturedUpdate === null) {
    queue.lastCapturedUpdate = null;
  } else {
    workInProgress.effectTag |= Callback;
  }
  if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
    // We processed every update, without skipping. That means the new base
    // state is the same as the result state.
    newBaseState = resultState;
  }
  //最后更新 queue 上的属性
  queue.baseState = newBaseState;
  queue.firstUpdate = newFirstUpdate;
  queue.firstCapturedUpdate = newFirstCapturedUpdate;

  // Set the remaining expiration time to be whatever is remaining in the queue.
  // This should be fine because the only two other things that contribute to
  // expiration time are props and context. We're already in the middle of the
  // begin phase by the time we start processing the queue, so we've already
  // dealt with the props. Context in components that specify
  // shouldComponentUpdate is tricky; but we'll have to account for
  // that regardless.

  // 由于执行了 update 队列的部分更新,
  // 那么 update 队列的expirationTime将由保留下来的 update 元素的最高优先级的 expirationTime 决定
  workInProgress.expirationTime = newExpirationTime;
  workInProgress.memoizedState = resultState;

  if (__DEV__) {
    currentlyProcessingQueue = null;
  }
}

解析: (1) 执行ensureWorkInProgressQueueIsAClone(),生成updateQueue的副本queue (2) 取出queue的第一个update 元素,并根据它的expirationTime判断是否需要执行更新 (3) 如果不需要执行更新,则该 update 元素会保留在queue中,并更新它的expirationTime (4) 如果需要执行更新的话,执行getStateFromUpdate(),来获取新的state (5)如果该 update 元素上,还有 callback 的话(即开发角度的this.setState({xx:yy},()=>{})回调函数()=>{}),还要设置相关属性来“提醒”更新 state 后,再执行 callback (6) update = update.next,跳到下一个 update 元素,重复执行 (2)、(3)、(4)、(5)

(7) 然后是「捕获错误」阶段的更新,逻辑同上,不再赘述 (8) 最后,更新 queue 和 workInProgress 上的属性

接下来讲解下ensureWorkInProgressQueueIsAClone()getStateFromUpdate()

六、ensureWorkInProgressQueueIsAClone 作用: 保证workInProgress上的update队列是queue的副本

源码:

代码语言:javascript
复制
//保证workInProgress上的update队列是 queue 的副本
function ensureWorkInProgressQueueIsAClone<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
): UpdateQueue<State> {
  const current = workInProgress.alternate;
  if (current !== null) {
    // If the work-in-progress queue is equal to the current queue,
    // we need to clone it first.
    if (queue === current.updateQueue) {
      queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
    }
  }
  return queue;
}

解析: 源码比较简单,就不讲了,cloneUpdateQueue()的源码如下:

代码语言:javascript
复制
//创建更新队列的副本
function cloneUpdateQueue<State>(
  currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    baseState: currentQueue.baseState,

    //首、尾之间的update用next串联
    firstUpdate: currentQueue.firstUpdate,
    lastUpdate: currentQueue.lastUpdate,

    // TODO: With resuming, if we bail out and resuse the child tree, we should
    // keep these effects.
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,

    firstEffect: null,
    lastEffect: null,

    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

七、getStateFromUpdate 作用: 获取最新的state

源码:

代码语言:javascript
复制
//获取最新的state
function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  switch (update.tag) {
    //返回执行payload后的 state
    case ReplaceState: {
      const payload = update.payload;
      if (typeof payload === 'function') {
        // Updater function
        //删除了 dev 代码
        const nextState = payload.call(instance, prevState, nextProps);
        //删除了 dev 代码
        return nextState;
      }
      // State object
      return payload;
    }
    case CaptureUpdate: {
      workInProgress.effectTag =
        //~ 的意思是获取除了ShouldCapture的所有属性,也就是获取了DidCapture
        //因此最后仍是获取了DidCapture
        (workInProgress.effectTag & ~ShouldCapture) | DidCapture;
    }
    // Intentional fallthrough
    //通过 setState 传入的属性
    case UpdateState: {
      const payload = update.payload;
      let partialState;
      //如果payload是 function 就获取执行payload后得到的 state
      if (typeof payload === 'function') {
        // Updater function
        //删除了 dev 代码
        partialState = payload.call(instance, prevState, nextProps);
        //删除了 dev 代码
      }
      //否则就直接赋值给 state
      else {
        // Partial state object
        partialState = payload;
      }
      //如果partialState没有值,则视为没有更新 state
      if (partialState === null || partialState === undefined) {
        // Null and undefined are treated as no-ops.
        return prevState;
      }
      // Merge the partial state and the previous state.
      //如果partialState有值的话,需要和未更新的部分 state 属性进行合并
      return Object.assign({}, prevState, partialState);
    }
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}

解析: 重点看UpdateState的情况,该情况也是开发角度上this.setState({xxx})的情况,如果有新state的话,则执行Object.assign({}, prevState, partialState)来进行state的合并

最后: 本文着重讲解了updateClassComponent的第一种情况— —「类实例(class instance)未被创建」的情况,第二三种情况会在后续文章中更新

GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberBeginWork.js


(完)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档