[React] Component, PureComponent, SFCComponentのレンダリングの挙動の違いをまとめてみる。

2018年9月2日

「Componentはどういう時に再レンダリングされるんですか?」

Reactで開発している方はもちろん、「propsやstateが変更された時だよ!」と自信をもって答えられるかもしれません。

が、

「ネストした時はどうなるんですか?」

「PureComponentやSFCとかってレンダリングの挙動が変わるんですか?どう変わるんですか?」

みたいなところにちゃんと答えられる自信がなかったのでそれぞれのコンポーネントの違いをまとめてみました。

 

各コンポーネントの違い

 

Component

 

class HelloComopnent extends Component {
  render() {
   return <div><h1>Hello World</h1></div>
}

言わずと知れたReactのComponentクラスです。

チュートリアルでも使っているように特に意識しなければ、このクラスを継承する形でコンポーネントを定義します。

Component classにはライフサイクルメソッドなるものが実装されており、コンポーネントのマウント時から、アンマウント時、propsが変化した場合などにフックを仕込むことができます。

he Component Lifecycle

 

PureComponent

 

class HelloComopnent extends PureComponent {
  render() {
   return <div><h1>Hello World</h1></div>
}

Reactのv15.3から追加されたコンポーネントで基本的な部分はComponentと変わらずライフサイクルメソッドが使えます。

Componentとの大きな違いは、デフォルトでshouldComponentUpdateが実装されている所です。

shouldComponentUpdateはライフサイクルメソッドの一つでReactコンポーネントを再レンダリングするかどうかをtrue/falseで返すことで再レンダリングを制御できます。(trueを返した場合だけ再レンダリングされます。)

繰り返しにはなりますがPureComponentはshouldComponentUpdateがデフォルトで実装されているので自身のコンポーネントのpropsに変更がなければ再レンダリングが走らないようになっています。

 

SFC(Stateless Functional Component)

 

const HelloComopnent = (props) => {
   return <div><h1>Hello World</h1></div>
}

SFCはステートレスの名前の通り、内部に状態を持ちません。

関数としてのコンポーネントなので、同じpropsからはつねに同じdomがレンダーされることが保証されています。

表示に特化したPlesentationalなコンポーネントとして用いられいることが多く、ライフサイクルメソッドが使えません(渡されたpropsを表示するだけとなので必要ないと言えばそうなのですが)

 

デモでそれぞれの違いを確認

 

説明のためにデモを作ってみました。

デモ

React Rendering Sample

 

実際にデモを見てもらうとわかるのですが、
親のコンポーネントがあってそのなかにそれぞれ並列でComponent,PureComponent,SFCが存在するような構造になります。

ソースはこちらです。
version-1/react-rendering-sample

紫のボタンがcounterをインクリメントしてくれるボタンで、
それぞれの色のボタンがそれぞれのflgをトグルしてくれるボタンです。

stateは全て親コンポーネントをラップするAppComponentが保持しています。

紫のボタンをクリックするとタブがcounterがインクリメントされます。
その他の色のボタンを押すと対応したコンポーネント内に表示されているflgの値が切り替わります。

また、それぞれのコンポーネントのrenderメソッド内にconsole.logを仕込んであるのでコンポーネントが再レンダリングされるタイミングでコンソールにログが表示されるようになっています。
(以下で色々と説明していきますが、実際にデベロッパーツール開いて自分で試した方がわかりやすいです。)

これらを前提に実際のコンポーネントの挙動を見ていきます。

 

親コンポーネントから渡されたpropsが変化した場合

 

先ほども説明したように、紫のボタンを押すとcounterの値がインクリメントされるので親コンポーネントのpropsが変化し、再レンダリングされます。続いてそのpropsが渡されている子のコンポーネントも連鎖して再レンダリングが走ります。

 

親コンポーネントから渡されていないpropsが変化した場合

 

親コンポーネントからpropsが渡されていない(自身のpropsが変化しない)ケースを試したいので、それぞれのボタンを押下して各コンポーネントがレンダリングされるかどうかを調べます。

結果は次の表の通りになり、

緑ボタン押下 黄ボタン押下 赤ボタン押下
Component(緑) レンダリングされる レンダリングされる レンダリングされる
Pure Component(黄) レンダリングされない レンダリングされる レンダリングされる
SFC(赤) レンダリングされる レンダリングされる レンダリングされる

PureComponent以外は親の状態(props) が変化すると自身の状態が変化していないのにもかかわらず再レンダリングされてしまうことがわかります。

 

無駄なレンダリングを避けるためのPureComponent

 

実際にデモを動かしてみて、無駄なレンダリング(自身の状態が変わっていないのに再描画すること)によるオーバヘットを防ぐにはPureComponentを極力使っていった方が良いということがわかりました。

もちろん、ComponentでもshouldComponentUpdateを愚直に実装すれば同じことができるのですが、まあPureComponentで良いですよね。またSFCの場合は、recomposeなどのライブラリを使えばPureComponentチックなSFCもかけるみたいです。

「現状そんなに複雑で大きいコンポーネントってないから別にPureComponentじゃなくても」というのもあるかと思うのですが、個人的な意見としてシステムは寿命が長くなるほど大きく複雑に変化しておくのでいまのうちから少しずつPureComponentで実装しておくと後が楽なのかなと思っています。

 

PureComponentを使わない方が良い場合

 

どうやら、shouldComponentUpdateの計算コストも少なくないらしく、頻繁にpropsが変化するようなコンポーネントでは、毎回レンダリングするかどうかの計算を行わないといけないのでそういったコンポーネントにはComponentを使って無条件に再レンダリングさせるのが良いようです。

PureComopnent最高!という感じで全てのコンポーネントを置き換えると逆に遅くなったというのもありますので一応ここは念頭においておく必要があるのではないでしょうか?

 

まとめ

 

  • PureComponentはデフォルトでshoulComponentUpdateが実装されており、自身のpropsが変化していなければ再レンダリングしないという判断を自動でしてくれる。
  • 親が再レンダリングされるとComponentもSFCも再レンダリングされ自身のpropsが変わっていないのにレンダリングされるという無駄が生じる。
  • PureComponentは頻繁に自身のpropsが書き換わるようなコンポーネントでは使わない方がよい

このように3つまとめてみましたが、今回の観点で比べるとPureComponentが特徴的な動作をするのでこのようなPureComponentが主役のまとめになりました。

レンダリングの挙動が言葉で説明されてもいまいちピンとこなかったのでデモを作って公開してみました。実際にさわってみると自分でも結構わかりやすいなと感じたのでぜひ触ってみてください。