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

68 阅读12分钟

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

什么是待办事项列表?

待办事项列表(To-do List)是一种用于管理个人或团队的任务和目标的工具,它可以帮助我们记录、安排、执行和跟踪我们想要完成的事情。

一个典型的待办事项列表应用程序通常具有以下功能:

  • 添加一个新的待办事项
  • 编辑一个已有的待办事项
  • 删除一个不需要的待办事项
  • 标记一个已完成的待办事项
  • 筛选不同状态的待办事项

为什么使用React来实现待办事项列表?

React是一个用于构建用户界面的JavaScript库,它可以让我们用组件的方式来开发网页或应用程序,提高了代码的可读性和可维护性。

React还有以下优点:

  • 使用虚拟DOM(Virtual DOM)来提高渲染性能,只更新变化的部分,避免不必要的重绘。
  • 使用JSX语法来编写组件,让我们可以在JavaScript中直接使用HTML标签,简化了视图的创建和渲染过程。
  • 使用Hooks来在函数组件中使用状态和生命周期,让我们可以不用类组件就能实现复杂的逻辑和交互。
  • 使用Props和State来管理数据流,让我们可以实现数据和视图的单向绑定,保证数据的一致性和可控性。

如何使用React来实现待办事项列表?

为了使用React来实现待办事项列表,我们需要完成以下几个步骤:

  1. 创建一个React项目
  2. 设计组件结构
  3. 编写组件代码
  4. 添加样式和交互

下面我们将逐一介绍这些步骤。

创建一个React项目

要创建一个React项目,我们可以使用create-react-app工具,它是一个官方推荐的方式,可以帮助我们快速搭建一个React开发环境,包括了Webpack、Babel、ESLint等常用的工具和配置。

要使用create-react-app工具,我们需要先安装Node.js和npm(或yarn),然后在命令行中执行以下命令:

# 使用npm
npx create-react-app todo-list

# 使用yarn
yarn create react-app todo-list

这样就会在当前目录下创建一个名为todo-list的文件夹,并且在其中生成了一些基本的文件和目录结构。

接下来,我们可以进入todo-list目录,并启动开发服务器:

# 进入todo-list目录
cd todo-list

# 使用npm
npm start

# 使用yarn
yarn start

这样就会在浏览器中打开一个地址为http://localhost:3000/ 的页面,显示了一个默认的React应用界面。

设计组件结构

在React中,组件是构建用户界面的基本单位,它可以让我们将复杂的界面分解为多个独立且可复用的部分。

为了设计我们的待办事项列表应用程序的组件结构,我们可以先从用户界面出发,分析出其中包含了哪些组件,并且它们之间有什么关系。

我们可以参考以下示意图来设计我们的组件结构:

![component-structure]

从图中可以看出,我们的应用程序由以下几个组件组成:

  • App:根组件,负责管理整个应用程序的状态和逻辑,以及渲染其他组件。
  • TodoForm:表单组件,负责接收用户输入,以及添加新的待办事项。
  • TodoList:列表组件,负责展示所有的待办事项,以及传递相关的事件和数据给子组件。
  • TodoItem:项目组件,负责展示一个单独的待办事项,以及处理相关的事件和数据。
  • TodoFilter:过滤器组件,负责提供不同的筛选条件,以及切换当前的筛选状态。

我们可以使用以下代码来创建这些组件,并且在App组件中引入和渲染它们:

// App.js
import React from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

function App() {
  return (
    <div className="todoapp">
      <h1>Todo List</h1>
      <TodoForm />
      <TodoFilter />
      <TodoList />
    </div>
  );
}

export default App;
// TodoForm.js
import React from "react";

function TodoForm() {
  return (
    <form>
      <input type="text" placeholder="Enter a new task" />
      <button type="submit">Add</button>
    </form>
  );
}

export default TodoForm;
// TodoList.js
import React from "react";
import TodoItem from "./TodoItem";

function TodoList() {
  return (
    <ul>
      <TodoItem />
      <TodoItem />
      <TodoItem />
    </ul>
  );
}

export default TodoList;
// TodoItem.js
import React from "react";

function TodoItem() {
  return (
    <li>
      <input type="checkbox" />
      <span>Task</span>
      <button>Edit</button>
      <button>Delete</button>
    </li>
  );
}

export default TodoItem;
// TodoFilter.js
import React from "react";

function TodoFilter() {
  return (
    <div>
      <button>All</button>
      <button>Active</button>
      <button>Completed</button>
    </div>
  );
}

export default TodoFilter;

现在,我们可以看到我们的应用程序已经有了一个基本的界面,如下图所示:

![basic-ui]

编写组件代码

在设计了组件结构之后,我们需要编写每个组件的具体代码,实现它们的功能和交互。

为了编写组件代码,我们需要使用React提供的一些特性和技术,包括:

  • Props:一种用于传递数据和方法给子组件的机制,它可以让我们实现组件之间的通信和协作。
  • State:一种用于存储和更新组件内部的状态的机制,它可以让我们实现组件的动态变化和响应。
  • Hooks:一种用于在函数组件中使用状态和生命周期等特性的机制,它可以让我们实现更简洁和优雅的组件代码。
  • JSX:一种用于在JavaScript中编写HTML标签的语法,它可以让我们更方便地创建和渲染组件的视图。

下面我们将逐一介绍这些特性和技术,并且使用它们来编写我们的组件代码。

Props

Props是一种用于传递数据和方法给子组件的机制,它可以让我们实现组件之间的通信和协作。

在React中,每个组件都可以接收一个props参数,它是一个JavaScript对象,包含了父组件传递给子组件的属性和值。

例如,在App组件中,我们可以给TodoList组件传递一个名为tasks的props属性,它是一个数组,包含了所有的待办事项:

// App.js
import React from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

function App() {
  // 定
Props

Props是一种用于传递数据和方法给子组件的机制,它可以让我们实现组件之间的通信和协作。

在React中,每个组件都可以接收一个props参数,它是一个JavaScript对象,包含了父组件传递给子组件的属性和值。

例如,在App组件中,我们可以给TodoList组件传递一个名为tasks的props属性,它是一个数组,包含了所有的待办事项:

// App.js
import React from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

function App() {
  // 定义一个tasks数组,存储所有的待办事项
  const tasks = [
    { id: 1, text: "Learn React", completed: false },
    { id: 2, text: "Build a todo app", completed: false },
    { id: 3, text: "Have fun", completed: true },
  ];

  return (
    <div className="todoapp">
      <h1>Todo List</h1>
      <TodoForm />
      <TodoFilter />
      {/* 给TodoList组件传递tasks属性 */}
      <TodoList tasks={tasks} />
    </div>
  );
}

export default App;

然后,在TodoList组件中,我们可以通过props参数来接收并使用tasks属性,例如,我们可以遍历tasks数组,为每个待办事项创建一个TodoItem组件,并且给它传递一些props属性,如id、text和completed等:

// TodoList.js
import React from "react";
import TodoItem from "./TodoItem";

function TodoList(props) {
  // 从props中解构出tasks属性
  const { tasks } = props;

  return (
    <ul>
      {/* 遍历tasks数组,为每个待办事项创建一个TodoItem组件 */}
      {tasks.map((task) => (
        // 给TodoItem组件传递id、text和completed等属性
        <TodoItem key={task.id} id={task.id} text={task.text} completed={task.completed} />
      ))}
    </ul>
  );
}

export default TodoList;

同样地,在TodoItem组件中,我们也可以通过props参数来接收并使用id、text和completed等属性,例如,我们可以根据这些属性来渲染不同的视图元素,并且添加一些事件处理函数:

// TodoItem.js
import React from "react";

function TodoItem(props) {
  // 从props中解构出id、text和completed等属性
  const { id, text, completed } = props;

  // 定义一些事件处理函数
  const handleCheck = () => {
    console.log(`Check task ${id}`);
  };

  const handleEdit = () => {
    console.log(`Edit task ${id}`);
  };

  const handleDelete = () => {
    console.log(`Delete task ${id}`);
  };

  return (
    <li>
      {/* 根据completed属性来设置复选框的状态 */}
      <input type="checkbox" checked={completed} onChange={handleCheck} />
      {/* 根据completed属性来设置文本的样式 */}
      <span style={{ textDecoration: completed ? "line-through" : "none" }}>{text}</span>
      <button onClick={handleEdit}>Edit</button>
      <button onClick={handleDelete}>Delete</button>
    </li>
  );
}

export default TodoItem;

通过使用Props,我们可以实现父子组件之间的数据传递和方法调用,让我们的组件更加灵活和可复用。

State

State是一种用于存储和更新组件内部的状态的机制,它可以让我们实现组件的动态变化和响应。

在React中,每个类组件都有一个state属性,它是一个JavaScript对象,包含了该组件内部的状态。我们可以通过this.state来访问该对象,并且通过this.setState来更新该对象。

例如,在App组件中,我们可以定义一个state对象,包含了一个名为tasks的状态,它是一个数组,包含了所有的待办事项:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  render() {
    return (
      <div className="todoapp">
        <h1>Todo List</h1>
        <TodoForm />
        <TodoFilter />
        {/* 给TodoList组件传递tasks属性 */}
        <TodoList tasks={this.state.tasks} />
      </div>
    );
  }
}

export default App;

然后,在App组件中,我们可以定义一些方法来更新tasks状态,例如,我们可以定义一个addTask方法,用来添加一个新的待办事项,并且通过this.setState来更新tasks状态:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  // 定义一个addTask方法,用来添加一个新的待办事项
  addTask = (text) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 创建一个新的待办事项对象,id为当前tasks长度加一,text为参数,completed为false
    const newTask = { id: tasks.length + 1, text, completed: false };
    // 使用this.setState来更新tasks状态,将新的待办事项添加到tasks数组的末尾
    this.setState({
      tasks: [...tasks, newTask],
    });
  };

  render() {
    return (
      <div className="todoapp">
        <h1>Todo List</h1>
        {/* 给TodoForm组件传递addTask方法 */}
        <TodoForm addTask={this.addTask} />
        <TodoFilter />
        {/* 给TodoList组件传递tasks属性 */}
        <TodoList tasks={this.state.tasks} />
      </div>
    );
  }
}

export default App;

同样地,在App组件中,我们也可以定义其他的方法来更新tasks状态,例如,我们可以定义一个toggleTask方法,用来切换一个待办事项的完成状态,并且通过this.setState来更新tasks状态:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  // 定义一个addTask方法,用来添加一个新的待办事项
  addTask = (text) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 创建一个新的待办事项对象,id为当前tasks长度加一,text为参数,completed为false
    const newTask = { id: tasks.length + 1, text, completed: false };
    // 使用this.setState来更新tasks状态,将新的待办事项添加到tasks数组的末尾
    this.setState({
      tasks: [...tasks, newTask],
    });
  };

  // 定义一个toggleTask方法,用来切换一个待办事项的完成状态
  toggleTask = (id) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 使用map方法遍历tasks
State

State是一种用于存储和更新组件内部的状态的机制,它可以让我们实现组件的动态变化和响应。

在React中,每个类组件都有一个state属性,它是一个JavaScript对象,包含了该组件内部的状态。我们可以通过this.state来访问该对象,并且通过this.setState来更新该对象。

例如,在App组件中,我们可以定义一个state对象,包含了一个名为tasks的状态,它是一个数组,包含了所有的待办事项:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  render() {
    return (
      <div className="todoapp">
        <h1>Todo List</h1>
        <TodoForm />
        <TodoFilter />
        {/* 给TodoList组件传递tasks属性 */}
        <TodoList tasks={this.state.tasks} />
      </div>
    );
  }
}

export default App;

然后,在App组件中,我们可以定义一些方法来更新tasks状态,例如,我们可以定义一个addTask方法,用来添加一个新的待办事项,并且通过this.setState来更新tasks状态:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  // 定义一个addTask方法,用来添加一个新的待办事项
  addTask = (text) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 创建一个新的待办事项对象,id为当前tasks长度加一,text为参数,completed为false
    const newTask = { id: tasks.length + 1, text, completed: false };
    // 使用this.setState来更新tasks状态,将新的待办事项添加到tasks数组的末尾
    this.setState({
      tasks: [...tasks, newTask],
    });
  };

  render() {
    return (
      <div className="todoapp">
        <h1>Todo List</h1>
        {/* 给TodoForm组件传递addTask方法 */}
        <TodoForm addTask={this.addTask} />
        <TodoFilter />
        {/* 给TodoList组件传递tasks属性 */}
        <TodoList tasks={this.state.tasks} />
      </div>
    );
  }
}

export default App;

同样地,在App组件中,我们也可以定义其他的方法来更新tasks状态,例如,我们可以定义一个toggleTask方法,用来切换一个待办事项的完成状态,并且通过this.setState来更新tasks状态:

// App.js
import React, { Component } from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import TodoFilter from "./TodoFilter";

class App extends Component {
  // 定义一个state对象,包含了一个tasks状态
  state = {
    tasks: [
      { id: 1, text: "Learn React", completed: false },
      { id: 2, text: "Build a todo app", completed: false },
      { id: 3, text: "Have fun", completed: true },
    ],
  };

  // 定义一个addTask方法,用来添加一个新的待办事项
  addTask = (text) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 创建一个新的待办事项对象,id为当前tasks长度加一,text为参数,completed为false
    const newTask = { id: tasks.length + 1, text, completed: false };
    // 使用this.setState来更新tasks状态,将新的待办事项添加到tasks数组的末尾
    this.setState({
      tasks: [...tasks, newTask],
    });
  };

  // 定义一个toggleTask方法,用来切换一个待办事项的完成状态
  toggleTask = (id) => {
    // 获取当前的tasks状态
    const tasks = this.state.tasks;
    // 使用map方法遍历tasks数组,找到与参数id相同的待办事项,然后将其completed属性取反,返回一个新的数组
    const updatedTasks = tasks.map((task) => {
      if (task.id === id) {
        return { ...task, completed: !task.completed };
      } else {
        return task;
      }
    });
    // 使用this.setState来更新tasks状态,将新的数组赋值给tasks
    this.setState({
      tasks: updatedTasks,
    });
  };

  render() {
    return (
      <div className="todoapp">
        <h1>Todo List</h1>
        {/* 给TodoForm组件传递addTask方法 */}
        <TodoForm addTask={this.addTask} />
        <TodoFilter />
        {/* 给TodoList组件传递tasks属性和toggleTask方法 */}
        <TodoList tasks={this.state.tasks} toggleTask={this.toggleTask} />
      </div>
    );
  }
}

export default App;

通过使用State,我们可以实现组件内部的状态管理和更新,让我们的组件更加动态和响应。