React Native源码分析——Virtual DOM

本文基于0.58.5分析React Native Reconciliation过程

Components、Elements和Instances

讲Virtual DOM之前,先讲下React Native几个核心概念和这样设计的目的。在面向对象的UI开发时,要渲染一个UI时,都要自己创建UI对象,并且管理对象引用,例如iOS上要渲染一个UIView:

UIView *view= [UIView new];
UILabel *label= [UILabel new];
label.text = @"test";
[view addSubview:label];

这种设计模式带来的问题是开发者必须自己创建、更新UI对象,当UI复杂时,维护成本会急剧增加。
React使用一种非常巧妙的设计模式来解决上面问题,UI开发时,只需要描述UI界面,引擎会根据描述自动创建具体的实例,在更新时也只需更新UI界面描述。这样开发者就从复杂的UI对象创建、更新中解放出来,开发者只需要关注UI长怎样和核心逻辑,React帮你搞定对象创建和维护,React通过Components、Elements和Instances来实现这种模式。

Elements

Element是用来描述UI的js对象,Element只有type和props两个属性,Element创建成本很低,一旦创建就不可变,Component更新到时候会创建新的Element。Element可以嵌套,React会递归解析Element直到叶子结点,这样就得到一颗Element树,这颗树就是Virtual DOM树,React通过diff等算法后把Virtual DOM渲染到屏幕,渲染过程做了很多优化。可以通过render()或者其他方法返回Element,例如:

render() {
  return(
    <View style = {{height:200,backgroundColor:'#999999'}}>
      <Text> test </Text>
   </View>
  )
}

上述JSX语法最终会转换成以下Element:

{
  type:View,
  props: {
    style:{height:200,backgroundColor:'#999999'},
    children: {
      type:Text,
      props: {
        children:'test'
      }
    }
  }
}

Components

Component是生成Element的对象,可以是个class,也可以是简单的方法。当Component是class的时候,可以存储state和其他属性,实现复杂的逻辑;当Component是方法的时候,是不可变的Component,相当于只有render()方法的class Component。

Instances

Instance就是Component 实例,React Native开发过程不用自己管理Instance,React引擎自动创建并维护Instance,具体创建逻辑下文详细介绍。

Virtual DOM

通常所说的Virtual DOM是指相对于Real DOM的element树,Virtual DOM是最初React版本的说法,最开始React只是用在前端,引入Virtual DOM的概念是为了提升UI渲染性能,在UI变化的时候可以先比较Virtual DOM,只更新有变化的Real DOM。

Reconciliation

React之所以有这么好的渲染性能,主要是因为在UI变化的时候可以先比较Virtual DOM,只更新有变化的Real DOM,整个更新过程叫Reconciliation

Fiber

React 16重构了Reconciliation 实现,新框架叫Fiber,Facebook团结花了两年时间实现Fiber,核心优化就是使用Vitual Stack的概念。Fiber之前更新UI的时候是通过同步递归的方式遍历Virtual DOM树,整个过程是同步的,并且在遍历结束之前无法中断,这样在动画的时候就可能导致卡顿。Fiber使用Vitual Stack的概念,把同步递归操作分解成一个个异步、可中断的操作单元,从而解决卡顿问题,并且随时可以取消不需要的操作。

UI更新

React Native UI更新主要可以分为以下两个阶段:

  • Render
  • Commit

Render过程计算新的element树,render()方法在这个阶段调用的;Commit过程调用diff算法,更新实际发生变化的UI。
Render 阶段核心方法调用顺序:

  • componentWillReceiveProps
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render()
  • reconcileChildren()

Commit 阶段核心方法调用顺序:

  • getSnapshotBeforeUpdate
  • diff
  • updateView
  • componentDidUpdate

componentWillReceiveProps和componentWillUpdate已经废弃,不推荐使用了,使用getDerivedStateFromProps和getSnapshotBeforeUpdate代替
updateView方法调用RCTUIManager.updateView更新Native View。

Reconciliation源码分析

接下来以setState方法为切入点分析,分析React Native Reconciliation过程,主要分析UI更新过程,并不深入Fiber细节。

setState

    _callback() {
      console.log('callback');
    }
  
    _onPress1() {
        this.setState(previousState => (
            { value:  previousState.value+1}
          ), this._callback.bind(this));
  }

上面代码是React Native上更新UI的最常用方法,我们知道setState是异步调用的,但state是什么时机更新?callback又什么时机调用呢?又是怎么触发Virtual DOM树和UI更新的呢?

//react.development.js:333
Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
//ReactNativeRender-dev.js:8456
var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function(inst, payload, callback) {
    var fiber = get$1(inst);
    var currentTime = requestCurrentTime();
    var expirationTime = computeExpirationForFiber(currentTime, fiber);

    var update = createUpdate(expirationTime);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      {
        warnOnInvalidCallback(callback, "setState");
      }
      update.callback = callback;
    }

    flushPassiveEffects();
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  }
  ...
}

React可以用在Web、Node、React Native,底层updater指向具体实现,React Native上就是classComponentUpdater。
可以看到setState最终会创建一个update结构,其中payload就是更新state的匿名方法,然后插入队列,payload和callback将在后面异步执行。

element树更新

前文说过Render过程计算新的element树,render()方法在这个阶段调用的,先看一下函数调用栈:

Render阶段函数调用栈

通过函数名可以猜测更新过程会把异步处理批量更新,这样可以提高性能,接下来分析Render过程核心方法。

performWorkOnRoot

//ReactNativeRender-dev.js:17168
function performWorkOnRoot(root, expirationTime, isYieldy) {
  // Check if this is async work or sync/expired work.
  if (!isYieldy) {
    // Flush work without yielding.
    // TODO: Non-yieldy work does not necessarily imply expired work. A renderer
    // may want to perform some work without yielding, but also without
    // requiring the root to complete (by triggering placeholders).

    var finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      root.finishedWork = null;
      // If this root previously suspended, clear its existing timeout, since
      // we're about to try rendering again.
      var timeoutHandle = root.timeoutHandle;
      if (timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
        cancelTimeout(timeoutHandle);
      }
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  }
  ...
}

performWorkOnRoot是UI更新的入口方法,React Native上isYieldy直接传的false。每次更新的时候都会从最顶端的节点开始计算新的element树,不管是哪个节点调的setState,但没变化的节点并不会重新计算,而是直接重用。但如果父节点发生变化,则所有字节的都会进行重新计算,而不管子节点是否变化,除非子节点shouldComponentUpdate返回false,或者子节点是PureReactComponent。
renderRoot就是Render阶段入口方法,completeRoot则是Commit阶段入口方法。

workLoop

//ReactNativeRender-dev.js:16111
function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until there's a higher priority event
    while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

workLoop方法就是遍历整颗element树,React 16重构了Reconciliation 实现,新框架叫Fiber,Fiber使用Vitual Stack的概念,把同步递归操作分解成一个个异步、可中断的操作单元,每个操作单元就是一个节点计算过程。
performUnitOfWork就是具体节点计算,每次执行完会通过深度优先返回下一个需要执行的节点,这样就可以遍历整个节点树了。

performUnitOfWork

//ReactNativeRender-dev.js:16049
function performUnitOfWork(workInProgress) {
  // The current, flushed, state of this fiber is the alternate.
  // Ideally nothing should rely on this, but relying on it here
  // means that we don't need an additional field on the work in
  // progress.
  var current$$1 = workInProgress.alternate;
...
  var next = void 0;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }
    next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
...
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }
  ReactCurrentOwner$2.current = null;
  return next;
}

performUnitOfWork主要就是调用beginWork方法,然后更新props。

beginWork
//ReactNativeRender-dev.js:12601
function beginWork(current$$1, workInProgress, renderExpirationTime) {
  var updateExpirationTime = workInProgress.expirationTime;

  if (current$$1 !== null) {
    var oldProps = current$$1.memoizedProps;
    var newProps = workInProgress.pendingProps;
    if (
      oldProps === newProps &&
      !hasContextChanged() &&
      updateExpirationTime < renderExpirationTime
    ) {
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        ...
        case ClassComponent: {
          var Component = workInProgress.type;
          if (isContextProvider(Component)) {
            pushContextProvider(workInProgress);
          }
          break;
        }
        ...
      return bailoutOnAlreadyFinishedWork(
        current$$1,
        workInProgress,
        renderExpirationTime
      );
    }
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    ...
    case ClassComponent: {
      var _Component2 = workInProgress.type;
      var _unresolvedProps = workInProgress.pendingProps;
      var _resolvedProps =
        workInProgress.elementType === _Component2
          ? _unresolvedProps
          : resolveDefaultProps(_Component2, _unresolvedProps);
      return updateClassComponent(
        current$$1,
        workInProgress,
        _Component2,
        _resolvedProps,
        renderExpirationTime
      );
    }
    ...
  }
}

可以看到beginWork主要是两个分支,每个分支都是一个很大switch case语句,第一个分支处理节点没变化的情况,这个时候不会进行计算,第二个分支处理节点发生变化的情况。每个switch case处理不同类型的节点,这里只分析ClassComponent类型。
最终会调用updateClassComponent方法更新发生变化的节点。

updateClassComponent

//ReactNativeRender-dev.js:11457
function updateClassComponent(
  current$$1,
  workInProgress,
  Component,
  nextProps,
  renderExpirationTime
) {
  {
   ...
  var instance = workInProgress.stateNode;
  var shouldUpdate = void 0;
  if (instance === null) {
    if (current$$1 !== 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$$1.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$$1 === null) {
    // In a resume, we'll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime
    );
  } else {
    shouldUpdate = updateClassInstance(
      current$$1,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime
    );
  }
  var nextUnitOfWork = finishClassComponent(
    current$$1,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime
  );
 ...
  return nextUnitOfWork;
}

updateClassComponent会判断Component是否实例化,如果没有实例化的话会创建Component实例,这也是前文说的React引擎自动创建Instance的时机,如果已经实例化则调用updateClassInstance更新实例,updateClassInstance会返回该实例是否真正需要更新,并更新props和state。最后会调用finishClassComponent更新element并返回下一个计算单元。

updateClassInstance

//ReactNativeRender-dev.js:9282
// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
  current,
  workInProgress,
  ctor,
  newProps,
  renderExpirationTime
) {
  var instance = workInProgress.stateNode;

  var oldProps = workInProgress.memoizedProps;
  instance.props =
    workInProgress.type === workInProgress.elementType
      ? oldProps
      : resolveDefaultProps(workInProgress.type, oldProps);

  var oldContext = instance.context;
  var contextType = ctor.contextType;
  var nextContext = void 0;
  if (typeof contextType === "object" && contextType !== null) {
    nextContext = readContext$1(contextType);
  } else {
    var nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }

  var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  var hasNewLifecycles =
    typeof getDerivedStateFromProps === "function" ||
    typeof instance.getSnapshotBeforeUpdate === "function";

  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the "current". However,
  // during componentDidUpdate we pass the "current" props.

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
      typeof instance.componentWillReceiveProps === "function")
  ) {
    if (oldProps !== newProps || oldContext !== nextContext) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext
      );
    }
  }

  resetHasForceUpdateBeforeProcessing();

//调用setState匿名方法更新state
  var oldState = workInProgress.memoizedState;
  var newState = (instance.state = oldState);
  var updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime
    );
    newState = workInProgress.memoizedState;
  }

  if (
    oldProps === newProps &&
    oldState === newState &&
    !hasContextChanged() &&
    !checkHasForceUpdateAfterProcessing()
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === "function") {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Snapshot;
      }
    }
    return false;
  }

  if (typeof getDerivedStateFromProps === "function") {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps
    );
    newState = workInProgress.memoizedState;
  }

  var shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext
    );

  if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillUpdate === "function" ||
        typeof instance.componentWillUpdate === "function")
    ) {
      startPhaseTimer(workInProgress, "componentWillUpdate");
      if (typeof instance.componentWillUpdate === "function") {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === "function") {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
      stopPhaseTimer();
    }
    if (typeof instance.componentDidUpdate === "function") {
      workInProgress.effectTag |= Update;
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      workInProgress.effectTag |= Snapshot;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === "function") {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Snapshot;
      }
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized props/state to indicate that this work can be reused.
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

  // Update the existing instance's state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;

  return shouldUpdate;
}

updateClassInstance判断节点是否需要跟新,调用以下life-cycles方法:

  • componentWillReceiveProps
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate

前文已经知道调用setState时会创建一个update结构,updateClassInstance 会调用processUpdateQueue方法计算新的state,processUpdateQueue方法里面会调用setState传的匿名函数

//ReactNativeRender-dev.js:8517
function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext
) {
  var instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === "function") {
    startPhaseTimer(workInProgress, "shouldComponentUpdate");
    var shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext
    );
    stopPhaseTimer();
    {
      !(shouldUpdate !== undefined)
        ? warningWithoutStack$1(
            false,
            "%s.shouldComponentUpdate(): Returned undefined instead of a " +
              "boolean value. Make sure to return true or false.",
            getComponentName(ctor) || "Component"
          )
        : void 0;
    }
    return shouldUpdate;
  }
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }
  return true;
}

updateClassInstance调用checkShouldComponentUpdate判断是否需要更新,checkShouldComponentUpdate实现比较简单,

  1. 如果Component实现shouldComponentUpdate方法,则调用shouldComponentUpdate;
  2. 如果是PureReactComponent,则调用shallowEqual比较props和state是否变化;
  3. 否则返回true

这里要注意checkShouldComponentUpdate默认返回true,所以只要父节点更新,默认就会更新所有子节点,这就是为什么可以通过shouldComponentUpdate返回false或使用PureReactComponent来提升性能。

finishClassComponent

Render阶段最后就是调用finishClassComponent方法计算新的element,并且调用reconcileChildren遍历子节点,具体代码如下:

//ReactNativeRender-dev.js:11562
function finishClassComponent(
  current$$1,
  workInProgress,
  Component,
  shouldUpdate,
  hasContext,
  renderExpirationTime
) {
...
  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }

    return bailoutOnAlreadyFinishedWork(
      current$$1,
      workInProgress,
      renderExpirationTime
    );
  }

  var instance = workInProgress.stateNode;

  // Rerender
  ReactCurrentOwner$3.current = workInProgress;
  var nextChildren = void 0;
  ...
      setCurrentPhase("render");
      nextChildren = instance.render();
 ...
    reconcileChildren(
      current$$1,
      workInProgress,
      nextChildren,
      renderExpirationTime
    );

  // Memoize state using the values we just used to render.
  // TODO: Restructure so we never read values from the instance.
  workInProgress.memoizedState = instance.state;
  ...
  return workInProgress.child;
}

如果shouldUpdate为false,则直接重用现有节点,跟beginWork处理没变化的节点一样。
如果shouldUpdate为true,则调用render方法计算新的element,然后调用reconcileChildren遍历子节点。

completeUnitOfWork

在遍历到叶子节点后performUnitOfWork会调用completeUnitOfWork

//ReactNativeRender-dev.js:16101
function performUnitOfWork(workInProgress) {
  ...
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }
  ...
}

completeUnitOfWork调用completeWork标记需要更新的节点,如果有兄弟节点则返回兄弟节点,继续遍历兄弟节点,否则标记父节点。

Commit 阶段源码分析

前文分析来Render阶段核心方法,Render阶段会生成一颗新的element树,并且生成一个Effect list,Effect list是一个线性列表,包含真正需要更新的操作,Commit 阶段则通过Effect list更新具体的UI,首先看下Commit 阶段的函数调用栈:

Commit阶段函数调用栈

commitAllHostEffects

//ReactNativeRender-dev.js:15349
function commitAllHostEffects() {
  while (nextEffect !== null) {
    {
      setCurrentFiber(nextEffect);
    }
    recordEffect();

    var effectTag = nextEffect.effectTag;

    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    if (effectTag & Ref) {
      var current$$1 = nextEffect.alternate;
      if (current$$1 !== null) {
        commitDetachRef(current$$1);
      }
    }

    // The following switch statement is only concerned about placement,
    // updates, and deletions. To avoid needing to add a case for every
    // possible bitmap value, we remove the secondary effects from the
    // effect tag and switch on that value.
    var primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement: {
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        // TODO: findDOMNode doesn't rely on this any more but isMounted
        // does and isMounted is deprecated anyway so we should be able
        // to kill this.
        nextEffect.effectTag &= ~Placement;
        break;
      }
      case PlacementAndUpdate: {
        // Placement
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        nextEffect.effectTag &= ~Placement;

        // Update
        var _current = nextEffect.alternate;
        commitWork(_current, nextEffect);
        break;
      }
      case Update: {
        var _current2 = nextEffect.alternate;
        commitWork(_current2, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
    nextEffect = nextEffect.nextEffect;
  }

  {
    resetCurrentFiber();
  }
}

commitAllHostEffects源码比较好理解,循环执行effect操作,effect操作可能是替换、删除、更新等

commitWork

更新操作会调用commitWork,源码如下:

//ReactNativeRender-dev.js:14628
function commitWork(current$$1, finishedWork) {
  ...
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      // Note: We currently never use MountMutation, but useLayout uses
      // UnmountMutation.
      commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
      return;
    }
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      var instance = finishedWork.stateNode;
      if (instance != null) {
        // Commit the work prepared earlier.
        var newProps = finishedWork.memoizedProps;
        // For hydration we reuse the update path but we treat the oldProps
        // as the newProps. The updatePayload will contain the real change in
        // this case.
        var oldProps =
          current$$1 !== null ? current$$1.memoizedProps : newProps;
        var type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        var updatePayload = finishedWork.updateQueue;
        finishedWork.updateQueue = null;
        if (updatePayload !== null) {
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork
          );
        }
      }
      return;
    }
    case HostText: {
      invariant(
        finishedWork.stateNode !== null,
        "This should have a text node initialized. This error is likely " +
          "caused by a bug in React. Please file an issue."
      );
      var textInstance = finishedWork.stateNode;
      var newText = finishedWork.memoizedProps;
      // For hydration we reuse the update path but we treat the oldProps
      // as the newProps. The updatePayload will contain the real change in
      // this case.
      var oldText = current$$1 !== null ? current$$1.memoizedProps : newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    ...
  }
}

React Native上Component都是由View、Text等基础Component组成的,所以最终实际更新的是View、Text等基础Component,最后会调用commitUpdate和commitTextUpdate完成实际的更新操作。

commitUpdate

//ReactNativeRender-dev.js:4153
function commitUpdate(
  instance,
  updatePayloadTODO,
  type,
  oldProps,
  newProps,
  internalInstanceHandle
) {
  var viewConfig = instance.viewConfig;

  updateFiberProps(instance._nativeTag, newProps);

  var updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);

  // Avoid the overhead of bridge calls if there's no update.
  // This is an expensive no-op for Android, and causes an unnecessary
  // view invalidation for certain components (eg RCTTextInput) on iOS.
  if (updatePayload != null) {
    UIManager.updateView(
      instance._nativeTag, // reactTag
      viewConfig.uiViewClassName, // viewName
      updatePayload // props
    );
  }
}

commitUpdate源码比较好理解,首先调用diff判断是否需要更新,如果需要更新的话调用UIManager.updateView更新Native UI,其中UIManager.updateView是Native 暴露的module。diff方法主要是比较props是否变化:

//ReactNativeRender-dev.js:3638
function diff(prevProps, nextProps, validAttributes) {
  return diffProperties(
    null, // updatePayload
    prevProps,
    nextProps,
    validAttributes
  );
}

commitTextUpdate

//ReactNativeRender-dev.js:14659
//ReactNativeRender-dev.js:4145
function commitTextUpdate(textInstance, oldText, newText) {
  UIManager.updateView(
    textInstance, // reactTag
    "RCTRawText", // viewName
    { text: newText } // props
  );
}

commitTextUpdate实现比较简单,因为completeWork方法标记TextComponent的时候已经判断了text是否变化,所以直接调用UIManager.updateView。

life-cycles方法调用

在更新完UI后将调用componentDidUpdate方法和setState callback方法,具体调用栈如下:


componentDidUpdate调用栈
setState callback调用栈

总结

React Native Reconciliation过程比较复杂,Fiber框架把递归操作分解成一个个异步、可中断的操作单元后进一步复杂度。
Reconciliation主要可以分为以下两个阶段:

  • Render
  • Commit

Render阶段从根节点开始遍历element树,对于不需要更新的节点直接重用fiber node,对于需要更新的节点,调用life-cycle方法,然后调用render方法计算新的element,最后调用reconcileChildren遍历子节点。
Render阶段还会标记更新,并且生成一个Effect list,Effect list是一个线性列表,包含真正需要更新的操作。
Commit 阶段通过Effect list更新具体的UI,这阶段会调用diff,然后调用UIManager.updateView更新Native View,最后调用componentDidUpdate方法和setState callback方法。

引用

Inside Fiber: in-depth overview of the new reconciliation algorithm in React
React Fiber Architecture
React Components, Elements, and Instances
Reconciliation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 1 基本概念:Component(组件)、instance(组件实例)、 element、jsx、dom Comp...
    水落斜阳阅读 1,531评论 1 3
  • react刚刚推出的时候,讲react优势搜索结果是几十页。 现在,react已经慢慢退火,该用用react技术栈...
    zhoulujun阅读 5,151评论 0 11
  • 一、React的工作原理 1、UI = f(data){} UI 就是界面 ,这个界面就是函数执行的结果,是按照函...
    it筱竹阅读 1,151评论 0 4
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,770评论 1 18
  • 黑天鹅事件是指非常难以预测,且不寻常的事件,通常会引起市场连锁负面反应甚至颠覆,(比如上个月的九寨沟地震)黑天鹅”...
    叶梓雯阅读 656评论 1 2