React
数据持久化
在 React 应用中,页面刷新会导致状态丢失。通过浏览器存储、URL 状态或状态管理库,可以实现数据的持久化保存,保证用户体验连贯。
一、主流持久化方案
| 方案 | 存储空间 | 适用场景 | 特点 |
|---|---|---|---|
| localStorage | 5-10MB | 用户偏好、表单数据 | 永久保存 |
| sessionStorage | 5-10MB | 临时会话数据 | 关闭页面清除 |
| URL 参数 | 受浏览器限制 | 筛选条件、页面状态 | 可分享、可收藏 |
| Redux Persist | 由存储方式决定 | 复杂全局状态 | 自动持久化 |
二、localStorage 方案
最常用的本地存储方案,适合保存用户偏好和表单草稿。
基础用法:状态初始化和自动保存
function TodoList() {
const [todos, setTodos] = React.useState<string[]>(() => {
// 组件挂载时从 localStorage 读取
const saved = localStorage.getItem('todos')
return saved ? JSON.parse(saved) : []
})
// 状态变化时自动保存
React.useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos))
}, [todos])
const addTodo = (text: string) => {
setTodos([...todos, text])
}
return (
<div>
<ul>
{todos.map((todo, i) => (
<li key={i}>{todo}</li>
))}
</ul>
<button onClick={() => addTodo('新任务')}>添加</button>
</div>
)
}防抖保存(避免频繁写入)
function AutoSaveForm() {
const [formData, setFormData] = React.useState(() => {
const saved = localStorage.getItem('formData')
return saved ? JSON.parse(saved) : { title: '', content: '' }
})
React.useEffect(() => {
// 防抖:1秒无操作后才保存
const timer = setTimeout(() => {
localStorage.setItem('formData', JSON.stringify(formData))
}, 1000)
return () => clearTimeout(timer)
}, [formData])
return (
<div>
<input
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
placeholder="标题"
/>
<textarea
value={formData.content}
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
placeholder="内容"
/>
</div>
)
}错误处理
function getStoredData<T>(key: string, defaultValue: T): T {
try {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : defaultValue
} catch (error) {
console.error(`Failed to read ${key} from localStorage:`, error)
return defaultValue
}
}
function MyComponent() {
const [data, setData] = React.useState(() =>
getStoredData('myData', { count: 0 }),
)
return <div>{data.count}</div>
}三、URL 状态
适合保存筛选条件、搜索参数等可共享的页面状态。
import { useSearchParams } from 'react-router-dom'
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
// 从 URL 读取搜索词
const keyword = searchParams.get('keyword') || ''
const category = searchParams.get('category') || 'all'
const handleSearch = (value: string) => {
setSearchParams({ keyword: value, category })
}
const handleCategoryChange = (value: string) => {
setSearchParams({ keyword, category: value })
}
return (
<div>
<input
value={keyword}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索"
/>
<select
value={category}
onChange={(e) => handleCategoryChange(e.target.value)}
>
<option value="all">全部</option>
<option value="articles">文章</option>
<option value="users">用户</option>
</select>
</div>
)
}四、Redux Persist 方案
结合 Redux 实现复杂状态的自动持久化。
import { configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { rootReducer } from './slices'
const persistConfig = {
key: 'root',
storage,
// 只持久化指定的状态片段
whitelist: ['user', 'settings'],
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export const store = configureStore({
reducer: persistedReducer,
})
export const persistor = persistStore(store)在应用入口使用 PersistGate 包裹:
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from './store'
function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<MainApp />
</PersistGate>
</Provider>
)
}五、跨标签页同步
同一浏览器的不同标签页可以通过 storage 事件同步数据。
function SyncedComponent() {
const [userData, setUserData] = React.useState(() =>
getStoredData('userData', null),
)
React.useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
// 监听其他标签页的 localStorage 变化
if (e.key === 'userData' && e.newValue) {
setUserData(JSON.parse(e.newValue))
}
}
window.addEventListener('storage', handleStorageChange)
return () => window.removeEventListener('storage', handleStorageChange)
}, [])
return <div>{userData?.name}</div>
}六、数据有效性检查
某些场景下需要检查存储的数据是否过期。
interface CachedData {
value: any
timestamp: number
ttl: number // 生存时间(毫秒)
}
function isCacheValid(cached: CachedData): boolean {
return Date.now() - cached.timestamp < cached.ttl
}
function getOrFetchData(key: string, fetcher: () => Promise<any>) {
const cached = getStoredData<CachedData | null>(key, null)
if (cached && isCacheValid(cached)) {
return cached.value
}
// 缓存过期或不存在,重新获取
return fetcher().then((data) => {
localStorage.setItem(
key,
JSON.stringify({
value: data,
timestamp: Date.now(),
ttl: 3600000, // 1 小时
}),
)
return data
})
}核心选择:简单数据用 localStorage(防抖处理避免频繁写入),可分享的状态用 URL 参数,复杂全局状态用 Redux Persist,跨标签页需要同步时监听 storage 事件,有失效需求时加时间戳和 TTL 检查。