大家好,我是FogLetter,今天继续我们的React Todos应用开发之旅。上次我们完成了基础功能,这次将深入两个React开发中的高级话题:本地存储实现数据持久化和自定义Hooks的优雅封装。这些技巧将让你的React应用更加专业和强大。
一、本地存储:让数据在刷新后依然存在
1.1 localStorage基础入门
localStorage是HTML5提供的Web存储API,它允许我们在浏览器中存储键值对数据:
            
            
              javascript
              
              
            
          
          // 存储数据
localStorage.setItem("title", "本地存储示例");
// 获取数据
const title = localStorage.getItem("title");
// 删除数据
localStorage.removeItem("title");关键特性:
- 存储容量约5MB(远大于cookie的4KB)
- 数据不会随HTTP请求发送到服务器
- 遵循同源策略,不同域名无法共享
- 存储的是字符串,对象需要JSON序列化
1.2 localStorage vs cookie
很多新手会混淆这两者,我们来做个对比:
| 特性 | localStorage | cookie | 
|---|---|---|
| 容量 | 5MB左右 | 4KB | 
| 是否随请求发送 | 否 | 是 | 
| 访问权限 | 仅客户端 | 客户端和服务器 | 
| API易用性 | 简单 | 较复杂 | 
| 过期时间 | 永不过期 | 可设置过期时间 | 
1.3 在React中集成localStorage
在我们的Todos应用中,我们可以使用useEffectHook来实现数据持久化:
            
            
              jsx
              
              
            
          
          const [todos, setTodos] = useState(() => {
  const savedTodos = localStorage.getItem("todos");
  return savedTodos ? JSON.parse(savedTodos) : [];
});
useEffect(() => {
  localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);技巧:
- 使用函数式初始状态避免每次渲染都读取localStorage
- 依赖数组[todos]确保只在todos变化时更新存储
- JSON.stringify/parse处理对象序列化
1.4 更健壮的存储方案
对于生产环境,我推荐使用localForage库,它提供了:
- 异步API(避免阻塞主线程)
- 自动回退机制(IndexedDB → WebSQL → localStorage)
- 支持Promise
- 自动进行序列化和反序列化
            
            
              javascript
              
              
            
          
          import localForage from "localforage";
const [todos, setTodos] = useState([]);
useEffect(() => {
  localForage.getItem("todos").then(savedTodos => {
    if (savedTodos) setTodos(savedTodos);
  });
}, []);
useEffect(() => {
  localForage.setItem("todos", todos);
}, [todos]);二、自定义Hooks:逻辑复用的艺术
2.1 什么是自定义Hooks?
自定义Hooks是React 16.8引入的机制,它让你可以提取组件逻辑到可重用的函数中。简单说:
- 以use开头的函数
- 可以调用其他Hooks
- 不包含UI,只包含逻辑
2.2 为什么需要自定义Hooks?
在标准Todos实现中,我们发现几个问题:
- 状态逻辑与UI渲染混杂
- 相同逻辑在多个组件间难以复用
- 组件变得臃肿难维护
自定义Hooks正是解决这些问题的银弹!
2.3 创建useTodos Hook
让我们将Todos逻辑提取到单独的Hook中:
            
            
              jsx
              
              
            
          
          // src/hooks/useTodos.js
import { useState, useEffect } from "react";
export const useTodos = (initialValue = []) => {
  const [todos, setTodos] = useState(() => {
    const saved = localStorage.getItem("todos");
    return saved ? JSON.parse(saved) : initialValue;
  });
  useEffect(() => {
    localStorage.setItem("todos", JSON.stringify(todos));
  }, [todos]);
  const addTodo = (text) => {
    setTodos([...todos, {
      id: Date.now(),
      text,
      isComplete: false
    }]);
  };
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, isComplete: !todo.isComplete } : todo
    ));
  };
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  return {
    todos,
    addTodo,
    toggleTodo,
    deleteTodo,
    setTodos
  };
};2.4 使用自定义Hook改造组件
现在我们的Todos组件变得极其简洁:
            
            
              jsx
              
              
            
          
          import { useTodos } from "@/hooks/useTodos";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
const Todos = () => {
  const { todos, addTodo, toggleTodo, deleteTodo } = useTodos();
  
  return (
    <div className="app">
      <h1>Todos</h1>
      <TodoForm onAddTodo={addTodo} />
      <TodoList 
        todos={todos}
        onToggle={toggleTodo}
        onDelete={deleteTodo}
      />
    </div>
  );
};优势:
- UI与逻辑完全分离
- 状态管理可复用
- 组件职责单一
- 测试更加容易
2.5 项目结构优化
推荐的自定义Hooks项目结构:
            
            
              bash
              
              
            
          
          /src
  /hooks
    useTodos.js
    useLocalStorage.js  # 可进一步抽象
    /common
      useDebounce.js
      useThrottle.js
    /business
      useUserProfile.js三、工程化配置:路径别名
3.1 告别"../../"地狱
在React项目中,我们经常看到这样的导入:
            
            
              jsx
              
              
            
          
          import { Button } from "../../../../components/Button";这既难看又容易出错。Vite提供了优雅的解决方案:
            
            
              js
              
              
            
          
          // vite.config.js
import { defineConfig } from "vite";
import path from "path";
export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});现在可以这样导入:
            
            
              jsx
              
              
            
          
          import { useTodos } from "@/hooks/useTodos";六、总结与最佳实践
通过本文,我们学习了:
- 本地存储集成:使用localStorage实现数据持久化
- 自定义Hooks:将逻辑从UI中解耦
- 工程化配置:路径别名优化导入路径
React进阶黄金法则:
- 逻辑与UI分离是高质量组件的关键
- 自定义Hooks是逻辑复用的最佳方式
- 工程化配置能显著提升开发体验
如果你觉得这篇文章有帮助,请点赞收藏,我会继续分享更多React高级模式和实践经验!