LoginSignup
6
5

More than 1 year has passed since last update.

Recoil最低限 CRA編(2021年8月)

Last updated at Posted at 2021-08-30

Reactにおける最新のグローバルステート管理ライブラリであるRecoilを試してみたいと思います。グローバルで利用できるuseStateと捉えるのが一番イメージに近いかもしれませんね。手軽に使えるのでマスターしておきたいところです。

redux版も書きました。こちらをどうぞ。

やりたいこと

  • recoilのテスト
  • グローバルで値が維持されていることを確認するために最低限のrouting(ページ移動)も行う
  • 値の永続化も行う

やりたいことイメージをまとめると下記のような感じ。

Kobito.lhGI5o.png

準備

作業場の確保とライブラリのインストール

npx craete-react-app recoil-test
cd recoil-test

npm install recoil react-router-dom

recoilと関係のない実装(骨組みづくり)

まず、複数ページからなる最低限の構造を作ります。とりあえずHomeとAboutからなるサイトにしてみます。

src以下にHome.jsとAbout.jsを作ります。

touch src/Home.js src/About

Home.jsを記述。

Home.js
import { Link } from "react-router-dom";

const Home = () => {
    return (
        <>
            <h1>Home</h1>
            <Link to="/about">About</Link>
        </>
    );
}

export default Home;

移動には<a></a>ではなく<Link></Lin>を利用してください。じゃないと、ページが更新されrecoilによる値の維持ができません。

About.jsを記述。

About.js
import { Link } from "react-router-dom";

const About = () => {
    return (
        <>
            <h1>About</h1>
            <Link to="/">Home</Link>
        </>
    );
}

export default About;

作成したHome.jsとAbout.jsを読み込んでサイトの構造を作ります。

App.js
import { BrowserRouter, Switch, Route } from "react-router-dom";

import Home from "./Home";
import About from "./About";

const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/" exact component={Home} />
        <Route path="/about" exact component={About} />
        <Route render={() => <p>Page not found.</p>} />
      </Switch>
    </BrowserRouter>
  );
}
export default App;

一度、動作を確認して置きましょう。

npm start

ページ間を移動する簡単なサイトができました。
Recoilを利用して、このページ間で状態を維持するようにしてみたいと思います。

スクリーンショット 2021-08-31 6.51.59.png

Recoilを利用した実装

ここからが本題です。

RecoilRootで状態を共有したエリアを囲む

ReduxでもContextでも値を共有したいエリアを専用タグで囲みました。recoilではRecoilRootです。
多くの場合、サイト全体で値を共有するのでindex.jsに記述します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { RecoilRoot } from "recoil"; //追加

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

Atomを作成

reacoilではatomと呼ばれる共有の変数を作成し値を共有します。
といらえずsrcの中にatoms.jsというファイルを作って、そこに記述します。

touch src/atoms.js

1つの変数につき1atom作る感じです。変数にはもちろんオブジェクトも持てます。
以下ではcountとuserの値を管理する2つのatomを作っています。

atoms.js
import { atom } from "recoil";

export const countState = atom({
    key: "count",
    default: 0
});

export const userState = atom({
    key: "user",
    default: {
        name: "hoge",
        age: 11
    }
});

Atomの利用(Home.jsでの表示と更新)

Home.jsを下記のようにしてみます。

Home.js
import { Link } from "react-router-dom";
import { useRecoilState } from "recoil";
//atoms.jsを読み込み
import { countState, userState } from "./atoms";

const Home = () => {

    //useRecoilState(読み書きできる)
    const [count, setCount] = useRecoilState(countState);
    const [user, setUser] = useRecoilState(userState);

    //countをインクリメント
    const increment = (c) => {
        return c + 1;
    }

    //user.ageをインクリメント(機能的な意味はなし)
    const updateUser = (u) => {
        return { ...u, ...{ age: u.age + 1 } };
    }

    return (
        <>
            <h1>Home</h1>
            <p>count:{count}</p>
            <button onClick={() => setCount(increment)}>increment</button>
            <hr />
            <p>user.name:{user.name}, user.age:{user.age}</p>
            <button onClick={() => setUser(updateUser)}>user age increment</button><br/><br/>
            <Link to="/about">About</Link>
        </>
    );
}

export default Home;

実行すると下記のような感じになります。
ボタンを押すとcountやuser.ageがカウントアップされます。

スクリーンショット 2021-08-31 7.23.08.png

ゴールはこれらの値がAbout.jsでも利用できか?ということです。

Atomの利用(About.jsでの表示:readonly)

About.jsを下記のようにします。
About.jsではuseRecoilStateではなくuseRecoilValueを利用してリードオンリーとしてみました。

ここでは触れませんが、useSetRecoilState()というセットだけのhookもあり、それを利用すると再レンダリングを最小限に抑えることなどができます。

Homeで変更した値が反映されていればOKです。

About.js
import { Link } from "react-router-dom";
import { useRecoilValue } from "recoil";
//atom.jsを読み込み
import { countState, userState } from "./atoms";

const About = () => {

    //useRecoilValue(読み取るだけ)
    const count = useRecoilValue(countState);
    const user = useRecoilValue(userState);

    return (
        <>
            <h1>About</h1>
            <p>count:{count}</p>
            <p>user_name:{user.name}</p>
            <p>user_age:{user.age}</p>
            <br/>
            <Link to="/">Home</Link>
        </>
    );
}

export default About;

変更した値がAbout.jsでも維持されていればOK。

スクリーンショット 2021-08-31 7.28.24.png

Recoilの永続化

グローバル(ページ間)で値は共有できるようになりましたが、ブラウザを更新したら値は消えてしまします。
多くのシーンでは値の永続化が不可欠となります。Recoilには永続化の仕組みが備わっているようですが、少々煩雑なのでrecoil-persistというモジュールを利用してみたいと思います。

モジュールのインストール

npm install recoil-persist

atoms.jsに記述を追加

atoms.jsに数行追加するだけで永続化が可能となります。
ここでは2つのatomのうち、userStateだけ永続化の対象としてみます。

atoms.js
import { atom } from "recoil";
import { recoilPersist } from "recoil-persist"; //追加

//標準でrecoil-persistというkey名でwebstorageに保存される(オプションで指定可能)
const { persistAtom } = recoilPersist();

export const countState = atom({
    key: "count",
    default: 0
});

export const userState = atom({
    key: "user",
    default: {
        name: "hoge",
        age: 11
    },
    effects_UNSTABLE: [persistAtom] //userStateにだけこの記述を追加
});

実行して動作確認してみます。
count, userどちらの値も変更してからブラウザを更新するとuserの方の値だけ保持されています。

スクリーンショット 2021-08-31 7.47.02.png

WebStorageを確認するとuserの値が保持されているのが確認できます(平文なので保存内容には注意を)。

その他

他にselectorとかもありますが、ここまでで多くの用途に対応できるでしょう。
Reduxも悪くはないですが、recoilの簡単さを知るともう戻れないですね。

6
5
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
6
5