iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

Follow me! 來一場前端技能樹之旅系列 第 29

[Day 29 - 小試身手] Todolist with React (4)

在上一章Todolist with React (3),使用 React-redux 完成了渲染任務清單、和任務新增刪除的動作,就讓我們繼續完成最後一個部分 — Filter 篩選器,控制任務清單的顯示。

切換上方的 Filter,可以切換對應的任務清單

初始化 Filter

首先先處理顯示篩選器按鈕的部分,被選擇的按鈕要顯示 Selected (黃底) 的樣式,總共會有三種狀態 SHOW_ALLSHOW_TODOSHOW_DONE

Filter Reducer

在 filter.js 定義控制 Filter 的 Reducer,State 參數的預設值為 SHOW_ALL,目前尚未定義 Action,直接回傳當前原本的 State。然後要在 index.js 裡整合 Filter Reducer。

reducer/filter.js

import * as types from '../actions/ActionTypes';

export default function filter(state = 'SHOW_ALL', action) {
    switch (action.type) {
      default:
        return state;
    }
}

reducer/index.js

...
import filterReducer from './filter';

const todoApp = combineReducers({
    filterReducer,
    todosReducer
});

顯示 Selected Filter

因為在 TaskList Component 內也會需要 Filter 的狀態來顯示對應的任務清單,所以我們在 TaskList Component 使用 useSelector 取得目前的 Filter 狀態,再把它傳入 Filter Component。

components/TaskList.js

...
import { useSelector } from "react-redux";

function TaskList() {
...
  const filter = useSelector((store) => store.filterReducer);

  const renderItems = ...

  return (
    <Wrapper>
      <Filter selected = {filter}/>
      ...
    </Wrapper>
  );
}

利用 Styled-components 根據 Props 可以改變樣式的功能,只要按鈕是 Selected 的狀態,傳入的 active 就是 true,會切換背景顏色變成黃色。

components/Filter.js

const Button = styled.div`
  background-color: ${(props) => (props.active ? "#ffc236" : "#bebebe")};
  ...
`;

function Filter(props) {

  return (
    <ButtonContainer>
      <Button active={props.selected === "SHOW_ALL"}>
        ALL
      </Button>
      <Button active={props.selected === "SHOW_TODO"}>
        TODO
      </Button>
      <Button active={props.selected === "SHOW_DONE"}>
        DONE
      </Button>
    </ButtonContainer>
  );
}

控制 Filter

可以顯示被選擇的按鈕後,接下來要新增 Filter 切換的功能,在 ActionTypes.js 定義 SET_FILTER 動作。

actions/ActionTypes.js

...
export const SET_FILTER = 'SET_FILTER';

切換 Filter

Step1:建立 Action Creator 產生設定 Filter 的動作 Function setFilter,並取得切換的 filter 名稱。

actions/filter.js

import * as types from './ActionTypes';

// action creator
export function setFilter(filter){
    return {
        type: types.SET_FILTER,
        filter
    };
}

Step2:處理 Filter Reducer,讀取 action.type 為 SET_FILTER 時,直接回傳新的 Filter 狀態。

reducer/filter.js

import * as types from '../actions/ActionTypes';

export default function filter(state = 'SHOW_ALL', action) {
    switch (action.type) {
      case types.SET_FILTER:
        return action.filter;
      default:
        return state;
    }
}

Step3:完成 Action 和 Reducer 的設定後,在 Filter Component 利用 useDispatch() 來調用 Action。點擊 後直接呼叫 setFilter(),傳入對應的篩選器名稱。

components/Filter.js

import { useDispatch } from "react-redux";

function Filter(props) {
  const dispatch = useDispatch();

  return (
    <ButtonContainer>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_ALL"))}
      >
        ALL
      </Button>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_TODO"))}
      >
        TODO
      </Button>
      <Button
        active={...}
        onClick={() => dispatch(actions.setFilter("SHOW_DONE"))}
      >
        DONE
      </Button>
    </ButtonContainer>
  );
}

渲染對應的 Task

切換篩選器的同時,下方的任務清單也要可以根據狀態切換對應的列表,我們只在 forEach 裡面新增 if 條件式判斷,總共會有三種情況:

  • filter === "SHOW_ALL":當 Filter 是 SHOW_ALL 時,判斷式永遠為 true,顯示該任務。
  • filter === "SHOW_TODO" && !item.isCompleted:當 Filter 是 SHOW_TODO 時,任務的狀態 isCompleted 要是 false,判斷式會為 true,才能顯示該任務。
  • filter === "SHOW_DONE" && item.isCompleted:當 Filter 是 SHOW_DONE 時,任務的狀態 isCompleted 要是 true,判斷式會為 true,才能顯示該任務。

components/TaskList.js

function TaskList() {
  ...

  const renderItems = () => {
    let list = [];
    
    tasks.forEach((item, index) => {
      if (
        (filter === "SHOW_ALL") ||
        (filter === "SHOW_TODO" && !item.isCompleted) ||
        (filter === "SHOW_DONE" && item.isCompleted)
      ) {
        list.push(
          <TaskItem key={item.taskName} task={{ ...item, idx: index }} />
        );
      }
    });
    
    return list;
  };

  return (
    <Wrapper>
      ...
      <TaskItemContainer>{renderItems()}</TaskItemContainer>
    </Wrapper>
  );
}

勾選 Task

終於完成任務清單和篩選器的功能,剩下最後一個功能 — 完成任務的時候要可以勾選框框,設定任務的狀態。一樣在 ActionTypes.js 定義勾選任務的動作 TOGGLE_TASK。

actions/ActionTypes.js

...
export const TOGGLE_TASK = 'TOGGLE_TASK';

Step1:建立 Action Creator 產生勾選 Task 的動作 Function toggleTask,並取得對應的任務索引值。

actions/todos.js

export function toggleTask(idx){
    return {
        type: types.TOGGLE_TASK,
        idx
    };
}

Step2:處理 Todo Reducer,讀取 action.type 為 TOGGLE_TASK 時,修改該任務的完成狀態並回傳的新 State。

reducer/todos.js

...
export default function todos(state = initialTasks, action) {
  switch (action.type) {
    case types.ADD_TASK:...
    case types.DELETE_TASK:...
    case types.TOGGLE_TASK:
      let newState = [...state];
      newState[action.idx].isCompleted = !newState[action.idx].isCompleted;
      return newState;
    default:...
  }
}

Step3:完成 Action 和 Reducer 的設定後,在 TaskItem Component 利用 useDispatch() 來調用 Action。點擊 <CheckBox /> 後直接呼叫 toggleTask(),傳入對應的任務索引值。

components/TaskItem.js

function TaskItem(props) {
  const dispatch = useDispatch();

  return (
    <Container>
      <CheckBox
        type="checkbox"
        checked={...}
        onChange={() => dispatch(actions.toggleTask(props.task.idx))}
      />
      ...
    </Container>
  );
}

小結

這樣就完成了一個簡單的 Todolist 囉

範例程式碼完成品

如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️


上一篇
[Day 28 - 小試身手] Todolist with React (3)
下一篇
[Day 30] 完賽心得:你的前端之旅還尚未結束
系列文
Follow me! 來一場前端技能樹之旅30

1 則留言

0
juck30808
iT邦新手 3 級 ‧ 2021-10-12 18:34:23

恭喜即將完賽!!!

我要留言

立即登入留言