用Create-React-App搭建TODO List

用Create-React-App搭建TODO List必须包含:
  ✅ 自定义Hook实现持久化存储
  ✅ Context API 跨组件状态管理
  
Todo List 的详细实现步骤:


一、项目初始化

1. 创建项目

npx create-react-app todo-list-context
cd todo-list-context

2. 清理默认文件

删除 src 目录下不必要的文件(如 App.test.js, logo.svg 等),仅保留:

src/
  ├── App.js
  ├── index.js
  └── styles.css (新建)

二、核心代码实现

1. 创建自定义 Hook useLocalStorage(持久化存储)

新建文件 src/hooks/useLocalStorage.js

import { useState, useEffect } from 'react';

export function useLocalStorage(key, initialValue) {
  // 从 localStorage 读取初始值
  const [value, setValue] = useState(() => {
    const storedValue = localStorage.getItem(key);
    return storedValue ? JSON.parse(storedValue) : initialValue;
  });

  // 当 value 变化时更新 localStorage
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

2. 创建 Context 状态管理

新建文件 src/contexts/TodoContext.js

import { createContext, useContext } from 'react';
import { useLocalStorage } from '../hooks/useLocalStorage';

const TodoContext = createContext();

export function TodoProvider({ children }) {
  // 使用自定义 Hook 持久化 todos
  const [todos, setTodos] = useLocalStorage('todos', []);

  // 添加 Todo
  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, completed: false }]);
  };

  // 切换完成状态
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  // 删除 Todo
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  // 全选/全不选
  const toggleAll = (completed) => {
    setTodos(todos.map(todo => ({ ...todo, completed })));
  };

  return (
    <TodoContext.Provider value={{ todos, addTodo, toggleTodo, deleteTodo, toggleAll }}>
      {children}
    </TodoContext.Provider>
  );
}

export function useTodos() {
  return useContext(TodoContext);
}

3. 主组件 App.js

import { TodoProvider } from './contexts/TodoContext';
import TodoList from './components/TodoList';
import AddTodo from './components/AddTodo';
import ToggleAll from './components/ToggleAll';
import './styles.css';

export default function App() {
  return (
    <TodoProvider>
      <div className="app">
        <h1>Todo List</h1>
        <AddTodo />
        <ToggleAll />
        <TodoList />
      </div>
    </TodoProvider>
  );
}

4. 子组件实现

(1) AddTodo.js(添加新 Todo)
import { useState } from 'react';
import { useTodos } from '../contexts/TodoContext';

export default function AddTodo() {
  const [text, setText] = useState('');
  const { addTodo } = useTodos();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      addTodo(text.trim());
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new todo..."
      />
      <button type="submit">Add</button>
    </form>
  );
}
(2) TodoList.js(显示 Todo 列表)
import { useTodos } from '../contexts/TodoContext';
import TodoItem from './TodoItem';

export default function TodoList() {
  const { todos } = useTodos();

  return (
    <ul className="todo-list">
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}
(3) TodoItem.js(单个 Todo 项)
import { useTodos } from '../contexts/TodoContext';

export default function TodoItem({ todo }) {
  const { toggleTodo, deleteTodo } = useTodos();

  return (
    <li className={todo.completed ? 'completed' : ''}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => toggleTodo(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => deleteTodo(todo.id)}>Delete</button>
    </li>
  );
}
(4) ToggleAll.js(全选/全不选)
import { useTodos } from '../contexts/TodoContext';

export default function ToggleAll() {
  const { todos, toggleAll } = useTodos();
  const allCompleted = todos.every(todo => todo.completed);

  return (
    <div className="toggle-all">
      <input
        type="checkbox"
        checked={allCompleted}
        onChange={() => toggleAll(!allCompleted)}
      />
      <label>Toggle All</label>
    </div>
  );
}

5. 样式文件 styles.css

.app {
  max-width: 600px;
  margin: 20px auto;
  padding: 20px;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-list li {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.todo-list li.completed span {
  text-decoration: line-through;
  color: #999;
}

input[type="text"] {
  width: 70%;
  padding: 8px;
  margin-right: 5px;
}

button {
  padding: 8px 12px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #0056b3;
}

.toggle-all {
  margin: 10px 0;
}

三、运行与测试

1. 启动项目

npm start

2. 功能验证

  • ✅ 添加新 Todo
  • ✅ 切换单个/全部完成状态
  • ✅ 删除 Todo
  • ✅ 刷新页面后数据保留(持久化验证)

四、项目结构总结

src/
├── components/
│   ├── AddTodo.js
│   ├── TodoList.js
│   ├── TodoItem.js
│   └── ToggleAll.js
├── contexts/
│   └── TodoContext.js
├── hooks/
│   └── useLocalStorage.js
├── App.js
├── index.js
└── styles.css

五、技术要点

  1. 自定义 Hook
    useLocalStorage 封装了 localStorage 的读写逻辑,实现状态持久化。
  2. Context API
    • TodoProvider 包裹整个应用,提供全局状态
    • useTodos 方便子组件获取状态和方法
  3. 组件拆分
    将 UI 拆分为 AddTodoTodoListTodoItem 等可复用组件
  4. 单向数据流
    通过 Context 传递状态和操作方法,避免 prop drilling

这个实现方案完整覆盖了 React Hooks、Context API 和持久化存储的核心知识点,适合作为学习模板。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值