React Native源码分析——Virtual DOM

作者: AlexTing杂货店 | 来源:发表于2019-05-12 22:18 被阅读1次

    本文基于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

    相关文章

      网友评论

        本文标题:React Native源码分析——Virtual DOM

        本文链接:https://www.haomeiwen.com/subject/qjbjgqtx.html