個人開発でExpo + React Nativeでアプリを作っているんですが、認証しているユーザーの情報など、アプリ全体でグローバルに変数を持ちたいという気持ちが高まってきました。
React標準でContext APIというものがあるけども、前回そっちを試したので今回はRecoilを触ってみます。
Expo + React NativeのContext APIでユーザー認証情報を引き回すサンプル
新しいプロジェクトの生成とRecoilのインストール
expo cli
で新しいプロジェクトを生成。TypeScript
のblankでやります(別になんでもいい)。
$ expo init RecoilSample ❯ blank (TypeScript)
生成が済んだらRecoil
をインストール。
$ cd RecoilSample $ expo install recoil
公式ドキュメントに従って試す
公式のGetting Startedを参考にして動かしてみます。公式はReact Native
ではなくReact
での実装例が載っているので適当にReact Native
に置き換えてます。
App.tsx
公式の例だと、一つのファイルに全部書く感じですが、それだとグローバルの状態管理の良さがまるで分からないので、コンポーネントごとに別ファイルにしてます。
import React from 'react'; import { StyleSheet, View } from 'react-native'; import { RecoilRoot } from 'recoil'; import CharacterCounter from './CharacterCounter'; export default function App() { return ( <RecoilRoot> <View style={styles.container}> <CharacterCounter /> </View> </RecoilRoot> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, });
CharacterCounter.tsx
import React from 'react' import { View } from 'react-native' import TextInputWithPrint from './TextInputWithPrint' import CharacterCount from './CharacterCount' export default function CharacterCounter() { return ( <View> <TextInputWithPrint /> <CharacterCount /> </View> ); }
TextInputWithPrint.tsx
import React from 'react' import { TextInput, Text, View } from 'react-native' import { useRecoilState } from 'recoil' import { textState } from './atoms/Text' export default function TextInputWithPrint() { const [text, setText] = useRecoilState(textState); return ( <View> <TextInput style={{ height: 40, borderColor: 'gray', borderWidth: 1 }} value={ text } onChangeText={text => setText(text)}></TextInput> <Text>Echo: { text }</Text> </View> ); }
CharacterCount.tsx
import React from 'react' import { Text } from 'react-native' import { selector, useRecoilValue } from 'recoil' import { textState } from './atoms/Text' const charCountState = selector({ key: 'charCountState', get: ({get}) => { const text = get(textState); return text.length; }, }); export default function CharacterCount() { const count = useRecoilValue(charCountState); return ( <Text>Character Count: {count}</Text> ) }
atoms/Text.ts
Atom
というのは、グローバルに管理されるステートのオブジェクトのことで、atom
関数を使ってどんなキーでどんな値が保存されるかを宣言することが出来る。
いろんな箇所で使い回されるはずなのでたぶん別ファイルに切り出すべきなんだろうなと思っている。
参考: https://qiita.com/serinuntius/items/3d6519988233d7ba643c
import { atom } from 'recoil' export const textState = atom({ key: 'textState', default: 'hoge', });
こんな感じに動く。
Redux
とRecoil
で並べて登場することが多かったので、使ってみる前は、割と重厚なライブラリなのだろうか?って想像してたけど、どうやら単にContext APIの部分だけを扱っているライブラリなので全然そんなことなかった。
Context APIと出来ることはそう大差ないようには思えるが、比較的記述は直感的だと思う。hooksと同じようなインターフェースで使えるのでこっちの方が使いやすいなあという感想。
おまけ
開発中にしょっちゅう下記のエラーが出る。正確に説明出来るかはあやしいけど、Expoには、ソースコードの反映が再ビルドなしに行われるHMR(Hot Module Replacement)という仕組みが備わっている。
おかげで開発がサクサク進んで大変すばらしいんだけども、どうやらソースコードを修正するたびにatom
の宣言が行われてしまう?らしく、キーが重複してるよ!って怒られている。
参考ページにある通り、今はどうしてもエラーが出てしまうので、とりあえず無視するしかないねとのこと。
Duplicate atom key "textState". This is a FATAL ERROR in production. But it is safe to ignore this warning if it occurred because of hot module replacement., undefined
参考: https://file-translate.com/ja/blog/recoil-duplicate-atom-key