Blogブログ

【React】Recoilの強み。無駄なレンダリングを防ぐとは?

こんにちは。最近、韓国ドラマとSPY×FAMILYにハマっているHLCのエンジニアの掛橋です。

前回の記事ではRecoilの簡単な説明や、Atomの使い方などざっくり説明させていただきました。

まだご覧になってない方はこちらをご覧ください。

今回の記事では無駄なレンダリングを防ぐRecoilの強みについて説明します。

無駄なレンダリングとは?

カウントアップするアプリを例にします。

前回の例に引き続き、親(Parent.tsx)、子(Child.tsx)、孫(GrandChild.tsx)というコンポーネントがあり、
親は子のコンポーネントを持ち、子は孫コンポーネントを持つものとします。

親にはボタンがあり、クリックすると孫が持っている数値がカウントアップされる
という動きです。(Parent,Child,GrandChildにはレンダリングされるとログを吐き出す様にしています。)

状態管理はContextで行っています。

親のもつボタンを押すと、親、子、孫全てのコンポーネントが再レンダリングされるのが分かります。

親のボタンを押したら、孫のみ再レンダリングされていれば十分ですが

親、子の2つのコンポーネントが余分に再レンダリングされています。こちらが無駄なレンダリングになります。

どうやって無駄なレンダリングを防ぐのか?

RecoilでAtomの状態を変化させたり取得するhooksは代表して以下3つ

useRecoilState

useSetRecoilState

useRecoilValue

こちらを適宜必要な箇所で使用することで実現できます。

useRecoilState

こちらはReactのuseStateと同じように
Atomの値とAtomの値を更新する関数がタプル型で返されます。


const [value, setValue] = useRecoilState(Atom)

useSetRecoilState

こちらはAtomの値を更新する関数のみを作成できます。


const setValue = useSetRecoilState(Atom)
setValue(100) //Atomに100という値を入れる

useRecoilValue

こちらはAtomの値そのものを取得します。


const value = useRecoilValue(Atom)
console.log(value) //Atomに100の値が入っていれば、ログに100と表示される

親はuseSetRecoilStateを扱い
孫はuseRecoilValueを使うことで無駄なレンダリングを防ぐことが可能です。

※親の階層でuseRecoilStateを使うと、state が更新されると孫まで再レンダリングがされてしまいます。

実装方法

1.状態を管理したい最上位層をRecoilRootタグで囲む


import React from "react"
import { RecoilRoot } from "recoil"
import { Parent } from "./components/Parent"

export const App = () => {
  return (
    <div>
      <div className="App">
        <RecoilRoot>
          <Parent />
        </RecoilRoot>
      </div>
    </div>
  )
}

2.Atomの準備


import { atom } from "recoil"

export const countState = atom({
  key: "countState",
  default: 0,
})

3.親コンポーネントでuseSetRecoilStateを使用


import React from "react"
import { useSetRecoilState } from "recoil"
import { countState } from "../globalStates/counterAtom"
import { Child } from "./Child"

export const Parent = () => {
  const setCount = useSetRecoilState(countState)
  const handleClick = () => {
    console.log("count up")
    setCount((value) => value + 1)
  }
  console.log("Parent")
  return (
    <div style={{ backgroundColor: "orange", padding: 30 }}>
      親やで
      <Child />
      <button onClick={handleClick}>+1</button>
    </div>
  )
}

4.孫コンポーネントでuseRecoilValueを使用


import React from "react"
import { useRecoilValue } from "recoil"
import { countState } from "../globalStates/counterAtom"

export const GrandChild = () => {
  const count = useRecoilValue(countState)
  console.log("GrandChild")
  return (
    <div style={{ backgroundColor: "white", padding: 30 }}>
      <div>孫やで</div>
      {count}
    </div>
  )
}

結果

上:Recoil 下:Context
Contextを使用していた時と比べると親、子のレンダリングがなくなっているのが分かりますね。

Recoil、ええやん!素敵やん!