面试题:如何使用 React 开发任务记录网站?实现思路是什么?

开发一个任务记录网站(To-Do List)是 React 的经典入门项目,它涵盖了组件化、状态管理、用户交互等核心概念。以下是详细的实现思路和步骤:


一、功能需求分析

一个基础的任务记录网站通常包含以下功能:

  1. 添加任务:输入任务名称并添加到列表。
  2. 查看任务列表:显示所有任务。
  3. 标记完成/未完成:切换任务的完成状态。
  4. 删除任务:移除不再需要的任务。
  5. (可选)编辑任务:修改任务内容。
  6. (可选)筛选任务:按“全部”、“已完成”、“未完成”过滤。

二、技术选型与工具

  • 框架:React
  • 状态管理useState Hook(足够满足此项目)
  • 路由react-router-dom(如果有多页面,如主页、归档页)
  • 样式:CSS Modules / Tailwind CSS / Styled-components 等
  • 构建工具:Create React App 或 Vite

三、组件结构设计(组件化)

将 UI 拆分为可复用的组件:

App
├── Header (标题)
├── AddTodo (添加任务表单)
├── TodoList (任务列表容器)
│   └── TodoItem (单个任务项)
└── Footer (筛选器、统计信息)

四、状态(State)设计

App 组件中定义核心状态:

const [todos, setTodos] = useState([
  { id: 1, text: '学习 React', completed: false },
  { id: 2, text: '写 To-Do 应用', completed: true }
]);

const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed'
  • todos: 存储任务数组,每个任务对象包含 id, text, completed 属性。
  • filter: 当前的筛选状态。

五、核心功能实现

1. 添加任务 (AddTodo 组件)

function AddTodo({ onAdd }) {
  const [input, setInput] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (input.trim() !== '') {
      // 调用父组件传递的回调函数
      onAdd(input);
      setInput(''); // 清空输入框
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="添加新任务..."
      />
      <button type="submit">添加</button>
    </form>
  );
}

App 组件传递 onAdd 回调:

function handleAdd(text) {
  setTodos(prevTodos => [
    ...prevTodos,
    { id: Date.now(), text, completed: false }
  ]);
}

2. 显示和操作任务列表 (TodoListTodoItem)

// TodoItem 组件
function TodoItem({ todo, onToggle, onDelete, onEdit }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editText, setEditText] = useState(todo.text);

  const handleSave = () => {
    onEdit(todo.id, editText.trim());
    setIsEditing(false);
  };

  if (isEditing) {
    return (
      <li>
        <input
          value={editText}
          onChange={(e) => setEditText(e.target.value)}
          onBlur={handleSave}
          onKeyPress={(e) => e.key === 'Enter' && handleSave()}
          autoFocus
        />
      </li>
    );
  }

  return (
    <li>
      <span
        style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        onClick={() => onToggle(todo.id)}
      >
        {todo.text}
      </span>
      <button onClick={() => setIsEditing(true)}>编辑</button>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </li>
  );
}

// TodoList 组件
function TodoList({ todos, onToggle, onDelete, onEdit }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
          onDelete={onDelete}
          onEdit={onEdit}
        />
      ))}
    </ul>
  );
}

App 组件提供回调函数:

const handleToggle = (id) => {
  setTodos(prevTodos =>
    prevTodos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  );
};

const handleDelete = (id) => {
  setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
};

const handleEdit = (id, newText) => {
  if (newText === '') return;
  setTodos(prevTodos =>
    prevTodos.map(todo =>
      todo.id === id ? { ...todo, text: newText } : todo
    )
  );
};

3. 筛选任务 (Footer 组件)

function Footer({ filter, onFilterChange }) {
  const filters = ['all', 'active', 'completed'];

  return (
    <div>
      {filters.map(f => (
        <button
          key={f}
          style={{ fontWeight: filter === f ? 'bold' : 'normal' }}
          onClick={() => onFilterChange(f)}
        >
          {f === 'all' ? '全部' : f === 'active' ? '未完成' : '已完成'}
        </button>
      ))}
    </div>
  );
}

根据 filter 状态计算要显示的任务:

const filteredTodos = todos.filter(todo => {
  if (filter === 'active') return !todo.completed;
  if (filter === 'completed') return todo.completed;
  return true; // 'all'
});

六、数据持久化(进阶)

默认情况下,刷新页面后数据会丢失。可以使用 localStorage 进行简单持久化。

// 在 App 组件中
useEffect(() => {
  const savedTodos = localStorage.getItem('todos');
  if (savedTodos) {
    setTodos(JSON.parse(savedTodos));
  }
}, []);

useEffect(() => {
  localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

七、总结:实现思路

  1. 拆分组件:将 UI 分解为独立、可复用的小组件。
  2. 提升状态:确定哪些数据是共享的,将其提升到共同的父组件(通常是 App)中管理。
  3. 单向数据流:通过 props 将数据和回调函数从父组件传递给子组件。
  4. 响应用户交互:在事件处理函数中调用 setState 来更新状态,触发重新渲染。
  5. 关注点分离AddTodo 负责输入,TodoItem 负责单个任务的展示和操作,Footer 负责筛选。
  6. 不可变性:更新数组或对象状态时,创建新的副本,而不是直接修改。

这个项目虽然简单,但完整地体现了 React 的核心思想:组件化、声明式 UI、单向数据流和状态驱动视图。掌握它,就掌握了 React 开发的基础范式。

THE END
喜欢就支持一下吧
点赞10 分享