2
0

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 1 year has passed since last update.

ta1m1kamが一人で書くAdvent Calendar 2022

Day 16

ReactHooksについてまとめる(Context Hooks)

Last updated at Posted at 2022-12-15

概要

Contextはコンポーネントが親コンポーネントから情報を受け取る際に、それをpropsとして渡さずに済むようにします。
アプリのトップレベルのコンポーネントはネストの数に関係なくその下にある全てのコンポーネントに対してprops経由なしに情報を渡せる。

useContext

Contextを使用するためには、その値を使用したいツリー全体の親コンポーネントを対応するContext.Providerでラップします。
以下の例ではButtonコンポーネント内でthemeとしての値を使用したい場合に、Buttonコンポーネントの親コンポーネントであるFormコンポーネントThemeContext.Providerでラップする。

function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  return (
    // ...
    <Button />
    // ...
  )
}

そして、useContextを使用することによってその値をとして "dark"を受け取ることができます。ここでButtonコンポーネントがProviderとの間に何層ネストされてても関係なくuseContextの値は取得することができる。

import { useContext } from 'react';

function Button() {
  const theme = useContext(ThemeContext);
  // ...

もしProviderを親ツリーから見つけられない場合は、useContextが返す値は、そのContextを作成した時に指定したデフォルト値になる。

const ThemeContext = createContext(null);

useContextを使った例

codesandboxの例のように、contextを更新したい場合は、setStateをProvider配下のコンポーネントに渡してイベントハンドラー等で発火させてstateの値を更新する。 Providerの引数に value={{ theme, setTheme }} のように渡してconst {setTheme} = useContext(ThemeContext)のように受け取りstateを更新する関数を受け取ることもできる。

大規模アプリでのContext

ContextとReducer

大規模なアプリでは、contextとreducerを組み合わせて、ある状態に関連するロジックをコンポーネントから抽出することが多い。
こうすることによって、contextの更新に関連するロジックをコンポーネントから分離でき単体テスト等が書きやすくメンテナンスしやすいコードが書ける。

レンダリングの最適化

ログインの認証状態等を管理するために最上位の親コンポーネントでProviderでラップすることが多いが、その際にページ移動のような遷移だけで login関数が別オブジェクトとして認識されるために、再レンダリングが走りuseContext(AuthContext)を利用する深い子コンポーネントまで再レンダリングしてしまう。

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}

再レンダリングの必要はないため、別オブジェクトとして認識されないように useCallbackでlogin関数をラップして、オブジェクトの作成を useMemoでvalueとして渡している {currentUser,login}useMemoでラップすることでパフォーマンスが向上できる。

こうすることによって、currentUserが変更されない限り、useContext(AuthProvider)を呼び出すコンポーネントは再レンダリングの必要がなくなる。

import { useCallback, useMemo } from 'react';

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

その他の使い方

同じ名前のProviderでラップすることでcontextの値をOverrideすることができる。

<ThemeContext.Provider value="dark">
  ...
  <ThemeContext.Provider value="light">
    <Footer /> // ← この中での const theme = useContext(ThemeContext); のthemeの値は "light"
  </ThemeContext.Provider>
  ...
</ThemeContext.Provider>

参考

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?