[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 罗

范例程序码完成品

如果文章中有错误的地方,要麻烦各位大大不吝赐教;喜欢的话,也要记得帮我按赞订阅喔❤️


<<:  近似最短路径 (8)

>>:  Day 28 整体Privacy by Design需求规划评估实作

唤醒与生俱来的数学力 (2) 顺序 & 因果

由於昨天晚上听了明就仁波切的分享,今天原本想读 In Love with the World,但读了...

创作App-Xcode资料库

有了基础的注册系统後,建立资料库来连接系统,用於储存用户、使用者的帐号与密码、等级等,区分版本功能。...

Guaranteed Results with Updated SASM PDF Questions

Passing Scrum SASM certification exam with minimum...

轻松跨越Windows地雷而不会被炸得粉身碎骨

曾经被系统的地雷,炸得支离破碎 很多好用的应用程序都对Windows不太友善,今天就让你轻松跨越这些...

【Day 13】 浅谈 OSM

Open Street Map 开放街图,OpenStreetMap,简称 OSM,我们把它当成一个...