全栈笔记之 TodoMVC by React and TypeScript

Table of Contents

这个 TypeScript + React 的 TodoMVC 项目是一个经典的前端示例应用,适合学习如何用 React 和 TypeScript 构建一个小型的功能完整的应用。接下来,我会 详细拆解代码结构和核心逻辑,让你更容易理解。


一、项目结构概览

项目的目录结构如下:

typescript-react/
│── index.html
│── package.json
│── tsconfig.json
│── src/
│   ├── index.tsx
│   ├── components/
│   │   ├── App.tsx
│   │   ├── TodoItem.tsx
│   │   ├── TodoFooter.tsx
│   ├── models/
│   │   ├── Todo.ts

每个文件的作用如下:

  • index.html: HTML 入口文件,包含 <div id="app"></div>,React 应用会挂载到这里。
  • package.json: 记录了项目依赖(React、TypeScript等),以及运行和打包的脚本。
  • tsconfig.json: TypeScript 配置文件,定义了代码编译规则。
  • src/:
    • index.tsx: React 入口文件,把 App 组件渲染到 HTML 中。
    • components/:
    • App.tsx: 应用的核心组件,负责管理待办事项的状态(添加、删除、修改)。
    • TodoItem.tsx: 代表一个待办事项的组件,渲染待办事项,并处理勾选/删除等操作。
    • TodoFooter.tsx: 负责底部筛选、统计和清除已完成任务的功能。
    • models/:
    • Todo.ts: 定义 Todo 类型结构,方便 TypeScript 进行类型检查。

二、代码详细解析

1. index.tsx - 入口文件

import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./components/App";

const root = createRoot(document.getElementById("app")!);
root.render(<App />);

解析:

  • 引入 React 和 ReactDOM,用于渲染 React 组件。
  • createRoot(document.getElementById("app")!): 在 index.html 里找到 <div id="app"> 并挂载 React 应用。
  • root.render(<App />);: 渲染 App 组件,它是应用的根组件。

2. models/Todo.ts - 定义 Todo 类型

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

解析:

  • 定义 Todo 类型,有 id(唯一标识)、text(任务内容)、completed(是否完成)。
  • 这个类型被 App.tsxTodoItem.tsx 用来确保传递的数据符合预期。

3. App.tsx - 应用的核心组件

import React, { useState } from "react";
import { Todo } from "../models/Todo";
import { TodoItem } from "./TodoItem";
import { TodoFooter } from "./TodoFooter";

export const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

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

  const toggleTodo = (id: number) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const removeTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <h1>待办事项</h1>
      <input
        type="text"
        placeholder="添加新的待办事项"
        onKeyDown={(e) => {
          if (e.key === "Enter" && e.currentTarget.value) {
            addTodo(e.currentTarget.value);
            e.currentTarget.value = "";
          }
        }}
      />
      <ul>
        {todos.map((todo) => (
          <TodoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} removeTodo={removeTodo} />
        ))}
      </ul>
      <TodoFooter todos={todos} setTodos={setTodos} />
    </div>
  );
};

解析:

  • useState<Todo[]>([]): 定义 todos 状态(存储所有待办事项)。
  • addTodo(text): 按 Enter 键添加新任务。
  • toggleTodo(id): 切换任务状态(已完成/未完成)。
  • removeTodo(id): 删除任务。
  • 渲染 TodoItem 组件,并传递 todo 数据和操作方法。

4. TodoItem.tsx - 单个待办事项

import React from "react";
import { Todo } from "../models/Todo";

interface TodoItemProps {
  todo: Todo;
  toggleTodo: (id: number) => void;
  removeTodo: (id: number) => void;
}

export const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleTodo, removeTodo }) => {
  return (
    <li>
      <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} />
      <span style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
        {todo.text}
      </span>
      <button onClick={() => removeTodo(todo.id)}>删除</button>
    </li>
  );
};

解析:

  • todo: 一个待办事项对象(textcompleted 等)。
  • toggleTodo(todo.id): 点击复选框切换完成状态。
  • removeTodo(todo.id): 点击删除按钮移除任务。

5. TodoFooter.tsx - 底部筛选和清除功能

import React from "react";
import { Todo } from "../models/Todo";

interface TodoFooterProps {
  todos: Todo[];
  setTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
}

export const TodoFooter: React.FC<TodoFooterProps> = ({ todos, setTodos }) => {
  const clearCompleted = () => {
    setTodos(todos.filter(todo => !todo.completed));
  };

  return (
    <footer>
      <span>剩余任务: {todos.filter(todo => !todo.completed).length} 项</span>
      <button onClick={clearCompleted}>清除已完成任务</button>
    </footer>
  );
};

解析:

  • 统计未完成任务数量 并显示出来。
  • 提供 "清除已完成任务" 按钮,点击后删除所有 completed: true 的任务。

三、总结

  1. index.tsx 入口文件:加载 App 组件。
  2. App.tsx 负责管理 任务列表的状态,并渲染 TodoItemTodoFooter 组件。
  3. TodoItem.tsx 负责 渲染单个待办事项,支持切换完成状态和删除任务。
  4. TodoFooter.tsx 负责 任务统计和清除已完成任务

整个应用基于 React 的 组件化架构,使用 TypeScript 进行 类型检查,让代码更安全可维护。你可以尝试自己修改代码,加深理解!

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: 似水流年