React16.2的fiber架构详解(3)

React16是否能异步渲染,在于内部一个变量。在开始之前,我们需要准备一个例子。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>by 司徒正美</title>
    <meta name="viewport" content="width=device-width">
    <!-- <script type='text/javascript' src="./src/React2.js"></script>-->
    <script type='text/javascript' src="./test/react.js"></script>
    <script type='text/javascript' src="./test/react-dom.js"></script>
    <script src="test/babel.js"></script>
</head>

<body>
    <div id="test"></div>
    <div id="content"></div>
</body>
<script type="text/babel">
    var container = document.getElementById("test");
    class Root extends React.Component{
        constructor(props){
            super(props)
            this.props = props
        }
        render(){
            console.log("Root render..", Date.now())
            return <div><A /></div>
        }
    }
    class A extends React.Component{
        constructor(props){
            super(props)
            this.props = props
        }
        render(){
            console.log("A render..", Date.now())
            return <div>{1111}</div>
        }
    }
    ReactDOM.render(<Root />, container, function(){
        console.log("callback",Date.now())
    })
   console.log("end", Date.now())
</script>

</html>

当中的react.js与react-dom.js是React16.4beta,大家可以在bootcdn上下载。

前文已经提过,ReactDOM.render/hydrate/unstable_renderSubtreeIntoContainer/unmountComponentAtNode都是legacyRenderSubtreeIntoContainer方法的加壳方法。

《React16.2的fiber架构详解(3)》

legacyRenderSubtreeIntoContainer里面调用legacyCreateRootFromDOMContainer创建一个ReactRoot对象,然后再调用其render或legacy_renderSubtreeIntoContainer方法

//by 司徒正美
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
  var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // First clear any existing content.
  if (!shouldHydrate) {
    var warned = false;
    var rootSibling = void 0;
    while (rootSibling = container.lastChild) {
      {
        if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
          warned = true;
          warning_1(false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.');
        }
      }
      container.removeChild(rootSibling);
    }
  }
  {
    if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
      warnedAboutHydrateAPI = true;
      lowPriorityWarning$1(false, 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v17. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.');
    }
  }
  // Legacy roots are not async by default.
  var isAsync = false;
  console.log("new ReactRoot",container, isAsync, shouldHydrate)
  return new ReactRoot(container, isAsync, shouldHydrate);
}

留意里面的isAsync,是写死的,强制使用同步,我们可以改一改,就能使用异步

var isAsync = true;

本节的内容就准备读如何
异步渲染

ReactRoot之前已经说过,再贴一下源码。

《React16.2的fiber架构详解(3)》

从DOMRenderer.updateContainer到达updateContainerAtExpirationTime到达scheduleWork到达scheduleWork到达scheduleWorkImpl到达requestWork,我们一路加点注释

下面是scheduleWorkImpl的代码:

《React16.2的fiber架构详解(3)》

requestWork里面才进行同步异步逻辑分家

《React16.2的fiber架构详解(3)》

scheduleCallbackWithExpiration是干了什么呢?它会判定是否工作与不干作,工作就是重新计算过期时间,然后执行scheduleDeferredCallback方法。scheduleDeferredCallback有两个参数,第一个是回调函数,第二个是对象,里面的timeout决定它在执行scheduleDeferredCallback最迟多少ms才执行。

scheduleDeferredCallback是何方神圣呢?它是大名鼎鼎的requestIdleCallback

《React16.2的fiber架构详解(3)》

《React16.2的fiber架构详解(3)》

requestIdleCallback的语法如下:

《React16.2的fiber架构详解(3)》

performAsyncWork与performSyncWork也是一对兄弟。

//by 司徒正美

  function performAsyncWork(dl) {
    performWork(NoWork, true, dl);
  }

  function performSyncWork() {
    performWork(Sync, false, null);
  }

performWork的源码

function performWork(minExpirationTime, isAsync, dl) {
    deadline = dl;
    // Keep working on roots until there's no more work, or until the we reach
    // the deadline.
    findHighestPriorityRoot();
    if (enableUserTimingAPI && deadline !== null) {
      var didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
      stopRequestCallbackTimer(didExpire);
    }
    if (isAsync) {
      while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && 
        (minExpirationTime === NoWork ||
         minExpirationTime >= nextFlushedExpirationTime) && (!deadlineDidExpire || recalculateCurrentTime() >= nextFlushedExpirationTime)) {
        performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, !deadlineDidExpire);
        findHighestPriorityRoot();
      }
    } else {
      while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && 
        (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
        performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
        findHighestPriorityRoot();
      }
    }
    // We're done flushing work. Either we ran out of time in this callback,
    // or there's no more work left with sufficient priority.
    // If we're inside a callback, set this to false since we just completed it.
    if (deadline !== null) {
      callbackExpirationTime = NoWork;
      callbackID = -1;
    }
    // If there's work left over, schedule a new callback.
    if (nextFlushedExpirationTime !== NoWork) {
      scheduleCallbackWithExpiration(nextFlushedExpirationTime);
    }
    // Clean-up.
    deadline = null;
    deadlineDidExpire = false;

    finishRendering();
  }

下面是同步与异步的执行情况

《React16.2的fiber架构详解(3)》

《React16.2的fiber架构详解(3)》

但异步模式为什么会调用两次render呢?估计这还在测试阶段,许多BUG。我们追踪到finishClassComponent方法,看到它的render方法:

《React16.2的fiber架构详解(3)》

我们再改一下Root组件的代码,添加一个componentDidMount.

class Root extends React.Component{
        constructor(props){
            super(props)
            this.props = props
            this.state = {
                x: 1
            }
        }
        render(){
            console.log("Root render..", Date.now())
            return <h1><A x={this.state.x} /></h1>
        }
        componentDidMount(){
            console.log("Root componentDidMount")
            this.setState({
                x: 2
            })
        }
    }
     class A extends React.Component{
        constructor(props){
            super(props)
            this.props = props
            this.state = {
                text: props.x
            }
        }
        componentWillReceiveProps(p){
            this.setState({
               text: p.x
            })
        }
        render(){
            console.log("A render..", Date.now())
            return <h2>{this.state.text}</h2>
        }
    }

《React16.2的fiber架构详解(3)》

我们再看一下fiber树。fiber有许多种类型,但主要是四种, ClassFiber, FunctionFiber, HostComponentFiber, HostTextFiber,分别对应原来的类组件,无状态组件,元素虚拟节点,文本虚拟节点。Fiber表面上比React15的虚拟DOM多了一些属性,如parent, child, sibling。换言之,fiber可以像真实DOM一样上下右遍历(没有左)。

《React16.2的fiber架构详解(3)》

React16的源码里面有两个方法beginWorkfinishWork重要方法。beginWork,就是从一个Fiber开始,初始化它的state(如果fiber.type为函数,则new 实例或一个类似类似的东西,如果type为标签名,则创建元素节点或文本节点),并遍历它的第一重孩子,让孩子们加上parent,sibling(注意这时孩子没有stateNode)。最后返回第一个孩子,作为刚才fiber的child。 然后对这个child再执行beginWork操作。

beginWork的过程中,确到组件,需要用到context,context是来自contextStack。这是一个全局对象。在顶层,默认会push一个空对象。然后到达某个组件时,peek一下(不使用pop方法)。 如果这个组件有getChildContext方法呢,这时就会产生一个新context, push进去。

有些fiber是没有孩子的,比如说文本节点,或一些元素节点,这样它开始 finishWork操作,找它的sibling,对sibling进行beginWork操作,没有sibling就往上找,这时就会再次访问到某个组件,如果这个组件有getChildContext,于是就pop一下。

finishWork还有一个重要任务,就是收集DOM操作指令,一开始所有fiber的effects都PLACEMENT,叫做置换,其实相当于append。每次往上找时,父fiber就把它所有孩子的effect收集一下,最后到顶层Root组件时, contextStack为空,而effects则装得满满的,然后交给commintAllWork执行这些指令。

fiber架构是很好地解决context的往下传送问题。

    原文作者:司徒正美
    原文地址: https://segmentfault.com/a/1190000013722981
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞