UXにこだわったReactの書き方

最近、Reactで書かれたSPAのサイトがどんどん増えてきました。
そこでReactでどのような書き方をしたら早く動くのか、逆にどんな書き方をしてしまったら重くなってしまうのかまとめてみました。

まずはReactで書かれたwebサイトを見てみよう

https://www.twi-reviews.jp

今人気のゲームを、最新AI技術を用いた独自のシステムで評価しているサイトです。分析は、該当商品に関するツイートを集計し、評価・星付けをしています。 このwebサイトはReactで書かれており、サックサク動きます。このようなサイトがどのような工夫をしてReactを書いているのかをまとめてみました。

Reactの強み

  1. めっちゃ早い

なのでUXもめっちゃよくなります。 それは、DOMツリー作成時に、差分だけを描画するアルゴリズムが採用されているからです。 簡単に言うと、一度作ったComponentは再利用しちゃおうってことです。

  1. めっちゃ書きやすい

今までhtmlでテンプレート作って、cssで見た目整えて、jqueryでイベントを処理して、、、という作業を繰り返していたと思います。それがjsで全てかける分楽です。画期的です。

ミスると重くなる

上記のように早いかつ書きやすいReactですが、書き方のお作法を守らないと途端に遅くなります。今回はそんな例をいくつか紹介したいと思います。

mapでのkey忘れ

これが一番ありがちですね。
ex)

<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
  <div>
    <Component/>
  </div>
  );
})}
</div>

正しくは

<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
  <div key = {index} >
    <Component/>
  </div>
  );
})}
</div>

としましょう。
この理由は、先ほど紹介した、Reactの差分アルゴリズムにあります。uniqueなkeyを割り当てて置かないと、差分がどこにあるのか特定できず、結局全てのcommponentを再描画することになってしまいます。mapを用いる場合は必ずuniqなkeyを割り当てましょう(uniqなkeyとは、そのmapのなかでのみuniqであればよく、プログラム全体でuniqなkeyである必要はありません。) そうしていないと、以下のようなwarningが出るので、これを見たらkeyをつけましょう。

Each child in an array should have a unique "key" prop. Check the render method of KeyTrap. See http://fb.me/react-warning-keys for more information.

参考:

React.jsの地味だけど重要なkeyについて - Qiita

render内でメソッドを定義

これは盲点だと思います。keyつけてるのに遅い、、、なんで、、、ってなったらこれを疑いましょう。
しかも公式のドキュメントでもこのような書き方をしている部分があるので、多くの人が陥りがちな記法です。
ex)

render(){
return(
<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
  <div key = {index}>
    <Button onClick={()=>this.hogeFunction()}/>
  </div>
  );
})}
</div>
)
}

、、、え、なんでダメなの?って思った人が多いと思います。この記法でダメなポイントは
()=>this.hogeFunction()をrender内で定義しているため、メモリリークとなってしまう
という点です。この例ではlistのサイズは5なので、さほど問題はないのですが、このlistが大きくなればなるほどメモリは消費されます。 なので、以下のように書きましょう。

render(){
return(
<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
  <div key = {index}>
    <Button onClick={this.hogeFunction}/>
  </div>
  );
})}
</div>
)
}

この書き方で大丈夫です
()をなくすだけの簡単なお仕事ですね

関数に引数をもたせたい場合

この時が少し厄介です。
ex)

render(){
return(
<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
  <div key = {index}>
    <Button onClick={()=>this.hogeFunction(index)}/>
  </div>
  );
})}
</div>
)
}

先ほどのmapでlistを回した際に、indexの値を引数としてもたせたい場合は先ほどの記法ではうまくいきません。
そんな時はmapの中身をclass化しましょう

render(){
return(
<div>
  {[1, 2, 3, 4, 5].map(index => {  
  return (
 <HogeActionButtonClass key={index} index={index} hogeFunction={this.hogeFunction}/>
  );
})}
</div>
)
}
...
class HogeActionButtonClass extends React.Component {
  constructor(props) {
    super(props);
    this.hogeFunction = this.hogeFunction.bind(this);
  }

  hogeFunction(){
  this.props.hogeFunction(this.props.index)
  }

  render(){
  return(
  <div>
    <Button onClick={this.hogeFunction}/>
  </div>
  );}
}

このようにかくと、render内の()=>という記法が無くなります!イメージとしては、全体の関数であったHogeFunctionを各Buttonにも持たせることで、引数をpropsとして扱えるようにした点です。これでメモリリークを防ぐことができます。
上記サイト(https://www.twi-reviews.jp)では、ランキングの各ComponentはClass化してあります。

f:id:saegusa41010:20190215165919p:plain
Class化されたComponent

最後に

最後までお読みいただきありがとうございました!不適切な表現やわかりにくい表現等ご意見お待ちしています。