青训营笔记12 | 使用React实现一个简单的待办事项列表

41 阅读3分钟

  todo list是不少人学习框架的入门经典案例,正好青训营的实践选题有这个方向,就随手写了一个,就当复习练手了。

  主要功能:添加、修改、删除、批量删除、搜索;

  为了使按钮看起来更美观,参考arco design 的样式指南调整按钮默认颜色、鼠标悬浮颜色和点击颜色;

1. 效果一览

初始化

image.png

添加任务

image.png


image.png

搜索

image.png

修改

  • 修改内容:双击任务或点击编辑图标

image.png

  • 修改完成状态

image.png

删除

使用prompt做二次确认

  • 删除单个任务

image.png

  • 批量删除

image.png

2. 组件拆分及通信

  1. 首先是把整体拆分成TodoHeaderTodoBodyTodoFooter三个组件,由Todolist父组件传递属性和上下文;
  2. 增删改通过useReducer将具体逻辑分理成单独的文件,并创建DispatchContext进行分发,由子组件自行注入;查通过SearchKeywordContext进行分发;

模块划分如下:

image.png

开始写

由于笔记活动代码文字比例不能太高,后面尽量粘贴图片

首先是类型,这里把操作单独枚举出来,便于在distapch时选择合适的操作,由参数提示:

/**
 * 操作类型枚举
 */
export enum Behavior {
    ADD = "添加",
    DELETE = "删除",
    UPDATE = "修改",
}

/**
 * 一个todo的类型
 */
export interface ITodoItem {
    id: string;
    content: string;
    selected: boolean;
    edit: boolean;
    done: boolean;
}

export interface ITodoState {
    todos: ITodoItem[];
}

/**
 * reducer的action
 */
export interface IAction {
    type: Behavior;
    items: ITodoItem[];
}

然后定义reducerreducer函数返回要把state修改成的值:

  • 添加时直接加到数组最后;
  • 删除时会收到一个数组,遍历原state,对于其中每个元素,如果id在要删除的数组中,就过滤掉;
  • 修改时遍历参数中的数组,找到id和传入item的id相同的任务,进行替换;
import { Behavior, IAction, ITodoState } from "../../types";

export function reducer(state: ITodoState, action: IAction): ITodoState {
    switch (action.type) {
        case Behavior.ADD:
            return { todos: [...state.todos, ...action.items] };
        case Behavior.DELETE:
            const delIds = action.items.map((i) => i.id);
            return { todos: state.todos.filter((todo) => !delIds.includes(todo.id)) };
        case Behavior.UPDATE:
            return {
                todos: state.todos.map((todo) => action.items.find((item) => todo.id === item.id) ?? todo),
            };
        default:
            throw new Error("should not arrived");
    }
}

之后是context

  • DispatchContext用于传递查任务的state以及增删改任务的dispatch,在index.tsx中提供,在其子组件中注入使用;
  • SearchKeyContext用于传递搜索的keywordsetKeyword方法,在TodoHeader中修改,在TodoBody中对任务数组进行过滤;
export interface DispatchContextValue {
    state: { todos: ITodoItem[] };
    dispatch: Dispatch<IAction>;
}
export const DispatchContext = createContext<DispatchContextValue>({} as DispatchContextValue);

export interface SearchKeyword {
    keyword: string;
    setKeyword(keyword: string): void;
}

export const SearchKeywordContext = createContext<SearchKeyword>({} as SearchKeyword);

index.tsx中:

const initData: ITodoState = {
    todos: [
        {
            id: "0",
            content: "吃饭",
            selected: true,
            edit: false,
            done: false,
        },
        {
            id: "1",
            content: "睡觉",
            selected: false,
            edit: false,
            done: true,
        },
        {
            id: "2",
            content: "学习",
            selected: true,
            edit: false,
            done: false,
        },
    ],
};

export default function TodoList() {
    const [state, dispatch] = useReducer<Reducer<ITodoState, IAction>>(reducer, initData);
    const [keyword, setKeyword] = useState<string>("");

    return (
        <DispatchContext.Provider value={{ state, dispatch }}>
            <SearchKeywordContext.Provider value={{ keyword, setKeyword }}>
                <TodoHeader></TodoHeader>
                <TodoBody></TodoBody>
                <TodoFooter></TodoFooter>
            </SearchKeywordContext.Provider>
        </DispatchContext.Provider>
    );
}

之后开始写三个子组件:

TodoHeader 作用:搜索和添加

  • 修改搜索输入框时会调用context中的setKeyword方法,同时后面的TodoBody中会根据keyword对展示列表进行过滤:
  • 添加时可以直接通dispatch修改stateTodoBody中也会同步更新;
  • 通过绝对定位在搜索框中添加一个x,便于快速清除输入,而且只在输入.trim()不为空字符串时才显示;

code.png

TodoBody
作用:显示、过滤、修改、删除、选择;

  • 修改、删除时会调用从context获取的dispatch方法;
  • 通过把编辑inputref设置为一个函数,可以在不使用useRef的情况下,获取到DOM之后直接获取焦点,即切换为编辑模式后自动获取焦点;

image.png

TodoFooter
作用:全(不)选、批量删除;

  • 也是通过distapch进行修改和删除;
  • 全(不)选切换时使用数组的every方法,如果所有的都done说明当前已经全选,都改为未选中;否则说明至少有一个未选择,改为都选中;

image.png

总结

  通过一个简单的todo list案例,实现了任务的增删改查。todo list是入门框架的经典案例,可以作为练习组件拆分、组件通信等基础技能的敲门砖,熟练掌握其中包含的原理对日后的开发有一定帮助。