React 自定义 Hook Todo 应用全流程实战

搭建思路:实现一个简单的待办事项(Todos)应用,支持添加、切换完成状态、删除待办,并采用现代 React(函数组件 + 自定义 Hook)实现,结构清晰、易维护。

一、开发前的准备工作:项目初始化和样式设计

1.使用 Vite 初始化 React 项目

bash 复制代码
npm create vite@latest . -- --template react
npm install

选择 react 模板,Vite 会自动生成基础目录和配置。

bash 复制代码
npm run dev

浏览器访问本地地址,看到 React 欢迎页即初始化成功。

2.清理模板代码

  1. 删除 App.css、App.jsx、main.jsx 等文件中的多余内容,只保留最基础的结构。
  2. 删除或重命名 Vite 自带的 logo、svg 等资源。

3.搭建现代化的 CSS 环境:Stylus

  • 为什么用 Stylus?
    Stylus 是一种 CSS 预处理器,支持变量、嵌套、函数等高级特性,让样式编写更高效、结构更清晰。
  • 如何集成?
    1. 安装依赖:npm install stylus stylus-loader --save-dev
    2. 在 src/global.styl 编写全局样式,体验变量、嵌套等特性。
    3. 在组件中 import '../global.styl' 即可生效。

(二)组件架构

css 复制代码
src/
├── App.jsx:根组件
├── components/:存放 UI 组件
│   └── todos/:待办相关组件
│       ├── index.jsx:聚合组件
│       ├── TodoForm.jsx:新增表单
│       ├── TodoList.jsx:列表
│       └── TodoItem.jsx:单项
├── hooks/:自定义 Hook
│   └── useTodos.js
├── index.jsx、main.jsx:入口文件
└── 样式文件

顶层组件 App

  • 作为应用的根组件,负责整体布局和页面结构。

2. 业务主组件 Todos

  • 负责待办事项的核心功能和页面展示。
  • 在内部调用自定义 Hook(useTodos)来管理 todos 的数据和操作方法。
  • 只关注 UI 结构和 props 传递,不处理具体业务逻辑。

3. 子组件

1)TodoForm

  • 新增待办事项的表单组件。
  • 包含输入框和添加按钮,用户输入内容后调用 addTodo 方法。

2)TodoList

  • 展示所有待办事项的列表组件。
  • 接收 todos 数据,遍历渲染每一项。

3)TodoItem

  • 单个待办事项的展示组件。
  • 显示 todo 内容,支持切换完成状态和删除操作。

二、React相关实现原理

(一)组件通信

  • props 单向传递

    jsx 复制代码
    // 父组件
    <TodoForm addTodo={addTodo} />
    // 子组件
    props.addTodo('新任务')
  • 父组件定义一个函数(比如 addTodo),通过 props 传递给子组件(TodoForm)。

  • 子组件拿到 props.addTodo 后,调用它并传参(比如 '新任务'),实现 "子组件通知父组件执行逻辑"。

(二)数据绑定

  • useState 实现响应式

    jsx 复制代码
    const [todos, setTodos] = useState([]);
  • todos 是当前状态(初始值是空数组 []),存所有待办项。

  • setTodos 是修改状态的函数,调用它会触发组件重新渲染。

  • 数据驱动视图

    jsx 复制代码
    {todos.map(todo => <TodoItem todo={todo} />)}
  • 遍历 todos 数组(状态数据),为每个 todo 项生成一个 TodoItem 组件。

  • 通过 props.todo 把单个待办数据传给子组件(TodoItem),让子组件渲染具体内容。

(三)本地存储

js 复制代码
useEffect(() => {
  localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
  • 利用 useEffect 钩子,在 todos 状态发生变化时,把最新的待办事项列表 todos 存储到浏览器本地存储(localStorage)中。
js 复制代码
const [todos, setTodos] = useState(() => {
  const saved = localStorage.getItem('todos');
  return saved ? JSON.parse(saved) : [];
});
  • 组件初始化时,从 localStorage 中读取之前存储的待办事项数据,作为 todos 状态的初始值 。
  • return saved ? JSON.parse(saved) : [] :如果获取到了数据(saved 存在),就用 JSON.parse 把 JSON 字符串转成数组(还原成 JavaScript 能识别的对象 / 数组格式);要是没获取到(比如第一次使用,本地还没存),就返回空数组 [] ,作为 todos 的初始值 。

三、自定义 Hook 实战

(一)什么是自定义 Hook?

在 React 中,自定义 Hook(Custom Hook)是以 use 开头的函数,用来复用组件之间的状态逻辑。

它本质上就是一个普通的 JS 函数,但可以使用 React 的内置 Hook(如 useState、useEffect 等)。

(二)useTodos Hook: 集中管理"待办事项"的所有业务逻辑和状态

js 复制代码
import { useState } from "react"; // 引入 React 的 useState,用于声明状态变量

export default function useTodos() { // 定义自定义 Hook,名称以 use 开头,方便复用逻辑
  const [todos, setTodos] = useState([]); // 声明 todos 状态,初始值为空数组,setTodos 用于更新 todos

  // 新增 todo 的方法,接收文本参数 text
  const addTodo = (text) => {
    setTodos([
      ...todos, // 保留原有 todos
      {
        id: Date.now(), // 用当前时间戳生成唯一 id
        text, // 用户输入的内容
        completed: false, // 新增的 todo 默认未完成
      },
    ]);
  };

  // 切换 todo 完成状态的方法,接收要切换的 todo 的 id
  const toggleTodo = (id) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id // 找到 id 匹配的 todo
          ? { ...todo, completed: !todo.completed } // 切换 completed 字段
          : todo // 其他 todo 保持不变
      )
    );
  };

  // 删除 todo 的方法,接收要删除的 todo 的 id
  const removeTodo = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id)); // 过滤掉 id 匹配的 todo,保留其他 todos
  };

  // 返回 todos 状态和三个操作方法,供组件使用
  return {
    todos, // 当前所有待办事项
    addTodo, // 新增 todo 的方法
    toggleTodo, // 切换完成状态的方法
    removeTodo, // 删除 todo 的方法
  };
}

(三)Todos 组件: 不关心数据怎么处理,只用 Hook 提供的能力

jsx 复制代码
import useTodos from '../hooks/useTodos'; // 引入自定义 Hook,用于管理 todos 的数据和逻辑
import TodoForm from './TodoForm.jsx'; // 引入新增待办事项的表单组件
import TodoList from './TodoList.jsx'; // 引入展示待办事项列表的组件

export default function Todos() { // 定义 Todos 组件,作为 todos 功能的主界面
  // 调用 useTodos 自定义 Hook,获取 todos 数据和操作方法
  // 这样 UI 组件只关注界面,所有业务逻辑都交给 Hook
  const { todos, addTodo, removeTodo, toggleTodo } = useTodos();

  // 渲染 UI:包含新增表单和待办列表
  // addTodo 传给 TodoForm,负责添加新待办
  // todos、toggleTodo、removeTodo 传给 TodoList,负责展示、切换和删除
  return (
    <div>
      <TodoForm addTodo={addTodo} /> {/* 表单组件,用户输入后调用 addTodo 新增待办 */}
      <TodoList todos={todos} toggleTodo={toggleTodo} removeTodo={removeTodo} /> {/* 列表组件,展示所有 todos,并支持切换和删除 */}
    </div>
  );
}

四、进阶优化

(一)路径别名:告别 "../../" 地狱

  • 配置路径别名
    在 jsconfig.json 或 vite.config.js 里配置 @ 指向 src,引入更清晰。

    json 复制代码
    {
      "compilerOptions": {
        "baseUrl": "src"
      }
    }
    js 复制代码
    // 现在可以这样写
    import useTodos from '@/hooks/useTodos';

(二)跨组件通信优化:用 useContext 解决 "层级太深"

  • Context 方案
    当多个层级需要共享 todos 或操作时,用 Context 提供全局状态,避免 props 层层传递。

    js 复制代码
    // 创建 Context
    export const TodoContext = React.createContext();
    // Provider 包裹
    <TodoContext.Provider value={...}>{children}</TodoContext.Provider>
    // 子组件用 useContext(TodoContext) 获取

(三)性能细节

1. 组件拆分与最小化重渲染

组件拆分

项目将 UI 拆分为 TodoForm、TodoList、TodoItem 等小组件,每个组件只负责自己的功能。这样可以减少每次状态变化时的重渲染范围,提高渲染效率。

只传递必要的 props

父组件 Todos 只把必要的数据和方法传递给子组件,避免无关数据导致子组件无谓重渲染。

2. 自定义 Hook 的性能优势

  • useTodos 自定义 Hook 将所有业务逻辑集中管理,组件只负责 UI。
  • 这样可以让逻辑复用,减少重复代码,也让状态变更只影响真正需要的组件。

3. React 内置优化机制

useState 的局部性

  • useState 只会引起当前组件的重渲染,不会影响无关组件。

  • 只有当 todos、addTodo、removeTodo、toggleTodo 这些 props 发生变化时,TodoList、TodoForm 等子组件才会重渲染。

小结

该项目通过自定义 Hook(useTodos)将待办事项的增删改查等业务逻辑与 UI 组件彻底分离,实现了高内聚、低耦合的代码结构。组件架构层次分明,主功能组件 Todos 负责状态管理和方法分发,子组件如TodoForm、TodoList、TodoItem 各司其职,专注于界面展示和用户交互。

相关推荐
Highcharts.js4 小时前
React 中原生支持使用 JSX 组件配置自定义表格 Highcharts Grid
前端·javascript·react.js·前端框架·jsx·highcharts·自定义表格
JudithHuang5 小时前
React 嵌套路由与布局
前端·react.js·arcgis·react·react router
前端小D8 小时前
React Native项目开发(iOS侧为例)
javascript·react native·react.js
志尊宝1 天前
AI原生前端开发(042):AI辅助的路由设计与优化
javascript·react.js·ai-native
znsoft2 天前
MaClaw(码卡龙)技术拆解:一个 Go + React 的可自进化智能体平台
开发语言·react.js·golang
濮水大叔2 天前
React/Next.js 生态里,有没有 CabloyJS 这样的全栈框架?
react.js·全栈·next.js
前端小D2 天前
React Native Android 项目搭建
android·react native·react.js
Highcharts.js3 天前
Highcharts 集成到 React 或 Vue 项目中时,如何高效地使用 TypeScript技巧?
vue.js·人工智能·react.js·typescript·前端框架·highcharts
liuchangng4 天前
React 19 + Vite 6 实战:从教材 PDF 到 866 词间隔重复单词 App
前端·react.js·pdf·vite·开源项目
鸿蒙开发5 天前
Three.js 首帧卡顿排查:compileAsync 之后,为什么还会停一下?
react.js·three.js