本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。
//todosSlice.js
const todosSlice = createSlice({
name: "todos",
initialState: dummyData,
reducers: {
//新增 todo
addTodo(state, action) {
const { listId, name } = action.payload;
state[listId].todos.push({ name: name, finished: false });
},
},
});
reducers物件裡的key會被轉為action type,像是addTodo會變成 "todos/addTodo",而當有這個type的action被dispath時就會執行addTodo的方法。
寫成arrow function 或許比較好理解:
//todosSlice.js
reducers: {
//新增 todo
addTodo: (state, action)=>{
...
},
},
另外在reducers裡可以看到 state[listId].todos.push這樣的用法,
但不是不應該直接變更state嗎?
這是createSlice提供的另一個便利點,它打包reducers會幫你搞定state immutable的問題,你可以自在的直接撰寫變更state的方法。
上面提到reducers同時建立了action與對應的function,
createSlice會根據action建立 action creator 並打包在actions裡,我們可以把每個action creator作為麼模組輸出。
注意是action creator 方法,不是action物件。
//todosSlice.js
const todosSlice = createSlice({
name: "todos",
initialState: dummyData,
reducers: {
addTodo(state, action) {
const { listId, name } = action.payload;
state[listId].todos.push({ name: name, finished: false });
},
},
});
//輸出addTodo這個 action
export const { addTodo } = todosSlice.actions;
export default todosSlice.reducer;
像上一篇的KanBan一樣,如果component要從store裡取得什麼,建一個container負責這些資料處理的部分。
會用到addTodo的組件是NewTodo,不過單單為了addTodo幫NewTodo建一個container有點費工,所以決定從List傳addTodo進去,在傳給NewTodo。
// containers/ListContainer.js
import { connect } from "react-redux";
import List from "../components/List";
//導入todosSlice裡的action creator
import { addTodo } from "../reducers/todosSlice";
//將action creator包上dispatch後做成props
const mapDispatchToProps = { addTodo };
export default connect(null, mapDispatchToProps)(List);
跟state不一樣的是,只有action creator並不會讓store執行動作,要用dispatch執行action才有用,在connect裡的第二個參數入有action creator的物件就會幫你處理這件事,會根據物件裡的action creator產出dispatch方法。
因為沒用到state所以connect的第一個參數要用null。
List本身沒有要改的,接收跟傳遞的prop名稱都沒變。
NewTodo裡呼的handleAddTodo要做個小改動:
// NewTodo.jsx
function handleAddTodo(e) {
if (e.target.value.trim()) {
//addTodo(listId, e.target.value);
addTodo({ listId, name: e.target.value });
}
e.target.value = "";
toggleShowNew();
}
要把參數打包成單一物件給addTodo,因為creatSlice產生的action creator只能接收一個參數payload,傳到reducer中在解析使用。
reducers: {
addTodo(state, action) {
//解構payload
const { listId, name } = action.payload;
state[listId].todos.push({ name: name, finished: false });
},
},
最後抽換掉KanBan裡的List,刪掉舊的addTodo就大功告成:
//KanBan.jsx
...
// import List from "./List";
import List from "../containers/ListContainer";
...
// 刪除舊的addTodo
// function addTodo(listIndex, newTodo) {...}
return (
<>
<KanBanNav />
<div className="board p-1">
{todos.map((list, index) => (
<List
key={index}
{...list}
listId={index}
// addTodo={addTodo} 刪除
updateEditState={updateEditState}
editListTitle={editListTitle}
updateMenuState={updateMenuState}
/>
))} //替換掉lists
...
</div>
</>
);
剩下的功能逐一辦理,做法都差不多:
所以就不逐一介紹,可以在這個Branch參考結果。
看看這乾乾淨淨的KanBan.jsx,狀態管理的工作都移出去了。
//KanBan.jsx
import React from "react";
import KanBanNav from "./KanBanNav";
import List from "../containers/List";
import Edit from "../containers/Edit";
import NewList from "../containers/NewList";
import ListMenu from "../containers/ListMenu";
export default function KanBan({ todos }) {
return (
<>
<KanBanNav />
<div className="board p-1">
{todos.map((list, index) => (
<List key={index} {...list} listId={index} />
))}
<NewList />
<Edit />
<ListMenu />
</div>
</>
);
}
接下來要製作拖曳Todo、隨意調換位置的功能。