12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【React】<Text val="hello"/>が32回も再描画されたわけ

Last updated at Posted at 2019-05-12

たとえばAtomic Designを採用してこんなシンプルなコンポーネントを作ったとします


const Text = (props) => <p>{props.val}</p>

そして、プログレスバーを表示するコンポーネントと同じコンポーネントに置きます

class Progress React.Component {
  render(){
    return (
      <React.Fragment>
        ...
          <Text val="処理中です" />
          <ProgressBar percent={this.props.percent} />
        ...
      </React.Fragment>
    )
  }
}

“処理中です” という文字とプログレスバーが表示されます。
意図通りに動いてくれます。
なにも問題なさそうに見えます

しかし、このコードだと this.props.percent が変更されるたびに
ProgressProgressBar だけでなく、Text も再描画されます。
this.props.percentが32回変更された場合には "処理中です" という 文字列しか渡していない Text も32回再描画されます

同じように、親やさらにその上のコンポーネントが更新されると、子・孫・曾孫・玄孫にあたる Text もrenderが走ってしまうわけですね

「ん? まって。なんで同じpropsを渡してrenderされるの・・・?」

確かに、同じpropsを渡しているならrenderなんて走らなくていいはず。
ましてや渡しているのはただの文字列。
なんで同じ文字列でrenderしちゃってるのでしょう。

同じ文字列で何度もrenderされる理由

ここで、Textがどんなpropsを受け取ってるのか見てみましょう

{ val: "処理中です" }

さて、this.props.percent が変更されました。
次に受け取るpropsはこんな感じ。

{ val: "処理中です" }

ここで、F12を押して開発者ツールのコンソールの中に、以下をコピペしてEnterを押してみてください

{ val: "処理中です" } === { val: "処理中です" }

そうすると、こうなります。
image.png

JavaScriptだと { val: "処理中です" }{ val: "処理中です" } は別のものとして認識されてしまいます。
オブジェクト同士を比較するときに同一のインスタンスかどうかで判断するため、falseが返ります。
渡されたpropsが前に渡されたものと異なるオブジェクト → じゃあ、またrenderしなきゃ ってなってるわけです。

や、待ってくれ。私がしてほしいのは

{ val: "処理中です" } === { val: "処理中です" }

ではなく

val === val

だ。 ちゃんと中身を見て、同じかどうか判別してほしい。
え、これどうしたら無駄なrender防げるの・・・?

解決法

そんな人のために React.memoReact.PureComponent が用意されました。
Reactの16.6以降のバージョンなら両方使えるようになっています。

使い方は簡単
React.memoは

const Text = (props) => <p>{props.val}</p>

const Text = React.memo((props) => <p>{props.val}</p>)

React.PureComponent は

class Progress React.Component {
  render(){
    ...
  }
}

class Progress React.PureComponent {
  render(){
    ...
  }
}

とするだけ。

そうすると

{ val: "処理中です" } === { val: "処理中です" }

ではなく

val === val

で比較してくれるようになります。

やったね!

気を付けないといけないこと

で、終わってくれればいいんですが、React.memoReact.PureComponent にも問題点はあります。

{ val: "処理中です" } === { val: "処理中です" }

val === val

にするということは、propsをたくさん渡すコンポーネントの場合、

{ v1:"a", v2: "b", ..., v1000: "zzz" } === { v1:"a", v2: "b", ..., v1000: "zzz" }

v1 === v1
v2 === v2
...
v1000 === v1000

といちいち展開して比較して、ってやってしまうわけです。
うっかりすると、renderするコストよりも比較するコストの方が重くなったりするので、
適宜計測して、どちらがよりコストが低いかを見ておいてください。

また、比較してくれるのは1段目だけです。

<Text val="処理中です" style={{ color:"red" }} />

とかしてしまうと、propsは

{ val: "処理中です", style: { color:"red" } }

となって

val === val // true
{ color:"red" } === { color:"red" } // false

という比較になり、どこかで変更があるたびに再度renderされてしまいますのでご注意を。

無駄にrenderされていないかを知るためには

why-did-you-render というライブラリがオススメ。
https://github.com/welldone-software/why-did-you-render

無駄にrenderされている場合にconsoleへ表示してくれます。
image.png

導入も簡単なので、試してみてください。

12
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?