ホームページ  >  記事  >  ウェブフロントエンド  >  React コンポーネントのパフォーマンスの最適化にはどのような側面がありますか?

React コンポーネントのパフォーマンスの最適化にはどのような側面がありますか?

php中世界最好的语言
php中世界最好的语言オリジナル
2018-04-12 14:04:101598ブラウズ

今回は、React コンポーネントの パフォーマンス最適化 とはどのような点にあるのか、また React コンポーネントのパフォーマンスを最適化する際の注意点について、実際の事例を交えてご紹介します。

Gartner: 「小さなパフォーマンスの最適化を無視することを忘れるべきです。97% の場合、時期尚早な最適化が諸悪の根源であると言えます。そして、最も効果的なコードの残り 3% に注意を払う必要があります。」パフォーマンスに重大な影響を及ぼします。」 全体的なパフォーマンスを大幅に改善しないコードにパフォーマンスの最適化エネルギーを無駄にしないでください。パフォーマンスに重大な影響を与える部分を最適化するのに早すぎるということはありません。なぜなら、パフォーマンスに影響を与える最も重要な部分は、多くの場合、ソリューションのコアに関係し、アーキテクチャ全体を決定します。将来変更が必要になった場合、アーキテクチャはさらに複雑になるからです。

1. 単一 React コンポーネントのパフォーマンスの最適化

React はレンダリング パフォーマンスを向上させるために Virtual DOM を使用します。各ページの更新ではほとんどのコンポーネントが再レンダリングされますが、以前のレンダリング コンテンツをすべて破棄して最初からやり直すことはありません。これが、React がデフォルトで非常に高速にレンダリングされる秘密です。

ただし、仮想 DOM では各 DOM 操作の量を最小限に抑えることができますが、仮想 DOM の計算と比較は依然として複雑なプロセスです

。 もちろん、仮想 DOM の計算を開始する前にレンダリング結果が変わらないと判断できれば、仮想 DOM の計算と比較を行う必要がなく、速度が速くなります。

2. shouldComponentUpdate のデフォルトの実装

仮想 DOM の計算を開始する前にコンポーネントのレンダリングを防止し、レンダリング結果が変わらないと判断してパフォーマンスを向上させることができるため、当然のことながら shouldComponentUpdate(nextProp, nextState) を使用することを考えることになります

shouldComponentUpdate 関数は、「いつ再レンダリングする必要がないのか」を判断するために render 関数の前に呼び出されます。 つまり、更新を続行するかどうかを決定するためにブール値が返されます。デフォルト値が true の場合、更新は中断されます。 このうち、nextProps は、この更新に渡される props です。このコンポーネントでは、レンダリング コンテンツに影響する props のみが完了し、これら 2 つの props が変更されていない限り、 shouldComponentUpdate は false を返し、不要な更新を防ぐことができます

。 ただし、上記の比較はあくまで「浅い比較」であり、型が基本型であれば、値が同じであれば「浅い比較」となります

。 私もこの 2 つは同じだと考えます:

では、プロップのタイプが複雑なオブジェクトの場合はどうなるでしょうか?

複雑なオブジェクトの場合、「浅い比較」メソッドは 2 つのプロパティが同じオブジェクトへの参照であるかどうかのみをチェックします。そうでない場合は、オブジェクトの内容がまったく同じであっても、それらは 2 つの異なるプロパティとみなされます。次に、「詳細比較」を使用します。ただし、オブジェクトの構造は予測できません。各フィールドに対して「詳細比較」を再帰的に実行すると、コードが複雑になるだけでなく、パフォーマンスの問題が発生する可能性があります。

したがって、前後のオブジェクト タイプの props が同じであることを確認したい場合は、props が同じ

JavaScript オブジェクト

を指していることを確認する必要があります:

shouldComponentUpdate(nextProp,nextState){
  return (nextProp.completed !== this.props.completed) ||
    (nextProp.text !== this.props.text)
}

上記の受け渡しメソッドの使用を回避するには、レンダリングごとに {color: "red"} オブジェクトを再作成する必要があり、参照アドレスは毎回異なり、その結果、毎回異なる styleProp が生成されます。

りー

'singleton pattern' を使用して、渡された styleProp が同じオブジェクトを指すようにします

それが関数だったらどうなるでしょうか?

りー

上記の関数転送モードの使用は避けるべきです。ここで割り当てられているのは匿名関数であり、割り当て中に生成されるため、レンダリングするたびに新しい関数が生成されることになり、これが問題となるからです。

渡される小道具がたくさんある場合はどうすればよいでしょうか?

そうですね~~React-Redux を使用している場合は、 shouldComponentUpdate のデフォルト実装があります。

3. 複数の React コンポーネントのパフォーマンスの最適化

React コンポーネントがロード、更新、アンロードされると、コンポーネントの一連の

lifecycle

関数が呼び出されます。ただし、これらのライフサイクル関数は特定の React コンポーネント関数用であり、アプリケーションでは多数の React コンポーネントが上から下まで結合されており、それらの間のレンダリング プロセスはより複雑になります。

同样一个组件的渲染过程也要考虑三个过程:装载阶段、更新阶段、卸载阶段

对于装载阶段,组件无论如何都要彻底渲染一次,从这个React组件往下的所有子组件,都要经历一遍React组件的装载生命
周期,所以并没有多少优化的事情可做。

对于卸载阶段,只有一个生命周期函数componentWillUnmount,这个函数只是清理componentDidMount添加的事件处理监听等收尾工作,所以,也没有什么可优化的空间;

4. React更新阶段的调和(Reconciliation)过程

在组件更新过程,会构建更新Virtual DOM,并将其与之前的Virtual DOM进行比较,从而找出不同之处,使用最少的DOM操作进行更新

调和过程:即React更新中对Virtual DOM找不同的过程,通常对比两个N个节点的树形结构的算法,时间复杂度是O(n*3),如果直接

使用默认对比,节点过多的话,需要操作的数量太多,而React不可能采用这种算法;

React实际采用的算法时间复杂度是O(N)(时间复杂度只是对一个算法最好和最差情况下需要的指令操作数量级的估量)

React的Reconciliation算法并不复杂,首先检查两个树形的根节点的类型是否相同,根据相同或者不同有不同的处理方式:

节点类型不同的情况

如果树形节点的类型不相同,那就意味着改动很大,直接认为原来的那个树形结构已经没用,可以扔掉,需要从新构建DOM树,原有的树形上的React组件便会经历“卸载”的生命周期;

也就是说,对于Virtual DOM树这是一个“更新”过程,但是却可能引发这个树结构上某些组件的“装载”和“卸载”过程
如:

更新前

 <p>
  <Todos />
 </p>

我们想要更新成这样:

 <span>
   <Todos />
 </span>

>1. 那么在作比较的时候,一看根节点原来是p,新的是span,类型就不一样了,那么这个算法就废弃之前的p包括里面的所有子节点,从新构建一个span节点和子节点;

>2. 很明显因为根节点不同就将所有的子节点从新构建,这很浪费,但是为了避免O(N*3)的时间复杂度,React这能选择这种比较简单、快捷的方法;

>3. 所以,作为开发者,我们一定要避免上面的浪费的情景出现

节点类型相同的情况

如果两个节点类型相同时,对于DOM元素,React会保留节点对应的DOM元素,只对其节点的属性和内容做对比,然后只修改更新的部分;

节点类型相同时,对于React组件类型,React做得是根据新节点的props去更新节点的组件实例,引发组件的更新过程;

在处理完根节点对比后,React的算法会对根节点的每一个子节点重复一样的操作

多个相同子组件的情况

如果最初组件状态为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />
</ul>

更新后为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />
  <TodoItem text = "Third" />
</ul>

那么React会创建一个新的TodoItem组件实例,而前两个则进行正常的更新过程但是,如果更新后为:

<ul>
  <TodoItem text = "Zero" />
  <TodoItem text = "First" />
  <TodoItem text = "Second" />
</ul>

(这将暴露一个问题)理想处理方式是,创建一个新的TodoItem组件实例放在第一位,后两个进入自然更新过程
但是要让react按照这种方式,就必须找两个子组件的不同之处,而现有计算两个序列差异的算法时间是O(N*2),显然则
不适合对性能要求很高的场景,所以React选择了一个看起来很傻的办法,即挨个比较每个子组件;

React首先认为把text为First的组件的text改为Zero,Second的改为First,最后创建一个text为Second的组件,这样便会破原有的两个组件完成一个更新过程,并创建一个text为Second的新组件

这显然是一个浪费,React也意到,并提供了方克服,不过需要开发人员提供一点帮助,这就是key

Key的使用

key属性可以明确的告诉React每个组件的唯一标识

如果最初组件状态为:

<ul>
  <TodoItem key={1} text = "First" />
  <TodoItem key={2} text = "Second" />
</ul>

更新后为:

<ul>
  <TodoItem key={0} text = "Zero" />
  <TodoItem key={1} text = "First" />
  <TodoItem key={2} text = "Second" />
</ul>

因为有唯一标识key,React可以根据key值,知道现在的第二和第三个组件就是之前的第一和第二个,便用原来的props启动更新过程,这样shouldComponentUpdate就会发生作用,避免无谓的更新;

注意:因为作为组件的唯一标识,所以key必须唯一,且不可变

下面的代码是错误的例子:

<ul>
  todos.map((item,index) => {
      <TodoItem
        key={index}
        text={item.text}
      />
    })
</ul>

使用数组下标作为key值,看起来唯一,但不稳定,因为随着todos数组值的不同,同样一个组件实例在不同的更新过程中数组的下标完全可能不同,把下标当做可以就会让React乱套,记住key不仅要唯一还要确保稳定不可变

需要注意:虽然key是一个prop,但是接受key的组件不能读取key的值,因为key和ref是React保留的两个特殊prop,并没有预期让组将直接访问。            

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

Vue.js的ul-li标签如何仿制select标签

在element-ui中table表格怎样滚动加载

VUE中更改计算属性后select选中值不变的处理方法

以上がReact コンポーネントのパフォーマンスの最適化にはどのような側面がありますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。