🏆

Reactのコンテキストについて

2022/09/17に公開

グローバルstate管理についての課題

「Providerディレクトリが増えてきたな・・・」
「contextでしかグローバルstate管理してないからだよな・・・」
「3年ぐらいのロングランなのにこの時点で5つ6つも存在していて大丈夫なんかな・・・」
「確かReactのcontextは不要な再レンダリングを走らせてしまう問題を抱えていた気がする・・・」
「このままの流れで全てをコンテキストで管理していくとすごくまずい気がする・・・勘だけど・・・」
「とりあえずコンテキストについて調べてみるか」

Reactのコンテキスト

「コンテキストって便利だな」
「どのコンポーネントからでも参照できる値を提供してくれるんだから」
「コンポーネント間の値の受け渡しじゃないから、」
「propsを使う必要ないし、」
「useContextで簡単にアクセスできる」

  • 親・子・孫コンポーネント関係なくどこからでもアクセスできるstateを提供する
  • そのためPropsの受け渡しを必要としない
  • React HooksではuseContextというAPIを用いる

公式
コンテクストはツリーの各階層で明示的にプロパティを渡すことなく、コンポーネント間でこれらの様な値を共有する方法を提供します。

グローバルとみなすことができるstateを提供する

「どういう値をコンテキストで管理するのに向いているんだろう」
「どこからでもアクセスしたい情報だから、」
「ユーザーのログイン情報とかどこからでも参照できたら便利だな」
「ログインしてるかしてないかで、」
「ヘッダーのUIの状態を変えたり、」
「ログインボタンとログアウトボタンを出し分けたり、」
「Myページを編集できたりする」

公式
コンテクストは、ある React コンポーネントのツリーに対して「グローバル」とみなすことができる、現在の認証済みユーザ・テーマ・優先言語といったデータを共有するために設計されています。

contextのメリット🍊

「コンテキストって複数作れるんだ」
「それだったらログイン状態だけじゃなくて、」
「ダークモードを切り替えるためのテーマだったり、」
「言語選択によって記事を翻訳できたらいいな」
「上記3つはそれぞれ異なる状態だしファイルを分けようかな」

  • 複数のストレージを持ち関心ごとに分離できる
  • グローバルな(App全体で共通で必要となるような)データを共有するための仕組みを持つ
  • Propsのバケツリレーを防ぐ
// ログイン管理用
export const LoginProvider: FC<Props> = ({children}) => {
  <LoginContext.Provider value={value}>
    {children}
  </LoginContext.Provider>
}

// テーマ管理用
export const ThemeProvider: FC<Props> = ({children}) => {
  <ThemeContext.Provider value={value}>
    {children}
  </ThemeContext.Provider>
}

// 言語管理用
export const LanguageProvider: FC<Props> = ({children}) => {
  <LanguageContext.Provider value={value}>
    {children}
  </LanguageContext.Provider>
}

contextのデメリット💣

「あれ、コンテキストで全部のstate管理してしまえば良いんじゃ・・・」
「でも、それが一般的じゃないのは何か理由があるんだろうな・・・」
「そういえばじゃけえさんのUdemyの動画再レンダリングが云々言ってたな・・・」

  • 不要な再レンダリング
    • コンテキストを参照しているコンポーネントは、
    • コンテキストが持つstate更新によって再レンダリングが発生する
    • 参照していないstateの更新であっても、
    • コンテキストを参照していれば再レンダリングが発生する
  • 例えば下記で、UserDetailではuserInfoしか参照していないが、LoginButtonが押下されることによって再レンダリングが発生する
// contextから提供されるvalue
type UserContextType = {
  isLogin: boolean
  userInfo: User
  setIsLogin: (curIsLogin: boolean) => void
  setUserInfo: (curUserInfo: User) => void
}
// ログイン用のボタン
export const LoginButton = () => {
  const { setLogin } = useContext(LoginContext)
  return (
    <button onClick={() => setIsLogin(true)}>ログイン</button>
  )
}
// ユーザー詳細ページ
export const UserDetail = () => {
  const { userInfo } = useContext(LoginContext)
  return (
    <div>
      <span>ユーザー名: {userInfo.username}</span>
      <span>年齢: {userInfo.age}</span>
    </div>
  )
}

contextを理解する🎓

「どこかでコンテキスト内のstateが更新されたとしたらコンテキストを参照しているコンポーネントは全て無条件で再レンダリングが発生してしまうんだ・・・」
「そうなると再レンダリングの回数が増えて、それを止めるためのメモ化にコストがかかるな・・・」
「無闇にグローバルstateを持たせるべきじゃないんだな・・・」

  • コンテキストはグローバルにどこからでも値を参照できて便利だが、
  • 再レンダリングの温床となりかねないため無闇に利用すべきでない
  • また、グローバルstateの管理はRedux等のライブラリを用いても行える
  • Redux等のライブラリは再レンダリングの課題を解決する仕組みを持つ
  • グローバルstateの意味合いによって使い分けるのが良い

Discussion