iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 14
0
Modern Web

React + GraphQL 全端練習筆記系列 第 14

仿Trello - 製作reducer

本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。

Slice中建立reducer

//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的方法。

輸出Action

上面提到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;

ListContainer

像上一篇的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。

在NewTodo呼叫addTodo

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>
    </>
  );

剩下的功能逐一辦理,做法都差不多:

  • 寫好reducer
  • 編輯container
  • 替換function、component

所以就不逐一介紹,可以在這個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、隨意調換位置的功能。

References:


上一篇
仿Trello - 建立 Redux Store
下一篇
仿Trello - 用React DnD製作拖曳(drag)功能
系列文
React + GraphQL 全端練習筆記30

尚未有邦友留言

立即登入留言