React 实现待办事项列表并扩展状态管理

52 阅读4分钟

React 实现待办事项列表并扩展状态管理

在现代前端开发中,状态管理是构建复杂应用程序的核心部分。对于一个简单的待办事项列表,可以通过 React 的本地状态(useState)来管理。然而,在大型项目中,随着状态的复杂化和组件间通信的增多,管理状态变得更加困难。通过引入状态管理工具(如 Redux 或 Context API),可以更高效地管理全局状态,并使代码更具扩展性和可维护性。

本文将从一个简单的待办事项列表入手,展示如何通过本地状态实现功能,并逐步引入 Redux 和 Context API 扩展状态管理,探讨大型项目中的最佳实践。

一、使用 React 本地状态实现基础功能

1. 项目需求

实现一个待办事项列表,包含以下功能:

• 添加新任务。

• 标记任务完成状态。

• 删除任务。

2. React 本地状态实现

以下是使用 useState 的基本实现。

代码实现:

import React, { useState } from "react";

function TodoApp() {

  const [tasks, setTasks] = useState([]);

  const [newTask, setNewTask] = useState("");

  const addTask = () => {

    if (newTask.trim() === "") return;

    setTasks([...tasks, { id: Date.now(), text: newTask, completed: false }]);

    setNewTask("");

  };

  const toggleTask = (id) => {

    setTasks(

      tasks.map((task) =>

        task.id === id ? { ...task, completed: !task.completed } : task

      )

    );

  };

  const deleteTask = (id) => {

    setTasks(tasks.filter((task) => task.id !== id));

  };

  return (

    

      

Todo List

      <input

        type="text"

        value={newTask}

        onChange={(e) => setNewTask(e.target.value)}

      />

      <button onClick={addTask}>Add Task

      

            {tasks.map((task) => (

              <li key={task.id}>

                <span

                  style={{

                    textDecoration: task.completed ? "line-through" : "none",

                  }}

                  onClick={() => toggleTask(task.id)}

                >

                  {task.text}

                

                <button onClick={() => deleteTask(task.id)}>Delete

              

            ))}

          

    

  );

}

export default TodoApp;

3. 分析基础实现

在上述代码中:

  1. useState 用于管理任务列表和输入框内容的状态。

  2. 通过数组的增删改操作实现任务的添加、完成状态切换和删除功能。

局限性

• 当组件层级加深时,状态的共享变得困难。

• 如果需要将任务列表的状态共享给多个组件,会产生“状态提升”和复杂的 props 传递。

二、扩展状态管理:使用 Context API

1. Context API 的介绍

React 的 Context API 是一种轻量级的状态管理工具,适用于共享全局状态的场景。它通过 Context.Provider 提供状态,通过 useContext 消费状态,避免了组件间复杂的 props 传递。

2. 改造代码

将任务状态提取到 Context 中,并通过 Provider 提供状态和操作方法。

代码实现:

任务状态 Context:

import React, { createContext, useContext, useState } from "react";

// 创建 Context

const TaskContext = createContext();

// 提供者组件

export const TaskProvider = ({ children }) => {

  const [tasks, setTasks] = useState([]);

  const addTask = (text) => {

    if (text.trim() === "") return;

    setTasks([...tasks, { id: Date.now(), text, completed: false }]);

  };

  const toggleTask = (id) => {

    setTasks(

      tasks.map((task) =>

        task.id === id ? { ...task, completed: !task.completed } : task

      )

    );

  };

  const deleteTask = (id) => {

    setTasks(tasks.filter((task) => task.id !== id));

  };

  return (

    <TaskContext.Provider value={{ tasks, addTask, toggleTask, deleteTask }}>

      {children}

    </TaskContext.Provider>

  );

};

// 使用 Context 的自定义钩子

export const useTasks = () => useContext(TaskContext);

待办事项组件:

import React, { useState } from "react";

import { TaskProvider, useTasks } from "./TaskContext";

function TaskInput() {

  const [newTask, setNewTask] = useState("");

  const { addTask } = useTasks();

  const handleAdd = () => {

    addTask(newTask);

    setNewTask("");

  };

  return (

    

      <input

        type="text"

        value={newTask}

        onChange={(e) => setNewTask(e.target.value)}

      />

      <button onClick={handleAdd}>Add Task

    

  );

}

function TaskList() {

  const { tasks, toggleTask, deleteTask } = useTasks();

  return (

    

          {tasks.map((task) => (

            <li key={task.id}>

              <span

                style={{

                  textDecoration: task.completed ? "line-through" : "none",

                }}

                onClick={() => toggleTask(task.id)}

              >

                {task.text}

              

              <button onClick={() => deleteTask(task.id)}>Delete

            

          ))}

        

  );

}

function TodoApp() {

  return (

    

      

Todo List with Context API

      

      

    

  );

}

export default TodoApp;

3. 分析 Context API 的改造

  1. 状态共享

• TaskProvider 作为状态提供者,可以共享状态给所有子组件,避免了复杂的 props 传递。

  1. 自定义钩子

• useTasks 简化了组件中状态和方法的调用。

适用场景

• 状态相对简单且集中时,Context API 是理想选择。

局限性

• 状态更新时,所有使用该 Context 的组件都会重新渲染,可能影响性能。

三、扩展状态管理:使用 Redux

1. Redux 的介绍

Redux 是一个流行的 JavaScript 状态管理工具,适用于复杂的应用程序。它通过全局状态树存储状态,并通过 dispatch 和 action更新状态,提供可预测的状态管理。

2. Redux 改造代码

将任务状态管理迁移到 Redux。

Redux 配置:

Actions:

export const addTask = (text) => ({

  type: "ADD_TASK",

  payload: text,

});

export const toggleTask = (id) => ({

  type: "TOGGLE_TASK",

  payload: id,

});

export const deleteTask = (id) => ({

  type: "DELETE_TASK",

  payload: id,

});

Reducer:

const initialState = {

  tasks: [],

};

export const taskReducer = (state = initialState, action) => {

  switch (action.type) {

    case "ADD_TASK":

      return {

        ...state,

        tasks: [

          ...state.tasks,

          { id: Date.now(), text: action.payload, completed: false },

        ],

      };

    case "TOGGLE_TASK":

      return {

        ...state,

        tasks: state.tasks.map((task) =>

          task.id === action.payload

            ? { ...task, completed: !task.completed }

            : task

        ),

      };

    case "DELETE_TASK":

      return {

        ...state,

        tasks: state.tasks.filter((task) => task.id !== action.payload),

      };

    default:

      return state;

  }

};

Store 配置:

import { createStore } from "redux";

import { taskReducer } from "./reducers";

export const store = createStore(taskReducer);

React 组件:

通过 react-redux 连接组件。

import React from "react";

import { useSelector, useDispatch } from "react-redux";

import { addTask, toggleTask, deleteTask } from "./actions";

function TodoApp() {

  const tasks = useSelector((state) => state.tasks);

  const dispatch = useDispatch();

  const handleAdd = (text) => {

    dispatch(addTask(text));

  };

  return (

    

      

Todo List with Redux

      <input type="text" onBlur={(e) => handleAdd(e.target.value)} />

      

            {tasks.map((task) => (

              

  •             <span

                  style={{

                    textDecoration: task.completed ? "line-through" : "none",

                  }}

                  onClick={() => dispatch(toggleTask(task.id))}

                >

                  {task.text}

                </