この記事は
- 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)
}
});
実装
Atom
とSelector
の動きを確認するために、実際に書いてみましょう!
データを表示・更新させる
先程の例で利用した体重の値を、Recoilの状態として登録しましょう。
/recoil/weight.js
を作成し、どのディレクトリでも利用できるようexportしておきましょう。
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
から、Atom
とSelector
両方の値を更新してみましょう。
/pages/weight.js
を作成し、Atom
とSelector
の値を表示・更新させます。
Selector
もAtom
と同様に、useRecoilState()
を用いて更新することができます。
(Atom
はuseRecoilValue ()
を用いて、読み取り専用にしています。)
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
からAtom
とSelector
両方の値更新を確認できると思います!
非同期処理でデータを取得する
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
を入れる書き方もできますので、コード設計に合わせて使い分けてみてください!)
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.Suspence
とuseRecoilValue()
を利用して、Qiitaのタイトルを表示させたかったのですが、2022 3/13時点でNext.js
はReact.Suspence
をまだサポートしていません。(試験的には行っているようですが、正式版ではまだですね。)
そのような時のために、Recoil側でuseRecoilValueLoadable()
という関数を用意してくれていますのでこれを使って見たいと思います。
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にアクセスすると、非同期通信が行われているのを確認できると思います!
「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