useReducer 实现todoList ts规范书写

2021-02-08  本文已影响0人  薛定谔的程序

优化 展示列表和输入组件应该没有自己的逻辑,应该完全可控,方法都是父组件传入

  const handleAddTodo = useCallback((todo: Todo) => {
    addTodo(todo)(dispatch);
  }, []);

  const handleRemoveList = useCallback(
    (id: ID) => () => {
      removeTodo(id)(dispatch);
    },
    []
  );

  const handleToggleListStatus = useCallback(
    (id: ID) => () => {
      toggleTodoListStatus(id)(dispatch);
    },
    []
  );
<Input onAddTodo={handleAddTodo} />
<List
     todoList={todoList}
     onRemoveList={handleRemoveList}
     onToggleListStatus={handleToggleListStatus}
   />

typing/index.ts

export type Content = string;
export type ID = string;
export interface Todo {
  id: ID;
  content: Content;
  complete: boolean;
}

export type TodoList = Array<Todo>;

export enum Actions {
  ADD_TODO = "ADD_TODO",
  REMOVE_TODO = "REMOVE_TODO",
  TOGGLE_TODO_STATUS = "TOGGLE_TODO_STATUS",
}
export interface IAction {
  type: Actions;
  payload: ID | Todo;
}

action.ts

import { Dispatch } from "react";
import { Actions, Todo, ID } from "./typing";
// add
export interface IAddTodoAction {
  type: Actions;
  payload: Todo;
}

const addTodoAction = (todo: Todo): IAddTodoAction => ({
  type: Actions.ADD_TODO,
  payload: todo,
});

export const addTodo = (todo: Todo) => (dispatch: Dispatch<IAddTodoAction>) => {
  dispatch(addTodoAction(todo));
};
// remove
export interface IRemoveTodoAction {
  type: Actions;
  payload: ID;
}

const removeTodoAction = (ID: ID): IRemoveTodoAction => ({
  type: Actions.REMOVE_TODO,
  payload: ID,
});

export const removeTodo = (ID: ID) => (
  dispatch: Dispatch<IRemoveTodoAction>
) => {
  dispatch(removeTodoAction(ID));
};
// toggle
export interface IToggleTodoStatusAction {
  type: Actions;
  payload: ID;
}

const toggleTodoStatusAction = (ID: ID): IToggleTodoStatusAction => ({
  type: Actions.TOGGLE_TODO_STATUS,
  payload: ID,
});

export const toggleTodoStatus = (ID: ID) => (
  dispatch: Dispatch<IToggleTodoStatusAction>
) => {
  dispatch(toggleTodoStatusAction(ID));
};

reducer.ts

import { TodoList, IAction, Actions, Todo, ID } from "./typing/index";

export const reducer = (state: TodoList, action: IAction): TodoList => {
  const { type, payload } = action;

  switch (type) {
    case Actions.ADD_TODO:
      return [...state, payload as Todo];
    case Actions.REMOVE_TODO:
      return state.filter((todo: Todo) => todo.id !== (payload as ID));
    case Actions.TOGGLE_TODO_STATUS:
      return state.map((todo: Todo) => {
        if (todo.id === payload) {
          todo.complete = !todo.complete;
        }
        return todo;
      });
    default:
      return state;
  }
};

index.tsx

import React, {
  FC,
  ReactElement,
  useReducer,
  useEffect,
  useCallback,
} from "react";
import { ContentContainer } from "../../constants/LayoutStyled";
import { Typography } from "@material-ui/core";
import Input from "./Input";
import List from "./List";
import { reducer } from "./reducer";

const initializer = () => JSON.parse(localStorage.getItem("todoList") || "[]");

const TodoList: FC = (): ReactElement => {
  const [todoList, dispatch] = useReducer(reducer, undefined, initializer);

  const saveLocalStorage = useCallback(
    (todoList) => () => {
      localStorage.setItem("todoList", JSON.stringify(todoList));
    },
    []
  );

  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(saveLocalStorage(todoList), [todoList]);

  return (
    <ContentContainer>
      <Typography variant="caption">ToDo List</Typography>
      <Input dispatch={dispatch} />
      <List todoList={todoList} dispatch={dispatch} />
    </ContentContainer>
  );
};
export default TodoList;

input.tsx

import React, { Dispatch, FC, ReactElement, useCallback, useRef } from "react";
import { TextField } from "../../components/UI/Input";
import { Button } from "../../components/UI/Button";
import { Grid } from "@material-ui/core";
import { addTodo, IAddTodoAction } from "./action";
import { v1 as UUIDV1 } from "uuid";
interface IProps {
  dispatch: Dispatch<IAddTodoAction>;
}

const Input: FC<IProps> = ({ dispatch }): ReactElement => {
  const input = useRef<HTMLInputElement>(null);

  const handleAddTodo = useCallback((): void => {
    const content = input!.current!.value;
    addTodo({ id: UUIDV1(), content, complete: false })(dispatch);
    input!.current!.value = "";
  }, [dispatch]);

  return (
    <Grid container spacing={2}>
      <Grid container item xs={12} sm={3}>
        <TextField inputRef={input} label="TODO" flex={1} />
      </Grid>
      <Grid
        container
        item
        xs={12}
        sm={3}
        alignItems="flex-end"
        justify="flex-start"
      >
        <Button
          onClick={handleAddTodo}
          width="100px"
          variant="outlined"
          color="primary"
        >
          ADD
        </Button>
      </Grid>
    </Grid>
  );
};

export default Input;

list/index.tsx

import React, { FC, ReactElement, useCallback, Dispatch } from "react";
import { ID, Todo, TodoList } from "../typing";
import { List as MaterialList, Grid, Typography } from "@material-ui/core";
import {
  IRemoveTodoAction,
  IToggleTodoStatusAction,
  removeTodo,
  toggleTodoStatus,
} from "../action";
import ListItem from "./ListItem";

interface IProps {
  todoList: TodoList;
  dispatch: Dispatch<IRemoveTodoAction | IToggleTodoStatusAction>;
}

const List: FC<IProps> = ({ todoList, dispatch }): ReactElement => {
  const handleToggleTodoStatus = useCallback(
    (Id: ID) => () => toggleTodoStatus(Id)(dispatch),
    [dispatch]
  );

  const handleRemoveTodo = useCallback(
    (Id: ID) => () => removeTodo(Id)(dispatch),
    [dispatch]
  );

  const renderTodoItem = useCallback(
    (todo: Todo) => (
      <ListItem
        key={todo.id}
        todo={todo}
        handleToggleTodoStatus={handleToggleTodoStatus}
        handleRemoveTodo={handleRemoveTodo}
      />
    ),
    [handleToggleTodoStatus, handleRemoveTodo]
  );
  if (todoList.length) {
    return (
      <Grid>
        <Grid item sm={6}>
          <MaterialList>{todoList.map(renderTodoItem)}</MaterialList>
        </Grid>
      </Grid>
    );
  }
  return <Typography color="textPrimary">not data</Typography>;
};

export default List;

List/ListItem

import {
  Checkbox,
  ListItemIcon,
  ListItemText,
  ListItem as MaterialListItem,
} from "@material-ui/core";
import { IndeterminateCheckBox } from "@material-ui/icons";
import React, { FC, ReactElement } from "react";
import { Todo, ID } from "../typing";

interface ITodoListItem {
  todo: Todo;
  handleToggleTodoStatus: (id: ID) => () => void;
  handleRemoveTodo: (id: ID) => () => void;
}

const ListItem: FC<ITodoListItem> = ({
  todo: { id, content, complete },
  handleRemoveTodo,
  handleToggleTodoStatus,
}: ITodoListItem): ReactElement => (
  <MaterialListItem divider button>
    <ListItemIcon>
      <Checkbox
        checked={complete}
        color="primary"
        onChange={handleToggleTodoStatus(id)}
      />
    </ListItemIcon>
    <ListItemText primary={content} />
    <ListItemIcon>
      <IndeterminateCheckBox onClick={handleRemoveTodo(id)} />
    </ListItemIcon>
  </MaterialListItem>
);

export default ListItem;

在这里插入图片描述
上一篇 下一篇

猜你喜欢

热点阅读