🏗️
Redux / Redux Toolkitで状態管理
Reduxとは
fluxを拡張した状態管理フレームワーク。
Reactとよくセットで扱われる。
引用元:ABC of Redux - DEV Community(https://dev.to/radiumsharma06/abc-of-redux-5461)
よく見るフロー図。
UI
ユーザーの入力(View)から値を入力し、Actionsに伝える。
Actions
typeとその内容を持ったオブジェクト。
{
type: 'ADD_USER',
contents: {
name: 'taro',
email: 'taro.example.com'
}
}
const addUser = () => {
return {
// ADD_USER
};
}
Dispatcher
生成されたActionsをStoreに送る
dispatch(addUser(contents));
Store
Store, Stateはアプリケーションに1つのみ存在する。
- 状態(State)を保持する
- StateにアクセスするためのgetStateを提供する
- Stateを更新するためのdispatch(action)を提供する
- リスナーを登録するためのsubscribe(linstner)を提供する
State
アプリケーションの状態。
今回の場合、ユーザー情報(名前、メールアドレス)を保持する。
{
users: [
{
contents: {
name: 'foo',
email: 'foo@example.com'
}
},
{
contents: {
name: 'bar',
email: 'bar@example.com'
}
}
]
}
Reducer
ActionsとStateから、新しいStateを生成して返すメソッド。
Reducerは元のStateを更新しない純粋関数でなければいけない。
function userApp(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return Object.assign({}, state, {
todos: [
{
contents: action.contents,
}
]
})
]
default:
return state
}
}
Reducerが肥大化しないように、子のReducerを作成できる。
function users(state = [], action) {
switch (action.type) {
case ADD_USER:
return [
...state,
{
text: action.contents,
}
]
default:
return state
}
}
function userApp(state = {}, action) {
return {
users: users(state.user, action)
}
}
Redux Toolkitとは
Reduxは小さい構成でもコード量が多く敬遠されがちだが、
Redux Toolkitを使うことでより少ないコード量で実装することが可能になる。
今回は作業の都合上Typescriptで実装。
インストールするパッケージは以下。
redux
react-redux
redux-logger
@reduxjs/toolkit
@types/react-redux
@types/redux-logger
Slice / createSlice
reducerとactionを同時に定義ができる。
pages/store/user/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export type UserState = {
name: string;
email: string;
isSignIn: boolean;
};
export const initialState: UserState = {
name: '',
email: '',
isSignIn: false,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
updateUser: (state, action: PayloadAction<UserState>) => ({
...state,
name: action.payload.name,
email: action.payload.email,
isSignIn: action.payload.isSignIn,
}),
},
});
export default userSlice;
Store
Store部分の実装。
pages/store/store.ts
import { Store, combineReducers } from 'redux';
import { logger } from 'redux-logger';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import userSlice, { initialState as userState } from './user/slice';
const rootReducer = combineReducers({
user: userSlice.reducer,
});
const preloadedState = () => {
return {
user: userState,
};
};
export type StoreState = ReturnType<typeof preloadedState>;
export type ReduxStore = Store<StoreState>;
const store = () => {
const middlewareList = [...getDefaultMiddleware(), logger];
return configureStore({
reducer: rootReducer,
middleware: middlewareList,
devTools: process.env.NODE_ENV !== 'production',
preloadedState: preloadedState(),
});
};
export default store;
Custom App
Storeを使うためにCustom Appを作成。
Next.jsの場合、pagesのルートに作るだけでOK。
pages/_app.ts
import React from 'react';
import { AppProps } from 'next/app';
import { Provider } from 'react-redux';
import store from '../store/store';
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<Provider store={store()}>
<Component {...pageProps} />
</Provider>
);
};
export default MyApp;
PageComponent
ページコンポーネントに追加。
pages/redux_user.ts
import React from 'react';
import { useDispatch } from 'react-redux';
import { userUserState } from '../store/user/selectors';
import userSlice from '../store/user/slice';
import Layout from '../components/Layout'
import Link from 'next/link'
const reduxUser: React.FC = () => {
const dispatch = useDispatch();
const state = userUserState().user;
const onClickUpdateUser = () => {
dispatch(userSlice.actions.updateUser({
name: 'foo',
email: 'foo@example.com',
isSignIn: true,
}));
};
return (
<Layout
title="Firebase Auth">
<h1>Redux User</h1>
<div>
<button type="button" onClick={onClickUpdateUser}>UPDATE USER</button>
<ul>
<li>name: {state.name}</li>
<li>email: {state.email}</li>
<li>isSignIn: {state.isSignIn ? 'COMPLETED' : ''}</li>
</ul>
</div>
<ul>
<li>
<Link href="/">
<a>Go home</a>
</Link>
</li>
</ul>
</Layout>
);
}
export default reduxUser;
[UPDATE USER]ボタンを押すと..
updateUserイベントが叩かれてviewが更新されるはず。
Discussion