Recoil触ってみた

f:id:yun8boo:20200528150941p:plain
Recoil入門

こんにちは。フロントエンドエンジニアの渡邉です。 最近フロントエンド界隈で盛り上がっているRecoilについて学びました。

本記事は自分のRecoil入門のついでに記事にしたので、初級者向けになっています。

目次

  • Recoilとは
  • 使ってみる
  • API Referenceを読む
  • 参考サイト

Recoilとは

Fecebookが新しく発表したのReactの状態管理ライブラリです。 公式ドキュメント

使ってみる

何からやったらいいか分からない人もいるかも知れないので自分の学習手順を紹介しつつ実際に触っていきます。

  • 公式ドキュメントのGetting Started & BasicTutorialをやる
  • Recoilについて書かれている記事を読む
  • 公式ドキュメントのAPI Referenceを読む

みたいな感じで学習しました。

Getting Startedでは入力した文字を出力するのとその入力された文字数カウントを出力するアプリを作っていきます。 f:id:yun8boo:20200528150902g:plain

この記事でもGetting Startedを一緒にやっていきます。

まずはアプリケーション作成・移動

npx create-react-app my-app
cd my-app

recoil install

npm install recoil
or
yarn add recoil

RecoilRoot

recoilを使うにはルートコンポーネントRecoilRootでくくる

App.js

import React from 'react';
import {
  RecoilRoot,
} from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

CharacterCounterでは入力フォームと入力された文字を出力するTextInputと入力された文字数を出力するCharacterCountを呼び出しています。

CharacterCounter.js

import React from 'react';
import TextInput from './TextInput';
import CharacterCount from './CharacterCount';

function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

export default CharacterCounter

Recoilでは複数コンポーネントで共有されるステートはatomと呼ばれます。 今回はTextInputで扱うstateをCharacterCountでも使いたいので、atom関数を用いて作ります。 atomの値を読み取るコンポーネントを暗黙的にサブスクライブされるので、atomに更新が入るとそのatomを使用しているコンポーネント全てに再レンダリングが走ります。

export const textState = atom({
  key: 'textState',
  default: ''
})

atomを作るにはkey(一意のID)とデフォルト値を設定します。 更に読み取り書き込みしたい場合は、useRecoilStateを使います。 useRecoilStateはuseStateみたいな感覚で使えます。 デフォルト値がuseStateと違い、先程定義したatomを引数に受け取ります。

TextInput.js

import React from 'react';
import { atom, useRecoilState } from 'recoil';

export const textState = atom({
  key: 'textState',
  default: ''
})

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

export default TextInput

CharacterCountでは先程定義したtextStateを使ってその文字数をレンダーします。 atomの値から計算し、別の値として出すにはselector関数を使います。

const characterCountState = selector({
  key: 'characterCountState',
  get: ({get}) => {
    const text = get(textState) // getの引数にstateを渡す。
    return text.length
  }
})

Recoilではatomselectorを合わせてstateと呼びます。 どういうことかと言うと、先程使用したuseRecoilStateはatomだけではなくselectorに対しても使えます。 atomとselectorはグローバルな値を提供するという点で共通しています。 違いとしては、atomは自身が値を持っており、selectorはatomから算出された値というところです。

atomと同様にuseRecoilStateを使い値を取り出せます。 ですが、今回必要なのは値の読み取りだけなのでその場合にはuseRecoilValueを使います。 逆に書き込みだけしたい場合にはuseSetRecoilStateを使います。 上記3つのhooksをまとめると

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

// useRecoilState
const [hoge, setHoge] = useRecoilState(hogeState)
// useRecoilValue
const hoge = useRecoilValue(hogeState)
// useSetRecoilState
const setHoge = useSetRecoilState(hogeState)

// useRecoilStateで値だけ取る場合
const [hoge] = useRecoilState(hogeState)
// useRecoilStateで更新関数だけ取る場合
const [, setHoge] = useRecoilState(hogeState)

今回はuseRecoilValueを使って値を読み取ります。

CharacterCount.js

import React from 'react';
import { selector, useRecoilValue } from 'recoil';
import { textState } from './TextInput'

const characterCountState = selector({
  key: 'characterCountState',
  get: ({get}) => {
    const text = get(textState)
    return text.length
  }
})

function CharacterCount() {
  const count = useRecoilValue(characterCountState)
  return (
  <p>character count: {count}</p>
  );
}

export default CharacterCount

これでGetting Startedのアプリは完成です。

API Referenceを読む

Getting Startedだけやっても基本的なことしかわからないので、Getting Startedを終えたらAPI Referenceを読みました。 この記事ではRecoilで基礎的な概念のatomselectorを紹介します。

atom()

atom(options)

  • options
    • key: atomを識別する一意の文字列 アプリケーション全体で他のatom,selectorに対して一意のである必要がある
    • default: atomの初期値
selector()

selector(options)

  • options
    • key : atomと同じくselectorを識別する一意の文字列
    • get : 引数からgetを受け取るget関数(ややこしすぎる)
      • get : get関数から受け取ったgetにstateを渡すことでそのstate値を用いることができる。

      getにわたしているstateは暗黙的にリストに追加されるのでstateが更新入るとselectorは再評価されます

    • set? : このプロパティが設定されると書込み可能なselectorになります。getとsetをパラメータとしてオブジェクトを渡す関数
      • get : set関数から受け取るgetは渡したatom/selectorにセレクターをサブスクライブしない
      • set : 他のatom/selectorに書き込むためのset関数

setはReference読んだだけだとちゃんと理解できなかったので簡易なコードを見ながら説明します。

流れを先に記載しておくのでコードと照らし合わせながら読んでみてください。

sCount 初期値0

  1. setSCount(sCount + 1) 実行
  2. set関数で受け取った他のatomを書き込むための(今回の場合はnomalCountに書き込む)set関数と、newValue(1)を受け取る
  3. atomに書き込むためのset関数にnomalCountを渡し、newValueを10倍にして返す
  4. nomalCountが更新されたのでspecialCountのgetが実行されnomalCountの値を返す
  5. sCountが更新され、描画
import React from 'react'
import {atom, selector, useRecoilState} from 'recoil';

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

const specialCount = selector({
  key: 'specialCount',
  get: ({get}) => get(nomalCount),
  set: ({set}, newValue) => set(nomalCount, newValue * 10),
});

export default function SpecialCounter() {
  const [sCount, setSCount] = useRecoilState(specialCount);

  const addSCount = () => setSCount(sCount + 1);

  return (
    <div>
      <p>specialCount: {sCount}</p>
      <button onClick={addSCOunt}>add special</button>
    </div>
  );
}

参考サイト

https://blog.uhy.ooo/entry/2020-05-16/recoil-first-impression/

まとめ

普段自分はReduxを使って状態管理をしています。ReduxとRecoilでは、stateの持ち方が異なることをがわかりました。 Reduxでは、ロジックや画面ごとにreducerを持ち、combineReducersを使って一つのreducerにまとめます。 stateを使う場合はuseSelectorを使ってstoreのstateから自分が必要なデータを取り出します。

一方Recoilはそもそも局所的にstateをもち、stateを利用するコンポーネント間でのみ共有します。

他にもRecoilではReduxみたいにstoreを作ってreducer作ってaction作ってみたいな作業がなくて、すぐに取り掛かれるのが個人的には嬉しかったです。

最後に、株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひエンジニア採用サイトをご覧ください。