发布于 2026-01-06 3 阅读
0

如何使用 React Hooks 构建 TODO 列表 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

如何使用 React Hooks 构建待办事项列表

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

本文最初发表于Educative 网站。作者Yazeed Bzadough致力于为开发者创作励志和教育内容,希望通过深入的理解来启发和指导读者。他主要关注 Web 技术,目前包括 JavaScript、TypeScript 和 React。


什么是钩子?

它们是无需 ES6 类即可提供 React 状态和生命周期钩子等功能的函数。

部分好处包括:

  • 隔离有状态逻辑,使其更容易测试。
  • 无需渲染属性或高阶组件即可共享有状态逻辑。
  • 基于逻辑而非生命周期钩子来分离应用程序的关注点。
  • 避免使用 ES6 类,因为它们很古怪,实际上并不是类,甚至会让经验丰富的 JavaScript 开发人员也感到困惑。

更多详情请参阅React 官方 Hooks 简介

请勿在生产环境中使用!截至撰写本文时,Hooks 仍处于 alpha 测试阶段,其 API 随时可能更改。我建议您在业余项目中进行实验,尽情体验 Hooks 的乐趣,但在其稳定之前,请勿将其用于生产代码。


让我们创建一个待办事项清单

替代文字

待办事项清单之所以被用滥,是有原因的——它们是绝佳的练习工具。我建议无论你想尝试哪种语言或库,都应该先练习一下。

我们的版本只能做几件事:

  • 以美观的 Material Design 风格展示待办事项
  • 允许通过输入框添加待办事项
  • 删除待办事项

设置

以下是GitHub和CodeSandbox的链接。

git clone https://github.com/yazeedb/react-hooks-todo
cd react-hooks-todo
npm install
Enter fullscreen mode Exit fullscreen mode

该分支上有已完成的项目,如果您想跟进,master请查看该分支。start

git checkout start

然后运行项目。

npm start

该应用程序应该运行在[此处应填写运行环境名称]上localhost:3000,这是我们的初始用户界面。

替代文字

我们已经使用material-ui配置好了页面,让它看起来更专业。接下来,让我们添加一些功能吧!

TodoForm 组件

添加一个新文件,src/TodoForm.js以下是初始代码。

import React from 'react';
import TextField from '@material-ui/core/TextField';

const TodoForm = ({ saveTodo }) => {
  return (
    <form>
      <TextField variant="outlined" placeholder="Add todo" margin="normal" />
    </form>
  );
};

export default TodoForm;
Enter fullscreen mode Exit fullscreen mode

顾名思义,它的功能就是向我们的状态添加待办事项。说到待办事项,这是我们的第一个亮点。


使用状态

请查看这段代码:

import { useState } from 'react';

const [value, setValue] = useState('');
Enter fullscreen mode Exit fullscreen mode

useState它只是一个接受初始状态并返回数组的函数。请继续使用console.log它。

数组的第一个索引是你的当前状态值,第二个索引是更新函数。

因此我们恰当地给它们命名value,并setValue使用了ES6 解构赋值


使用表单中的状态

我们的表单应该跟踪输入值,并saveTodo在提交时调用相应的函数。useState可以帮我们实现这个功能吗?

更新TodoForm.js:新代码以粗体显示。

import React, { useState } from 'react';
import TextField from '@material-ui/core/TextField';

const TodoForm = ({ saveTodo }) => {
  const [value, setValue] = useState('');

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        saveTodo(value);
      }}
    >
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
        onChange={(event) => {
          setValue(event.target.value);
        }}
        value={value}
      />
    </form>
  );
};
Enter fullscreen mode Exit fullscreen mode

返回后index.js,导入并使用此组件。

// ...

import TodoForm from './TodoForm';

// ...

const App = () => {
  return (
    <div className="App">
      <Typography component="h1" variant="h2">
        Todos
      </Typography>

      <TodoForm saveTodo={console.warn} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

现在,您的值已在提交后记录(按回车键)。

替代文字


使用待办事项状态

我们还需要为待办事项设置状态。导入相关useState模块index.js。初始状态应该是一个空数组。

import React, { useState } from 'react';

// ...

const App = () => {
  const [todos, setTodos] = useState([]);

  // ...
};
Enter fullscreen mode Exit fullscreen mode

待办事项列表组件

创建一个名为 . 的新文件src/TodoList.js。编辑:感谢Takahiro Hata帮助我将 onClick 移动到正确的位置!

import React from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import ListItemText from '@material-ui/core/ListItemText';
import Checkbox from '@material-ui/core/Checkbox';
import IconButton from '@material-ui/core/IconButton';
import DeleteIcon from '@material-ui/icons/Delete';

const TodoList = ({ todos, deleteTodo }) => (
  <List>
    {todos.map((todo, index) => (
      <ListItem key={index.toString()} dense button>
        <Checkbox tabIndex={-1} disableRipple />
        <ListItemText primary={todo} />
        <ListItemSecondaryAction>
          <IconButton
            aria-label="Delete"
            onClick={() => {
              deleteTodo(index);
            }}
          >
            <DeleteIcon />
          </IconButton>
        </ListItemSecondaryAction>
      </ListItem>
    ))}
  </List>
);

export default TodoList;
Enter fullscreen mode Exit fullscreen mode

它需要两个道具

  • Todos待办事项数组。我们遍历map每个待办事项并创建一个列表项。
  • DeleteTodo点击待办事项会IconButton触发此函数。它会传递一个唯一标识符index,该标识符将用于在我们的列表中唯一标识一个待办事项。

将此组件导入到您的系统中index.js

import TodoList from './TodoList';
import './styles.css';

const App = () => {
  //...
};
Enter fullscreen mode Exit fullscreen mode

App然后像这样在你的函数中使用它:

<TodoForm saveTodo={console.warn} />
<TodoList todos={todos} />
Enter fullscreen mode Exit fullscreen mode

添加待办事项

仍然在index.js,让我们编辑我们的TodoForm's属性,saveTodo

<TodoForm
  saveTodo={(todoText) => {
    const trimmedText = todoText.trim();

    if (trimmedText.length > 0) {
      setTodos([...todos, trimmedText]);
    }
  }}
/>
Enter fullscreen mode Exit fullscreen mode

只需将现有的待办事项与我们新的待办事项合并,多余的空白区域将被删除。

现在可以添加待办事项了!

替代文字


清除输入

注意:添加新待办事项后,输入内容不会清除。这用户体验很差!

我们可以通过对代码进行少量更改来解决这个问题TodoForm.js

<form
  onSubmit={(event) => {
    event.preventDefault();

    saveTodo(value);

    setValue('');
  }}
/>
Enter fullscreen mode Exit fullscreen mode

待办事项保存后,将表单状态设置为空字符串。

现在看起来不错!

替代文字


删除待办事项

TodoList提供每个待办事项index,因为这是找到我们要删除的事项的可靠方法。

TodoList.js

<IconButton
  aria-label="Delete"
  onClick={() => {
    deleteTodo(index);
  }}
>
  <DeleteIcon />
</IconButton>
Enter fullscreen mode Exit fullscreen mode

我们将利用这一点index.js

<TodoList
  todos={todos}
  deleteTodo={(todoIndex) => {
    const newTodos = todos.filter((_, index) => index !== todoIndex);

    setTodos(newTodos);
  }}
/>
Enter fullscreen mode Exit fullscreen mode

任何与提供的待办事项不匹配的事项index都会被保留并存储在状态中setTodos

删除功能已完成!

替代文字


抽象待办事项使用状态

我之前提到过,Hooks非常适合分离状态和组件逻辑。下面展示一下它在我们的待办事项应用中可能的样子。

创建一个名为“.”的新文件src/useTodoState.js

import { useState } from 'react';

export default (initialValue) => {
  const [todos, setTodos] = useState(initialValue);

  return {
    todos,
    addTodo: (todoText) => {
      setTodos([...todos, todoText]);
    },
    deleteTodo: (todoIndex) => {
      const newTodos = todos.filter((_, index) => index !== todoIndex);

      setTodos(newTodos);
    }
  };
};
Enter fullscreen mode Exit fullscreen mode

代码和之前一样index.js,只是分离了!我们的状态管理不再与组件紧密耦合。

现在直接导入即可。

import React from 'react';
import ReactDOM from 'react-dom';
import Typography from '@material-ui/core/Typography';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
import useTodoState from './useTodoState';
import './styles.css';

const App = () => {
  const { todos, addTodo, deleteTodo } = useTodoState([]);

  return (
    <div className="App">
      <Typography component="h1" variant="h2">
        Todos
      </Typography>

      <TodoForm
        saveTodo={(todoText) => {
          const trimmedText = todoText.trim();

          if (trimmedText.length > 0) {
            addTodo(trimmedText);
          }
        }}
      />

      <TodoList todos={todos} deleteTodo={deleteTodo} />
    </div>
  );
};

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

一切都照常运转。


抽象表单输入 useState

我们也可以用同样的方法修改表格!

创建一个新文件,src/useInputState.js

import { useState } from 'react';

export default (initialValue) => {
  const [value, setValue] = useState(initialValue);

  return {
    value,
    onChange: (event) => {
      setValue(event.target.value);
    },
    reset: () => setValue('')
  };
};
Enter fullscreen mode Exit fullscreen mode

现在TodoForm.js应该看起来像这样。

import React from 'react';
import TextField from '@material-ui/core/TextField';
import useInputState from './useInputState';

const TodoForm = ({ saveTodo }) => {
  const { value, reset, onChange } = useInputState('');

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();

        saveTodo(value);
        reset();
      }}
    >
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
        onChange={onChange}
        value={value}
      />
    </form>
  );
};

export default TodoForm;
Enter fullscreen mode Exit fullscreen mode

好了,就到这里!希望你们喜欢,下次再见!

如果您想了解更多关于使用 Hooks 的信息,可以访问“使用Hooks 的高级 React 模式”页面。此外,如果您想了解 Yazeed 的更多作品,可以查看他的课程“使用 RamdaJS 的函数式编程模式”

文章来源:https://dev.to/educative/how-to-build-a-todo-list-with-react-hooks-42dc