返回教程列表
教程 React 快速入门 第 8 节
进阶
30 分钟

React 实战项目

综合运用所学知识,构建一个完整的 React 应用

CodeGogo

项目实战:待办事项应用

让我们综合运用所学的知识,构建一个功能完整的待办事项应用。

项目结构

src/
├── components/
│   ├── TodoList.jsx
│   ├── TodoItem.jsx
│   ├── TodoForm.jsx
│   └── FilterBar.jsx
├── hooks/
│   └── useLocalStorage.js
├── App.jsx
└── main.jsx

1. 自定义 Hook:useLocalStorage

import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

export default useLocalStorage;

2. TodoForm 组件

import { useState } from 'react';

function TodoForm({ onAdd }) {
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      onAdd({
        id: Date.now(),
        text: text.trim(),
        completed: false
      });
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="添加新的待办事项..."
      />
      <button type="submit">添加</button>
    </form>
  );
}

3. TodoItem 组件

import { memo } from 'react';

const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span
        style={{
          textDecoration: todo.completed ? 'line-through' : 'none'
        }}
      >
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </li>
  );
});

export default TodoItem;

4. FilterBar 组件

import { useMemo } from 'react';

function FilterBar({ filter, onFilterChange, todos }) {
  const stats = useMemo(() => {
    const total = todos.length;
    const completed = todos.filter(t => t.completed).length;
    const active = total - completed;
    return { total, completed, active };
  }, [todos]);

  return (
    <div>
      <button
        className={filter === 'all' ? 'active' : ''}
        onClick={() => onFilterChange('all')}
      >
        全部 ({stats.total})
      </button>
      <button
        className={filter === 'active' ? 'active' : ''}
        onClick={() => onFilterChange('active')}
      >
        未完成 ({stats.active})
      </button>
      <button
        className={filter === 'completed' ? 'active' : ''}
        onClick={() => onFilterChange('completed')}
      >
        已完成 ({stats.completed})
      </button>
    </div>
  );
}

5. TodoList 组件

import { useMemo } from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos, onToggle, onDelete }) {
  const sortedTodos = useMemo(() => {
    return [...todos].sort((a, b) => {
      // 未完成的排前面
      if (a.completed === b.completed) {
        return b.id - a.id; // 新的排前面
      }
      return a.completed ? 1 : -1;
    });
  }, [todos]);

  return (
    <ul>
      {sortedTodos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
          onDelete={onDelete}
        />
      ))}
    </ul>
  );
}

export default TodoList;

6. 主 App 组件

import { useState, useMemo, useCallback } from 'react';
import useLocalStorage from './hooks/useLocalStorage';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';
import FilterBar from './components/FilterBar';

function App() {
  const [todos, setTodos] = useLocalStorage('todos', []);
  const [filter, setFilter] = useState('all');

  const filteredTodos = useMemo(() => {
    switch (filter) {
      case 'active':
        return todos.filter(t => !t.completed);
      case 'completed':
        return todos.filter(t => t.completed);
      default:
        return todos;
    }
  }, [todos, filter]);

  const handleAdd = useCallback((todo) => {
    setTodos(prev => [...prev, todo]);
  }, [setTodos]);

  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, [setTodos]);

  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, [setTodos]);

  const handleClearCompleted = useCallback(() => {
    setTodos(prev => prev.filter(todo => !todo.completed));
  }, [setTodos]);

  return (
    <div>
      <h1>待办事项</h1>
      <TodoForm onAdd={handleAdd} />
      <FilterBar
        filter={filter}
        onFilterChange={setFilter}
        todos={todos}
      />
      <TodoList
        todos={filteredTodos}
        onToggle={handleToggle}
        onDelete={handleDelete}
      />
      {todos.some(t => t.completed) && (
        <button onClick={handleClearCompleted}>
          清除已完成
        </button>
      )}
    </div>
  );
}

export default App;

样式

// App.css
.container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

form {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

form input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

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

button:hover {
  background: #0056b3;
}

ul {
  list-style: none;
  padding: 0;
}

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

.active {
  background: #007bff;
}

💡 项目总结

通过这个项目,你学会了:

  1. ✅ 使用自定义 Hook 管理本地存储
  2. ✅ 使用 React.memo 优化列表渲染
  3. ✅ 使用 useMemo 缓存计算结果
  4. ✅ 使用 useCallback 避免不必要的函数重建
  5. ✅ 组件化和状态管理
  6. ✅ 表单处理和事件处理

🎯 下一步学习

恭喜你完成了 React 快速入门!接下来可以:

  • 学习 React Router 实现路由
  • 学习 状态管理库(Redux、Zustand)
  • 学习 React Query 处理服务端状态
  • 学习 Next.js 构建全栈应用
  • 探索 UI 组件库(Material-UI、Ant Design)

继续保持学习的热情,React 的世界很精彩!

觉得这个教程有帮助?

继续学习更多教程,掌握编程技能

查看更多教程