【问题标题】:React componentDidMount timingReact componentDidMount 计时
【发布时间】:2018-09-05 12:40:03
【问题描述】:

componentDidMount 生命周期方法是否独立于兄弟组件?从下面的示例看来,它似乎只有在所有同级组件都已安装后才被调用。

假设我们有一个渲染 2 个子组件的顶级组件,第一个是简单的 render(),另一个是相对较慢的 render()

要复制的样本:https://codesandbox.io/s/j43klml9py?expanddevtools=1

TL;DR:


class SlowComponent extends Component {
  componentDidMount() {
    // perf mark
  }

  render() {
    // Simulate slow render
    // Takes 50ms
    return <h3>Slow component</h3>;
  }
}

class FastComponent extends Component {
  componentDidMount() {
    // perf mark
  }

  render() {
    return <h3>Fast component</h3>;
  }
}

class App extends Component {
  constructor(props) {
    super(props);

    // perf mark start
  }

  componentDidMount() {
    // perf mark
    // measure all marks and print
  }

  render() {
    return (
      <div>
        <FastComponent />
        <SlowComponent />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

我希望componentDidMount 的时间是这样的:

  1. FastComponent 10 毫秒
  2. SlowComponent 50 毫秒
  3. App 52 毫秒

但实际上我得到的是同时触发快速和慢速组件componentDidMount 回调,即

  1. FastComponent 50 毫秒
  2. SlowComponent 51 毫秒
  3. App 52 毫秒

当前示例和重现代码使用挂载回调,但同样适用于 componentDidUpdate

完整来源:

import ReactDOM from "react-dom";
import React, { Component } from "react";

class SlowComponent extends Component {
  componentDidMount() {
    performance.mark("slow-mounted");
  }

  render() {
    // Simulate slow render
    for (var i = 0; i < 10000; i++) {
      for (var j = 0; j < 100; j++) {
        const b = JSON.parse(
          JSON.stringify({
            test: "test" + i,
            test1: i * i * i
          })
        );
      }
    }
    return <h3>Slow component</h3>;
  }
}

class FastComponent extends Component {
  componentDidMount() {
    performance.mark("fast-mounted");
  }

  render() {
    return <h3>Fast component</h3>;
  }
}

class App extends Component {
  constructor(props) {
    super(props);

    performance.mark("init");
  }

  componentDidMount() {
    performance.mark("app-mounted");

    performance.measure("slow", "init", "slow-mounted");
    performance.measure("fast", "init", "fast-mounted");
    performance.measure("app", "init", "app-mounted");

    console.clear();

    console.log(
      "slow",
      Math.round(performance.getEntriesByName("slow")[0].duration)
    );
    console.log(
      "fast",
      Math.round(performance.getEntriesByName("fast")[0].duration)
    );
    console.log(
      "app",
      Math.round(performance.getEntriesByName("app")[0].duration)
    );

    performance.clearMarks();
    performance.clearMeasures();
  }

  render() {
    return (
      <div>
        <h1>Demo</h1>
        <FastComponent />
        <SlowComponent />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

【问题讨论】:

标签: javascript reactjs performance


【解决方案1】:

我假设您使用的是最新的 React 版本 (16.5.0)。

假设两个组件,一个慢速和一个快速封装在父组件中,执行流程如下:

  1. 父组件调用其UNSAFE_componentWillMount方法
  2. 按照在其渲染方法中定义的顺序迭代所有子级并调用它们的UNSAFE_componentWillMount 方法
  3. 迭代所有子节点并调用他们的render 方法(在这里你的慢组件昂贵的渲染方法将启动)
  4. 迭代所有孩子并调用他们的componentDidMount方法
  5. 父组件调用其componentDidMount方法

整个过程是同步完成的。

回到您的示例,这就是为什么您看到两个组件的时间几乎相同的原因,因为一旦所有组件的工作完成,就会调用所有组件生命周期方法。

一般来说,React 在概念上由 2 个阶段组成,“渲染”阶段和“提交”阶段。 “提交”阶段是 React 应用任何更改的时间,在您的示例中,生命周期方法 componentDidMount 在此阶段被调用。

您可以通过调试 React 并查看以下堆栈跟踪来遵循此流程:

componentDidMount
commitLifeCycles
commitAllLifeCycles
callCallback
invokeGuardedCallbackDev
invokeGuardedCallback
commitRoot

【讨论】:

    【解决方案2】:

    简单的答案

    所有组件在所有渲染完成后都会触发componentDidMount 方法。因为在将元素挂载到 DOM 之前,我们需要创建它们。

    【讨论】:

      【解决方案3】:

      根据您的生命周期顺序,您的 componentDidMount 将在您的组件挂载后被调用(顾名思义)。这将是在 DOM 被挂载之后,这样做会调用你的渲染。

      现在假设如果没有 I/O,即在任何地方的子组件中发生异步事件。

      (即使发生任何异步事件,函数也会被执行,但调用堆栈将在事件循环中保持执行..但这是一个单独的讨论)

      -- Parent
        -- Child-1
        -- Child-2
      

      执行顺序

      Parent constructor()
      Child 1 constructor()
      Child 1 render()
      Child 1 componentDidMount()
      Child 2 constructor()
      Child 2 render()
      Child 2 componentDidMount()
      Parent componentDidMount()
      

      根据 ReactJS 文档,按顺序安装触发器是

      • 构造函数()
      • 静态 getDerivedStateFromProps()
      • 渲染()
      • componentDidMount()

      React 文档 Mounting React Docs 中的安装顺序参考

      在旁注中,另一个关于执行顺序的详细阅读。 Understanding React — Component life-cycle

      【讨论】:

        【解决方案4】:

        是的,ComponentDidMount 生命周期方法独立于兄弟组件。这取决于您的渲染如何花费时间,因为它在您的渲染完成后调用。但是组件的安装顺序与它们在父组件中的顺序相同。例如 - 在上面的示例中,首先安装快速组件,然后安装第二个组件。

        而且你无法计算一个函数需要多少时间来迭代一个循环。所以在这里你假设计算慢组件的 ComponentDidMount 生命周期方法的时间是错误的。在没有分析的情况下,您不能期望基于迭代次数的特定时间估计。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-10-08
          • 2023-03-26
          • 1970-01-01
          相关资源
          最近更新 更多