LoginSignup
31
32

More than 1 year has passed since last update.

5分で使えるようになるRecoil

Last updated at Posted at 2022-02-04

「Reactの提供元であるFacebook(Meta)が開発中のナウでヤングな状態管理ライブラリ『Recoil』の噂は聞いてはいるけど、Context API とかReduxでなんとかなってはいるし、使い方とか勉強するのめんどくさいなぁ」

という忙しい現代人に5分で『使い方』のエッセンスがわかる記事がここに。

この記事では、Recoilの設計思想や他の状態管理ライブラリーとの比較は行いません。
あくまで、『使い方』を最短時間でわかるようになることにフォーカスします。

とりあえず、Next.jsで作ったデモアプリ

技術書が買える架空のショッピングカート的なやつをとりあえず、Next.jsとRecoilで作ってみた。

コンポーネントは四つ

Bookコンポーネント

Cartコンポーネント

Couponコンポーネント(Cartの子コンポーネント)

Totalsコンポーネント(Cartの子コンポーネント)

グローバルな状態は3つ

-recoill
   └──atoms
   │    ├──cartState
   │    └──couponState
   └──selectors
        └──totalSelector

カートの状態を保持するcartState, クーポンの状態を保持するcouponState, そしてcartStateとcouponStateの値によって計算された合計値を返すtotalsSelectorの3つ

atomsとかselectorってなんなん?って感じかもしれませんが、とりあえず、この後使えるように説明しますので、少々お待ちください。

まずはrecoilをインストール

yarn add recoil

or

npm install recoil

RecoilRootでappをラップする

recoilによる状態管理を使うにはRecoilRootが親コンポーネントにいなければなりません。
とりあえずrootコンポーネントをRecoilRootで囲っておくのが無難でしょう。(Next.jsなら_app.js)

_app.js
import { RecoilRoot } from 'recoil';

function MyApp({ Component, pageProps }) {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
}

export default MyApp;

グローバルで使えるstateをatomで定義しよう

『atomってなんやねん?鉄腕アトムか?』
とか色々思うことはあると思いますが、とりあえず、recoilという状態管理ライブラリーでグローバルに管理される個別の状態のことをatomっていうみたいです。

そんな適当な説明じゃ納得できないあなたに、公式の説明を
An atom represents a piece of state. Atoms can be read from and written to from any component. Components that read the value of an atom are implicitly subscribed to that atom, so any atom updates will result in a re-render of all components subscribed to that atom

deeplで翻訳すると
アトムは状態の断片を表します。アトムはどのコンポーネントからでも読み書きが可能です。アトムの値を読み取るコンポーネントは、暗黙のうちにそのアトムを購読しているので、アトムの更新は、そのアトムを購読しているすべてのコンポーネントの再レンダリングにつながります。

やはり公式ドキュメントとdeeplは神 公式ドキュメントとdeeplが全てを解決する

はい。じゃあ、カートのatomを定義してみよう。

/recoil/atoms/cartState.js
import { atom } from 'recoil';

const cartState = atom({
  key: 'cartState',
  default: {}
});

export default cartState;

atomのkeyはuniqueでなければならず、他のatomやselectorと重複してはいけません。
defaultは初期値

atomの値を操作してみよう

では、Bookコンポーネントの『カートに追加』ボタンを押したら、cartにbookが一冊追加されるようにしてみよう。

Book.jsx
import { useRecoilState } from 'recoil';
import cartState from '../recoil/atoms/cartState';
const Book = ({ id, data }) => {
  const [cart, setCart] = useRecoilState(cartState);
  const addBookHandler = (id) => {
    setCart({ ...cart, [id]: (cart[id] || 0) + 1 });
  };
 return (...)

めっちゃ簡単ですね。
atomの値を変更する処理をしたいときは、useStateフックのように、useRecoilStateを使うだけです。
useRecoilStateの引数には操作したいatomをいれてあげます。
今回ですと

const [cart, setCart] = useRecoilState(cartState);

ですね。

atomの値を表示しよう

CartコンポーネントでcartStateの状態を表示してあげましょう。

Cart.jsx
import { useRecoilValue } from 'recoil';

import cartState from '../recoil/atoms/cartState';

const Cart = ({ data }) => {
  const cart = useRecoilValue(cartState);

  if (Object.keys(cart).length === 0) return <p>カートは空です。</p>;
  return (
    <div className='cart_container'>
      <ul className='cart_items'>
        {Object.entries(cart).map(([id, quantity]) => (
          <li key={id}>
            {booksData[id].title} x <span className='cart_quantity'>{quantity}</span>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Cart;

いや、めっちゃ簡単ですね。atomの値を表示したいときに使うのはuseRecoilValueです。引数に目的のatomを渡してあげるだけです。

Selectorを定義してみよう

Selectorは複数のAtom・他のSelectorを受け取って処理をした値を返す動的なデータをつくりたいときに使えます。
えっ適当すぎる? じゃあ公式ドキュメント

A selector represents a piece of derived state. Derived state is a transformation of state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way:

deepl

セレクタは、派生した状態の一部を表します。派生状態とは、状態の変換のことです。派生状態は、与えられた状態を何らかの方法で変更する純粋な関数に状態を渡したときの出力と考えることができます。

はい。じゃあ、カートのatomとクーポンのatomを受け取って合計値を返すtotalsSelectorを定義してみよう。

/recoil/selectors/totalsSelector.js
import { selector } from 'recoil';
import cartState from '../atoms/cartState';
import couponState from '../atoms/couponState';
import booksData from '../../data/booksData';
import couponsData from '../../data/couponsData';

const totalsSelector = selector({
  key: 'totalsSelector',
  get: ({ get }) => {
    const cart = get(cartState);
    const coupon = get(couponState);
    const subtotal = Object.entries(cart).reduce((acc, [id, quantity]) => acc + booksData[id].price * quantity, 0);
    const couponDiscount = couponsData[coupon].discount;
    return {
      subtotal,
      coupon: couponsData[coupon].name,
      discount: couponDiscount,
      total: (subtotal * (100 - couponDiscount)) / 100,
    };
  },
});

export default totalsSelector;

getのreturnにコンポーネントで使いたい値を入れて返してあげます。
他のatomやselectorの値は、
const cart = get(cartState);
みたいな感じで取得して、それに処理を加えた値をreturnに入れてあげるって感じですね。

Selectorの値を表示しよう

では、定義したtotalsSelectorの値をTotalsコンポーネントで表示してみましょう。

Totals.jsx
import { useRecoilValue } from 'recoil';
import totalsSelector from '../recoil/selectors/totalsSelector';

const Totals = () => {
  const totals = useRecoilValue(totalsSelector);
  return (
    <div className='totals'>
      <div className='totals_cost'>
        <p>
          小計:<span className='totals_cost_num'>{totals.subtotal}</span>
        </p>
        <p>
          クーポン:<span className='totals_cost_num'>{totals.coupon}</span>
        </p>
        <p>
          請求:<span className='totals_cost_num'>{totals.total}</span>
        </p>
      </div>
    </div>
  );
};

export default Totals;

はい。selectorの値を表示するときは、atomのときと同じでuseRecoilValueを使います。引数に渡すのがselectorになるだけです!
めっちゃシンプル!!

はい、終わり

ここまでで、グローバルな状態を定義して、値を操作したり、表示したり、複数の値に対応して変化する値を定義したりできました。
Recoilってなんか難しそうなイメージあったかもしれませんが、めちゃくちゃシンプルですよね。
5分で使えるようになりましたか?
もし、そうだったら幸いです。

今回作ったrecoilが端的にわかるデモコードは
https://github.com/70ki8suda/recoil-demo
にアップしてありますので、みなさまご自由にご参照ください。

最後までお読みいただき、ありがとうございました。

31
32
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
31
32