15
18

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.

Recoilを基礎から理解しよう!(Selector編)

Last updated at Posted at 2022-03-13

この記事は

  • Reactで状態管理ツールを使ってみたい。
  • でも学習コストが高くて難しそう。

そのように感じている人向けに、Recoilの使い方をまとめたものになっています。

注意書き

本記事は「Recoilを基礎から理解しよう!(環境構築・Atom編)」で環境構築を行った、Next.jsで動作確認を行います。
よろしければ前回の記事を参考に環境構築を行ってください!

Selectorって何?

Selectorは前回説明したAtomや、他の Selectorを参照し、新しい値を作成する機能になります。
また作成したSelectorは参照元の値を更新することができ、更新時にはSelectorの値の再計算を行います。
(参照元が更新されると、作成したSelectorが自動計算される仕組みになっています。)

下記は体重のAtomを参照するSelectorを作成した例になります。

// 体重のAtomを作成
const weightState = atom({
  key: "weight",
  default: 50
});

// Selectorを作成
const weightUnitState = selector({
  key: 'weightUnit',

  // 参照先を元に新しい値を作成する場合は、「get」を利用します
  get: ({ get }) => {
    // 体重のAtomを参照
    const weight = get(weightUnitState);
    const unit = 'kg';

    // 単位(kg)付きの体重をreturn
    return `${weight}${unit}`;
  },

  // 参照先の値を更新する場合は、「set」を利用します
  set: ({ set }, newValue) => {
    set(weightState, newValue)
  }
});

実装

AtomSelectorの動きを確認するために、実際に書いてみましょう!

データを表示・更新させる

先程の例で利用した体重の値を、Recoilの状態として登録しましょう。
/recoil/weight.jsを作成し、どのディレクトリでも利用できるようexportしておきましょう。

/recoil/weight.js
import { atom, selector } from "recoil"

// 体重のAtomを作成
export const weightState = atom({
  key: "weight",
  default: 50
});

// Selectorを作成
export const weightUnitState = selector({
  key: 'weightUnit',

  // 参照先を元に新しい値を作成する場合は、「get」を利用します
  get: ({ get }) => {
    // 体重のAtomを参照
    const weight = get(weightState);
    const unit = 'kg';

    // 単位(kg)付きの体重をreturn
    return `${weight}${unit}`;
  },

  // 参照先の値を更新する場合は、「set」を利用します
  set: ({ set }, newValue) => {
    set(weightState, newValue)
  }
});

作成したSelectorから、AtomSelector両方の値を更新してみましょう。

/pages/weight.jsを作成し、AtomSelectorの値を表示・更新させます。
SelectorAtomと同様に、useRecoilState()を用いて更新することができます。
(AtomuseRecoilValue ()を用いて、読み取り専用にしています。)

/pages/weight.js
import { useState } from "react"
import { useRecoilState, useRecoilValue } from "recoil";
import { weightState, weightUnitState } from "../recoil/weight"

export default function Weight() {
  // 体重のAtomを呼び出し(読み取り専用)
  const weightAtom = useRecoilValue(weightState)
  // Selectorを呼び出し
  const [weightSelector, setWeightSelector] = useRecoilState(weightUnitState)
  
  const [weight, setWeight] = useState(0)

  // Selectorを用いて体重を更新する
  const updateWeight = (newWeight) => {
    setWeightSelector(newWeight)
    setWeight(0)
  }

  const handleChange = (e) =>{
    setWeight(e.target.value)
  }

  return (
    <div>
      <p>Atomの値:{weightAtom}</p>
      <p>Selectorの値:{weightSelector}</p>
      <div>
        <input type="number" value={weight} onChange={handleChange} />
        <button onClick={() => updateWeight(weight)}>
          Update Weight
        </button>
      </div>
    </div>
  )
}

Next.jsを起動させましょう。

npm run dev

localhost:3000/weightにアクセスすると、SelectorからAtomSelector両方の値更新を確認できると思います!

体重.gif

非同期処理でデータを取得する

Atomは非同期処理を扱うことはできませんが、Selectorは非同期処理も扱うことができます。
非同期のSelectorを利用する際、useRecoilValue()useRecoilState()は、Promiseが完了していれば値を返し、完了していなければPromiseオブジェクトをthrowします。
これはReact.Suspenceで扱えるようにするために、このような設計になっているのだと思います。

非同期処理を作成するので、axiosをインストールしておきましょう。

npm install --save axios

インストールが完了したら、APIからデータfetchを行うRecoilの状態を作成します。
今回はみんな大好き「Qiita API」を利用させてもらいます。
/recoil/qiita.jsを作成し、その中にSelectorを作成します。
(今回のようにAtomの中にSelectorを入れる書き方もできますので、コード設計に合わせて使い分けてみてください!)

/recoil/qiita.js
import { atom, selector } from "recoil"
import axios from "axios";

export const qiitaListState = atom({
  key: 'qiitaList',
  default: selector({
    key: 'getQiitaList',
    get: async ({get}) => {
      try {
        // 20件の、Qiita記事情報を取得
        const response = await axios('https://qiita.com/api/v2/items?page=1&per_page=20');
        const qiitaList = await response.data.map((v)=>v.title)
        return qiitaList;
      } catch (error) {
        throw error;
      }
    }
  })
});

次に/pages/qiita.jsを作成し、取得したQiitaのタイトルを画面に表示させます。

本当はReact.SuspenceuseRecoilValue()を利用して、Qiitaのタイトルを表示させたかったのですが、2022 3/13時点Next.jsReact.Suspenceをまだサポートしていません。(試験的には行っているようですが、正式版ではまだですね。)

そのような時のために、Recoil側でuseRecoilValueLoadable()という関数を用意してくれていますのでこれを使って見たいと思います。

/pages/qiita.js
import { useRecoilValueLoadable } from "recoil";
import { qiitaListState } from "../recoil/qiita"

export default function Qiita() {
  // Qiita記事情報を呼び出し
  const qiitaList = useRecoilValueLoadable(qiitaListState)

  // fetchの状態によって分岐
  switch (qiitaList.state) {
    case "hasError":
      throw qiitaList.contents;
    case "loading":
      return <div>Loading...</div>;
    case "hasValue":
      return (
        <div>
          <p>Qiita List</p>
          <ul>
            {qiitaList.contents.length && qiitaList.contents.map((v,i) => (
              <li key={i}>{v}</li>
            ))}
          </ul>
        </div>
      );
  }
}

localhost:3000/qiitaにアクセスすると、非同期通信が行われているのを確認できると思います!
Qiita.gif

「React.Suspence」がサポートされたらこんな感じになります!
import { Suspense } from "react";
import { useRecoilValue } from "recoil";
import { qiitaListState } from "../recoil/qiita"

export default function Qiita() {
  // Qiita記事情報を呼び出し
  const qiitaList = useRecoilValue(qiitaListState)

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <div>
        <p>Qiita List</p>
        <ul>
          {qiitaList.contents.length && qiitaList.contents.map((v,i) => (
            <li key={i}>{v}</li>
          ))}
        </ul>
      </div>
    <Suspense>
  );
}

ロジックとコンポーネントが分離されていて見やすいですね!
(早くサポートされないかな...)

最後に

次回はRecoilのカスタムフック利用について書きたいと思います!

今回利用したコードはこちらになります!
https://github.com/takusan64/nextjs-recoil

15
18
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
15
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?