- Published on
絶対に理解するReact Redux
- Authors
- Name
- Kikusan
- React Reduxとは
- Redux Data Flow
- tools
- Redux
- Provider
- Store
- Reducer
- Action
- useSelector/useDispatch
- Redux Toolkit
- Provider
- Store
- Slice
- useDispatch,useSelector
React Reduxとは
Reactコンポーネントに対して一か所で状態を保持しておくライブラリ
Redux Data Flow
- UIで操作
- Eventハンドリング
- ActionをStoreにDispatch
- 以前のStateにReducerでActionを反映
- StateをUIに反映
- Store: 状態を管理する場所
- State: アプリケーション全体の状態
- Action: 状態をどう変化させるかを保持するオブジェクト
- Dispatch: ActionをStoreに通知するメソッド
- Reducer: ActionによってStateを変更するロジック
tools
開発者ツールにタブが追加される。例えばstoreを見るにはstoreにwindow.REDUX_DEVTOOLS_EXTENSION && window.REDUX_DEVTOOLS_EXTENSION()を追加する。
Redux
サンプルソース
Provider
Providerで囲った配下ではstateを使用でき、Dispatchもできる。
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Store
import { createStore } from "redux";
import allReducers from './reducers';
// store
let store = createStore(
allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); // reducerを引数にとる。
Reducer
import { combineReducers } from "redux";
// reducer
const counterReducer = (state = 0, action) => { // stateの初期値 action戻り値を引数にとる
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
// 中略
const allReducers = combineReducers({
counter: counterReducer,
isLogin: isLoginReducer,
});
export default allReducers;
Action
export const increment = () => {
return {
type: "INCREMENT",
}
}
export const decrement = () => {
return {
type: "DECREMENT",
}
}
useSelector/useDispatch
useSelectorでstateの値を取得、useDispatchでActionを通知。
import { useDispatch, useSelector } from 'react-redux';
import { decrement, increment } from './actions';
function App() {
// stateの取得
const counter = useSelector((state) => state.counter);
const isLogin = useSelector((state) => state.isLogin);
const dispatch = useDispatch();
return (
<div className="App">
<h1>Hello Redux</h1>
<h3>count: {counter}</h3>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
export default App;
Redux Toolkit
Reduxをより簡潔に記述するためのツール
サンプルソース
Provider
Providerで囲った配下ではstateを使用でき、Dispatchもできる。
<Provider store={store}>
<App />
</Provider>
Store
import { configureStore } from "@reduxjs/toolkit"
import cartReducer from "./features/cart/CartSlice" // default exportに命名
import modalReducer from "./features/modal/ModalSlice"
export const store = configureStore({
reducer: {
cart: cartReducer,
modal: modalReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
Slice
SliceはAction(ActionCreator)とReducerとStateを同時に定義するもの。
関連するオブジェクトを同時に管理できる。
import {createSlice} from "@reduxjs/toolkit"
import cartItems from "../../cartItems"
import { CartItemType } from "../../components/CartItem"
type cartState = {
cartItems: CartItemType[],
amount: number,
total: number,
}
// 買い物かごの初期化
const initialState: cartState = {
cartItems: cartItems,
amount: cartItems.reduce((summ: number, current: CartItemType) => summ + current.amount, 0),
total: cartItems.reduce((summ: number, current: CartItemType) => summ + current.price * current.amount, 0),
}
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
clearCart: (_) => { // actionsに配置され、action名になる。 -> type: "cart/removeItem";
return { cartItems: [], amount: 0, total: 0 } // 戻り値がstateになる。
},
removeItem: (state, action) => { // actionにはpayloadとtypeが乗っている typeは自動で決まり、payloadはactionの引数が入る
state.cartItems = state.cartItems.filter(item => item.id !== action.payload)
},
increase: (state, action) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload)
if (cartItem) {
cartItem.amount = cartItem.amount + 1;
}
},
decrease: (state, action) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload)
if (cartItem) {
cartItem.amount = cartItem.amount - 1;
}
},
calculateTotals: (state) => {
let amount = 0;
let total = 0;
state.cartItems.forEach((item) => {
amount += item.amount
total += item.amount * item.price;
})
state.amount = amount;
state.total = total;
},
},
})
export const { clearCart, removeItem, increase, decrease, calculateTotals } = cartSlice.actions
export default cartSlice.reducer
useDispatch,useSelector
useSelectorでstateの値を取得、useDispatchでActionを通知。
import { useDispatch, useSelector } from "react-redux"
import { RootState } from "../store"
import CartItem from "./CartItem"
import { openModal } from "../features/modal/ModalSlice"
const CartContainer = () => {
const { cartItems, amount, total } = useSelector((state: RootState) => state.cart)
const dispatch = useDispatch();
return (
<section>
<header>
<h2>買い物かご</h2>
</header>
{ amount < 1
? <h4 className="empty-cart">何も入っていません・・・</h4>
: <div>
{cartItems.map((item) => <CartItem key={item.id} {...item}/>)}
</div>
}
<footer>
<hr />
<div className="cart-total">
<h4>合計{ total }円</h4>
</div>
<button className="btn clear-btn" onClick={() => dispatch(openModal())}>全削除</button>
</footer>
</section>
)
}
export default CartContainer