これはなに
redux-toolkitというreduxのラッパーライブラリ(公式推奨)の素振りをしてみた記事です。
前々から状態管理をちゃんとできるようになりたかったので今回備忘録がてら書いています。
サンプルとしてはtwitteridを取得してきて画面に表示します。
本編
まずはじめにフォルダの構成はこんな感じです。
create-react-app フォルダ名 --template typescript
で作ったので今回使わないものも混じっています。
yarn add react-redux @reduxjs/toolkit firebase
次にコレを実行します。
ちなみにfirebase.tsにfirebaseのinitialize用のやつが入っています。
import firebase from 'firebase' if(!firebase.apps.length){ let firebaseConfig = { apiKey: process.env.REACT_APP_FIREBASE_APIKEY, authDomain: process.env.REACT_APP_FIREBASE_AUTHDOMEIN, projectId: process.env.REACT_APP_FIREBASE_PROJECTID, storageBucket: process.env.REACT_APP_FIREBASE_STORAGEBUCKET, messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGINGSENDERID, appId: process.env.REACT_APP_FIREBASE_APPID, measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENTID }; firebase.initializeApp(firebaseConfig) } export default firebase
一応こんな感じです。
ファイルごとのイメージとしてはstores/twitter.tsにてreduxの関数とか状態回りの記述を行った上でstore.tsにてstoresのredux情報をまとめ上げてcomponents/Twitter.tsxから呼び出すという感じですね。
ここでreduxのデータフローのお話を少ししておきます。
このgifがreduxの基本的な流れです。
uiのクリックによってactionが発行され、それがstoreにdispatchされることで現在のstateと組み合わせて新たなstateとして発行されます。
コード的な話をするとactionを書いて、storeを操作するreducerを書いて、あとはそれらをいろいろ結合して、みたいに様々なコードが必要になってきます(わからん人は調べてください、公式tutorialが良いです)。
その面倒くささを緩和してくれるのがredux-toolkitです。なんとactionをreducerを一箇所に書くことが可能です。それではコードを見ていきましょう。
import {createSlice, PayloadAction} from '@reduxjs/toolkit' export type TwitterState = { id: string; } const initialState: TwitterState = { id: "" } export const twitterSlice = createSlice({ name: 'twitter', initialState: initialState, reducers: { updateId: (state, action: PayloadAction<string>) => { return { ...state, id:action.payload } } } })
まずはstores/twitter.tsです。redux-toolkitではSliceインスタンスがreducerやactionの生成をしてくれます。
nameによってactionの名前空間を管理してるみたいです。これだけでreduxで状態管理できるなら使ってみようってなりませんか??(僕はなりました)また、TwitterStateをexportしていることからもわかるとおり型定義をふんだんに使って書けます。
import { combineReducers } from 'redux' import { configureStore } from '@reduxjs/toolkit' import { twitterSlice, TwitterState } from './stores/twitter' export type AppState = { twitter: TwitterState; } const rootReducer = combineReducers<AppState>({ twitter:twitterSlice.reducer }) const store = configureStore({reducer:rootReducer}) export default store;
次にstore.tsです。結合処理です。今回は一つしかないですがstores/が増えるとAppStateやrootReducerの中身が増えます。
import React, { FC, useEffect } from "react"; import firebase from "../plugins/firebase"; import { useDispatch, useSelector } from "react-redux"; import { twitterSlice } from "../stores/twitter"; import { AppState } from "../store"; const TwitterLogin: FC = () => { const { twitterId } = useSelector<AppState, { twitterId: string }>((state) => { return { twitterId: state.twitter.id, }; }); const dispatch = useDispatch(); const { updateId } = twitterSlice.actions; useEffect(() => { (async () => { firebase .auth() .getRedirectResult() .then((result) => { console.log(result.additionalUserInfo); dispatch(updateId(result.additionalUserInfo?.username?result.additionalUserInfo?.username:"")) }); })(); }, [updateId, dispatch]); const LoginHandler = async () => { const provider = new firebase.auth.TwitterAuthProvider(); firebase .auth() .signInWithRedirect(provider) .then((result) => { console.log("ok"); }); }; return ( <div> <button onClick={LoginHandler}>PUSH</button> {twitterId.length > 0 ? twitterId : "LOADING"} </div> ); }; export default TwitterLogin;
そしてcomponents/Twitter.tsxです。useSelector<A,B>のAにはstateの型情報、Bには実際にその関数で使いたい情報を指定してやるといいです。
あとはtwitterSlice.actionsをuseDispatchにて生成した関数を用いで読んでやればreduxを扱うことができます。
おまけとしてtwitter idを取得してstoreに突っ込むコードが書いてあります(余談ですがidの取得はloginした上でgetRedirectResultを呼ばないと取得できないみたいですなんで....)。
redirect処理を挟むのでuseEffectでtwitter idを取得してやる処理が妥当だと思います(他にいいやり方あったら教えてください)。
import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; import { Provider } from "react-redux"; import store from "./store"; ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
最後にindex.tsxでAppの親としてProviderで囲ってやれば完成です。
あとがき
reduxなんとなく敬遠してたんですけどめちゃめちゃ使いやすくなっていていいですね。
あとreduxの流れを掴むためにいろいろ読みましたが公式tutorialが一番わかりやすかったのでおすすめです。
リンクとか
- redux
- redux-toolkit
- 参考にさせていただいたブログ
実装↓ github.com