Reduxを思い出しながらRedux toolkitへ移行してみる
Reduxを使う機会があるのですが、Redux自体は最近だと使わなくてもReactのcontextさえあれば代替出来てしまったり、そもそも使わなくても行けてしまうことも多く、完全に使い方を忘れていました。
その為、Reduxってそもそもどうやって使うんだっけ?(そもそも使っていたものReactがクラスコンポーネントだった時代)を振り返りつつ、Redux Toolkitの使い方も調べてみました。
React HooksでRedux(toolkitじゃない方)を使う
①store
まずは、store
を設定し、Provider
に渡す。
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import ConnectedTodo from "./components/Todo";
import { createStore } from "redux";
import rootReducer from "./redux/reducer";
import reportWebVitals from './reportWebVitals';
const store = createStore(rootReducer);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<StrictMode>
<ConnectedTodo />
</StrictMode>
</Provider>,
rootElement
);
reportWebVitals();
②action
ActionのtypeをStringでつくって、ActionCreator関数でObjectを返す。
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";
const COMPLETE_TODO = "COMPLETE_TODO";
export const addTodoCreator = (text) => {
return {
type: ADD_TODO,
text
};
};
export const deleteTodoCreator = (id) => {
return {
type: DELETE_TODO,
id
};
};
export const completeTodoCreator = (id) => {
return {
type: COMPLETE_TODO,
id
};
};
③reducer
actionのtypeに応じて、新しいstateを返します。(mutateしちゃだめ)
let count = 0;
const rootReducer = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [...state, { id: count++, todo: action.text, completed: false, deleted: false }];
case "COMPLETE_TODO":
let todo = [];
state.forEach(item => {
if(item.id === action.id)
item.completed = true;
todo.push(item);
})
return todo;
case "DELETE_TODO":
let todoarr = [];;
state.forEach(item => {
if(item.id === action.id)
item.deleted = true;
todoarr.push(item);
})
return todoarr;
default:
return state;
}
};
export default rootReducer;
最後にstateをReactでレンダリング
import React, {useState} from 'react'
import {deleteTodoCreator,completeTodoCreator, addTodoCreator} from '../redux/action'
import {connect} from 'react-redux'
import styles from './styles.css'
import ConnectedItem from './Item'
const Todo = (todo) => {
const [input, setInput] = useState('');
let todoArr = todo.todo;
function submitTodo(text) {
todo.addTodo(text);
setInput('');
}
return (
<div>
<form>
<h2>Todo Input</h2>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}/>
<button onClick={(e)=> {e.preventDefault(); submitTodo(input)}}>Submit</button>
</form>
{todoArr.length !== 0 ? (<ConnectedItem/>):(<div></div>)}
</div>
);
}
const mapStateToProps = (state) => {
return {
todo: state
}
}
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (todo) => dispatch(addTodoCreator(todo)),
completeTodo: (id) => dispatch(completeTodoCreator(id)),
deleteTodo: (id) => dispatch(deleteTodoCreator(id))
}
}
const ConnectedTodo = connect(mapStateToProps,mapDispatchToProps)(Todo)
export default ConnectedTodo
子コンポーネント:
import React from 'react'
import style from './styles.css'
import {connect} from 'react-redux'
import {deleteTodoCreator,completeTodoCreator, addTodoCreator} from '../redux/action'
const Item = (todo) => {
let todoArr = todo.todo;
function completeTodoItem(id) {
todo.completeTodo(id);
}
function deleteTodoItem(id) {
todo.deleteTodo(id);
}
return (
<ul>
{todoArr.map(item =>
item.deleted === false ?
(<li key={item.id} className="todoitem">
<input type='checkbox' onChange={() => completeTodoItem(item.id)} />
{item.completed === true? (<div>completed</div>):(<div></div>)}
<br/>
{item.todo}
<button onClick={() => deleteTodoItem(item.id)}>Delete?</button>
</li>):(<div></div>)
)}
</ul>
)
}
const mapStateToProps = (state) => {
return {
todo: state
}
}
const mapDispatchToPorps = (dispatch) => {
return {
addTodo: (todo) => dispatch(addTodoCreator(todo)),
completeTodo: (id) => dispatch(completeTodoCreator(id)),
deleteTodo: (id) => dispatch(deleteTodoCreator(id))
}
}
const ConnectedItem = connect(mapStateToProps,mapDispatchToPorps)(Item)
export default ConnectedItem
改めてReduxを書いてみた気づき:
store, reducer, actionを宣言するところまでは良いが、connectするところがめちゃくちゃ面倒というか、書き方やそもそもどういうものなのかという機能も完全に忘れていました。
特にReduxをはじめたばかりのころはmapStateToProps
やmapDispatchToProps
ってそもそも何で、何のためにconnectするのか?をまったく分かっていなかったのですが、長期間触っていなかったのでこれを完全に忘れていました。
簡単に言えば、mapStateToProps
はReduxで管理しているstateをReactのコンポーネントで使えるようにするため、mapDispatchToProps
はReactからaction関数をdispatch出来るようにするためで、connectでコンポーネントに紐づけます。
ここでconnectして初めてReactのコンポーネント上でpropsとしてこのaction関数やstateが使えるようになります。
React HooksでRedux Toolkitを使う
そもそもRedux Toolkitが今回初めてだったのですが、案外簡単にできてしまいました。
①store
Redux toolkitではcreateStore
がconfigureStore
になりました。
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import todoSlice from "./redux/reducer";
const store = configureStore({
reducer: todoSlice.reducer
});
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<StrictMode>
<App />
</StrictMode>
</Provider>,
rootElement
);
②createSlice
最初これを見たときはなんじゃこりゃと思ったのですが、actionとreducerを合体して宣言できるのがcreateSlice
のようです。(別々にやる方もある)
しかも、ReduxやReactだと基本stateは直接いじるのはダメ(don't mutate the state)!🙅ってこっぴどく学ぶと思うのですが、完全に直接いじってるように見えます。
が、この辺りはtoolkitだとImmerを使ってReducerがちゃんとmutateしないように内部でなっているようです。(参照: https://redux-toolkit.js.org/usage/immer-reducers)
Reduxの今までだとどうしてもコード量が最低でも多くなってしまうのが面倒、という人が多かったと思うのですが、toolkitだとcreateSliceなどを使って簡潔に書けるのはよさそうです。
const todoSlice = createSlice({
name: "todo",
initialState: {
todo: []
},
reducers: {
addTodo(state, action) {
state.todo.push({todo: action.payload})
},
toggleTodo(state, action) {
const todo = state.todo.find(todo => todo.id === action.payload)
if (todo) {
todo.completed = !todo.completed
}
}
}
})
export default todoSlice
③Redux Hooksも使っちゃう
Redux Hooksというのも出ており、useSelector
やuseDispatch
が使えることで「Reduxワカラナイ」の原因になっていたmapStateToProps
やmapDispatchToProps
を使わなくてもReduxのstateが取れるようになりました。
これでかなりコード量も減ってシンプルに書けるようになったのではないでしょうか。
import "./styles.css";
import {useSelector, useDispatch} from 'react-redux'
import todoSlice from './redux/reducer'
import {useState} from 'react'
const {addTodo, deleteTodo} = todoSlice.actions;
export default function App() {
const todo = useSelector(state => state.todo)
const dispatch = useDispatch()
const [input, setInput] = useState('')
return (
<div className="App">
<h2>Todo</h2>
<ul>{todo.map(item => <li key={Math.random()}>{item.todo}</li>)}</ul>
<form>
<input type="text" value={input} onChange={(e)=>setInput(e.target.value)}/>
<button onClick={(e) => {e.preventDefault(); dispatch(addTodo(input))}}>Submit</button>
</form>
</div>
);
}
Typescriptはまだ勉強中なので、次はこれにさらにTypescriptも導入してやっていきたいと思います!!