大家好,我是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应用中,我们可以使用useEffect
Hook来实现数据持久化:
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高级模式和实践经验!