Skip to content

Latest commit

 

History

History
158 lines (115 loc) · 7.22 KB

state-management.md

File metadata and controls

158 lines (115 loc) · 7.22 KB

🗃️状態管理

  • useState
  • jotai

を利用します。

基本的にはuseStateを利用しpropsで子に受け渡しましょう。

localstate

しかしコンポーネントの切り分けや呼び出しがあるとpropsリレー(props drilling) が発生します。

propsdrilling

これはその状態に関心がないコンポーネントにも状態がわたる、コンポーネントが密結合になるなどいくつか課題があります。
そういった場合にはjotaiを用いてグローバルなStateを用いてください。

globalstate

これにより灰色のコンポーネントには状態がわたらず view-component としてレイアウトに専念することができます。また、末端のコンポーネントも model-component として状態とその表示に専念できるでしょう。

jotaiはこのような半グローバルな状態共有やHooksと相性の良い状態管理ライブラリです。 類似ライブラリにRecoilがありますが、本テンプレートではよりシンプルなこちらを選定しました。
https://jotai.org/
キャラに独特の可愛さがありますね。

非常にシンプルでuseStateと似た使用感があります。

//store.ts
import { atom } from 'jotai'

const countAtom = atom(0)

//Counter.tsx
import { useAtom } from 'jotai'

const Counter = () => {
   const [count, setCount] = useAtom(countAtom);
  return (
    <div>
      {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
}

グローバルStateはよく空間の汚染が懸念されます。 その際には状態を共有したい一つ上をProviderで囲ってあげましょう。

import { Provider } from "jotai";

const SomeComponent = () => (
  <Provider>
    <SomeComponent />
  </Provider>
)

Providerはこちらの動画で詳細な使い方が紹介されています。

一方で、これは注意しなくてはならない実装パターンです。せっかくglobalに使用したい状態があったとしても、Providerが存在するとその下では別の状態として扱われてしまいます。
例を挙げると、「スナックバーの表示/非表示の状態が別ものとして扱われてしまう」ケースが挙げられます。

基本は Provider-less mode で利用し、feature/Component名pages/Component名 直下で定義し擬似的なスコープを獲得する。 特殊なケースで Provider を利用すると良いでしょう。

API通信とキャッシュ戦略

グローバルな状態とAPI通信は密接な関係にあります。例えば、「ユーザー情報を読み込んでその状態をキャッシュして使い回す」などです。
Reactにはサーバー通信をキャッシュするために他にもメジャーな手段がります。

それが

などです。bulletproof-reactでも紹介されていますのでそちらもご覧ください。

上記のライブラリを端的にまとめると

  1. 情報をフェッチする
  2. 情報をライブラリ側にとっておく(キャッシュ化する)
  3. 次回フェッチ時にキャッシュから情報を返す(無用なAPI通信を避け高速化を図る)

という役割を持つものとなり、状態をグローバルstateにsetする手間が減り便利です。
一方で、キャッシュを使用することになりますので適切に扱わなければ古い情報が返ってくるなどの問題が生じます。

これらのライブラリの根底には stale-while-revalidate という考え方があります。正しく運用するためにも、導入前に一度触れておくと良いでしょう。参考

上記の中から、本リポジトリではTanstack Query(React Query)を採用しています。 最も使われているデータフェッチングライブラリであることや、キャッシュのkeyとフェッチングの関数が独立しており細かい調整がしやすいことから選定しました。

使用方法は公式ドキュメントや以下が参考になります。

コンポーネントの出し分けとSuspense

上記のライブラリはリクエストを飛ばす際に

  • data
  • error
  • isLoading(fetching)

などの状態を返却してくれます。また、Reactはあくまで関数ベースです。

したがってコンポーネントの出しわけ、返却値を分岐させるには以下のようなパターンが使えます。

function Example() {
  const { isLoading, error, data } = useQuery(['repoData'], () =>
    fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
      res.json()
    )
  )

  if (isLoading) return 'Loading...'

  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{' '}
      <strong>{data.stargazers_count}</strong>{' '}
      <strong>🍴 {data.forks_count}</strong>
    </div>
  )
}

Tanstack Queryより引用

エラーが生じたら失敗の表示を、データがなければローディングを返すという形ですね。

基本はこちらの実装で問題ありませんが、React 18から似た機能として Suspense が導入されています。また Suspense はパフォーマンス向上にも寄与します。参考

実装方法としては

  • 対応ライブラリを使用する
  • Suspenseで囲む
  • fallback用のコンポーネントを指定する

という程度ですので負担は大きくありません。

<Suspense fallback={<div>サスペンドしたらこれが表示される</div>}>
  {/* ↓サスペンドしなかったらこれが表示される */}
  <MyComponent />
</Suspense>

ReactのSuspense対応非同期処理を手書きするハンズオン より引用

しかし登場して間もない技術であること、内部実装が少々特異であるため導入はチームで話し合って決定しましょう。




>>「🧪テスト」へ進む