本文內容將提及以下內容
先前的文章解決propDrilling問題—簡化consumer的useContext提及useContext將會有效能隱憂,因此在第三方套件庫當中發展了許多狀態管理的library,其中以redux最為大宗,我們透過useReducer和useContext來製作reducer和context環境,接下來將來演示如果使用redux的解決方案又是如何設定。
以下將以目前redux react推薦的toolkit版本實作to do list
在src底下的資料夾結構,store資料夾包含toDoSlice用來產生reducer、action等等,而index.js撰寫store的設定。
│ App.js
│ index.js
│
├─components
│ └─Todo
│ AddToDo.jsx
│ index.jsx
│ ListToDo.jsx
│ ToDoItem.jsx
│
└─store
index.js
toDoSlice.js
使用redux的createSlice可以方便產出actions和reducer,我們只要在createSlice
面撰寫name、initialState
和reducers物件
,而reducers物件裡面包含reducer的function,就可以輕易產出了。
我們將分段講解toDoSlice檔案
這裡先從redux的toolkit 引入createSlice
創建一個initialState稍後將帶入createSlice物件
import { createSlice } from '@reduxjs/toolkit';
//宣告一個物件叫做initialState裡面,裡面包含state,這裡的state是一個陣列叫做listDate
const initialState = {
listData: [
{
content: "測試",
id: 409823109843,
done: false
},
{
content: "測試二",
id: 543098209,
done: false
}
]
}
createSlice為一個函式
,其參數要帶入物件,將剛剛創建的initialState作為key和value帶入,另外也需要給一個name和reducers物件其撰寫方式如下
export const toDoSlice = createSlice({
name: 'toDo',
initialState,//簡化的寫法其值是key和value
reducers: {
addToDo: (state, action) => {
const toDo = {
content: action.payload,
id: Date.now(),
done: false
}
state.listData.push(toDo);
},
deleteToDo: (state, action) => {
state.listData = state.listData.filter(
toDoItem => toDoItem.id !== action.payload
);
},
completeToDo: (state, action) => {
const index = state.listData.findIndex((todo) => (todo.id === action.payload));
state.listData[index].done = !state.listData[index].done
}
},
})
將addToDo和deleteToDo和completeToDo導出稍後的useDispatch將會用到。
另外也會將toDoSlice.reducer設為default導出,等等configureStore將會使用。
export const { addToDo, deleteToDo, completeToDo } = toDoSlice.actions;
//將toDoSlice.reducer設為default導出,等等configureStore將會使用
export default toDoSlice.reducer
我們可以console.log(toDoSlice)
觀看createSlice幫我們產出的內容如下圖
以下檔案將透過configureStore設定store
第一種方式可以將剛剛從toDoSlice建立的toDoReducer引入然後交給combineReducers函式製作,所回傳的值可以給configureStore使用
。
第二種方式是自己撰寫reducer的物件,其物件內容就是放入剛剛在toDoSlice所建立的toDoReducer
import { configureStore } from '@reduxjs/toolkit'
import { combineReducers } from 'redux';
import toDoReducer from './toDoSlice';
// 方法一 如果有多個slice的話可以透過combineReducers將其結合
// const reducer = combineReducers({ toDoReducer })
// const store = configureStore({
// reducer
// })
// 方法二 直接在物件裡面宣告一個key為reducer值為你所創建的reducer
//透過configureStore的方式設定store
const store = configureStore({
reducer: {
toDoReducer
}
})
export default store;
如同useContext的用法,其store需要包含所有需要用到的元件,以便後續某個component需要使用,如下列程式碼
我們將剛剛store所建立的檔案放入作為Provider的value
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'
import App from './App';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
入useDispatch作為用來派發事件的函式
,其引入addToDo是從toDoSlice導出的actionCreator函式,addToDo帶入的參數就是payload。
import { useState } from 'react';
import { useDispatch } from "react-redux";
import { addToDo } from '../../store/toDoSlice'
const AddToDo = () => {
//in-line style的部分
const margin0Auto = { width: "300px", margin: "0 auto" };
const textAlign = { textAlign: "center" };
//引入useDispatch作為稍後派發事件使用
const dispatch = useDispatch();
//使用Controlled component
const [input, setInput] = useState("");
const inputChange = (e) => {
setInput(e.target.value);
}
//透過從toDoSlice引入的addToDo函式,其函式為一個actionCreator
//所以我們將input作為payload帶入即可。
return (
<div style={{ ...textAlign, ...margin0Auto }}>
<input type="text" value={input} onChange={inputChange} />
<button onClick={() => {
dispatch(addToDo(input));
setInput('');
}}>add</button>
</div>
)
}
export default AddToDo
另外我們也可以嘗試著console.log(addToDo("test"))
看看。
如下圖,此函式的確回傳一個action物件
這邊引入useSelector將state提取出來
,我們需要的是listData,因此使用解構的方式將listDate提取後作為遍歷ToDoItem的array
import React from 'react'
import ToDoItem from './ToDoItem';
import { useSelector } from "react-redux";
const ListToDo = () => {
//in-line style的部分
const margin0Auto = { width: "300px", margin: "0 auto" };
//透過useSelector將store的state
const {listData} = useSelector((state) => state.toDoReducer);
return (
<ul style={margin0Auto} >
{listData.map((data) => {
return <ToDoItem key={data.id} content={data.content} id={data.id} done={data.done} />
})}
</ul >
)
}
export default ListToDo
當然我們也可以使用console.log(useSelector(state => state))
;
其回傳如下圖
這邊的toDoReducer是來自於我們store的configureStore的key值,而state就是當初initialState的值
最後同理引入deleteToDo和completeToDo這兩個actionCreator函式透過useDispatch派發action
import { deleteToDo, completeToDo } from '../../store/toDoSlice'
import { useDispatch } from "react-redux";
const ToDoItem = ({ id, content, done }) => {
const margin10 = { margin: "10px" };
const displayFlex = { display: "flex", justifyContent: "center", alignItems: "center" };
const displayBlock = { display: "block" };
const dispatch = useDispatch();
return (
<li style={{ ...margin10, ...displayFlex }}>
<input type="checkbox"
checked={done} onChange={() => { dispatch(completeToDo(id)) }}
/>
<p style={
{ textDecoration: done ? 'line-through' : 'none' }
}
>
{content}
</p>
<button style={{ ...margin10, ...displayBlock }} onClick={() => {
dispatch(deleteToDo(id))
}}
>
delete
</button>
</li >
)
}
export default ToDoItem
藉由實作to do list理解redux的運作方式,可以發現在實作上面需要許多步驟,有時候我們僅是要將狀態的邏輯部分抽離出來或是state的變更並不會時常操作時仍然可以使用useContext來解決狀態管理,當應用程式大到一定的數量級以上的時候就能考慮使用redux的做法。
本系列告一段落了,希望以上希望對大家有所幫助,