优化 展示列表和输入组件应该没有自己的逻辑,应该完全可控,方法都是父组件传入
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;
在这里插入图片描述
网友评论