React頁面刷新數(shù)據(jù)不丟失的五種方案
一、問題場景:為什么數(shù)據(jù)會丟失?
我們先通過一個簡單例子感受一下問題:
function UserForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
preferences: {
theme: 'light',
notifications: true
}
});
// 用戶輸入數(shù)據(jù)...
// 頁面刷新 → 所有數(shù)據(jù)歸零 ??
return (
<form>
<input
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
{/* 其他表單項 */}
</form>
);
}
數(shù)據(jù)丟失的根本原因是:React狀態(tài)(state)存儲在內(nèi)存中,頁面刷新時內(nèi)存被清空。
二、方案一:localStorage(最常用)
基本實現(xiàn)
function useLocalStorage(key, initialValue) {
// 從localStorage讀取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// 更新狀態(tài)并保存到localStorage
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用示例
function PersistentForm() {
const [userData, setUserData] = useLocalStorage('user_form_data', {
username: '',
email: '',
theme: 'light'
});
return (
<div>
<input
value={userData.username}
onChange={(e) => setUserData({
...userData,
username: e.target.value
})}
/>
{/* 保存后刷新頁面,數(shù)據(jù)還在! */}
</div>
);
}
進階:自動過期和加密
class SecureStorage {
constructor(namespace) {
this.namespace = namespace;
}
set(key, value, ttl = null) {
const item = {
value,
timestamp: Date.now(),
ttl
};
// 簡單加密(生產(chǎn)環(huán)境應使用更安全的方案)
const encrypted = btoa(JSON.stringify(item));
localStorage.setItem(`${this.namespace}:${key}`, encrypted);
}
get(key) {
const encrypted = localStorage.getItem(`${this.namespace}:${key}`);
if (!encrypted) return null;
try {
const item = JSON.parse(atob(encrypted));
// 檢查是否過期
if (item.ttl && Date.now() - item.timestamp > item.ttl) {
this.remove(key);
return null;
}
return item.value;
} catch {
return null;
}
}
}
// 使用
const storage = new SecureStorage('myApp');
storage.set('token', 'abc123', 1000 * 60 * 60); // 1小時過期
三、方案二:sessionStorage(標簽頁級別)
function useSessionStorage(key, initialValue) {
const [state, setState] = useState(() => {
// 只在客戶端執(zhí)行
if (typeof window === 'undefined') return initialValue;
const stored = sessionStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
sessionStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
}
// 特性:不同標簽頁數(shù)據(jù)隔離,關閉標簽頁自動清除
四、方案三:IndexedDB(大量數(shù)據(jù))
當需要存儲大量結(jié)構(gòu)化數(shù)據(jù)時,IndexedDB是更好的選擇:
class IndexedDBService {
constructor(dbName, version) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
async open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('state')) {
db.createObjectStore('state', { keyPath: 'key' });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve();
};
request.onerror = (event) => reject(event.target.error);
});
}
async set(key, value) {
const transaction = this.db.transaction(['state'], 'readwrite');
const store = transaction.objectStore('state');
store.put({ key, value });
}
async get(key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['state'], 'readonly');
const store = transaction.objectStore('state');
const request = store.get(key);
request.onsuccess = () => resolve(request.result?.value);
request.onerror = () => reject(request.error);
});
}
}
// React Hook封裝
function useIndexedDB(key, initialValue) {
const [value, setValue] = useState(initialValue);
const [db, setDb] = useState(null);
useEffect(() => {
const initDB = async () => {
const dbService = new IndexedDBService('AppDB', 1);
await dbService.open();
setDb(dbService);
const stored = await dbService.get(key);
if (stored) setValue(stored);
};
initDB();
}, [key]);
const updateValue = async (newValue) => {
setValue(newValue);
if (db) await db.set(key, newValue);
};
return [value, updateValue];
}
五、方案四:URL參數(shù)(分享友好型)
適合篩選條件等需要分享的場景:
import { useHistory, useLocation } from 'react-router-dom';
function useURLState(key, initialValue) {
const location = useLocation();
const history = useHistory();
const queryParams = new URLSearchParams(location.search);
const [value, setValue] = useState(() => {
const param = queryParams.get(key);
return param ? JSON.parse(decodeURIComponent(param)) : initialValue;
});
const setURLValue = (newValue) => {
setValue(newValue);
// 更新URL參數(shù)
const newParams = new URLSearchParams(location.search);
newParams.set(key, encodeURIComponent(JSON.stringify(newValue)));
history.replace({
pathname: location.pathname,
search: newParams.toString()
});
};
return [value, setURLValue];
}
// 使用:URL會顯示 ?filters=%7B%22status%22%3A%22active%22%7D
function FilterComponent() {
const [filters, setFilters] = useURLState('filters', { status: 'all' });
// 分享當前URL,別人打開能看到同樣的篩選結(jié)果
}
六、方案五:Redux + 持久化中間件
// store/persistConfig.js
const persistConfig = {
key: 'root',
storage, // 可以是localStorage、sessionStorage等
whitelist: ['auth', 'userPreferences'], // 只持久化這些reducer
blacklist: ['temporaryData'], // 不持久化這些
transforms: [
// 數(shù)據(jù)轉(zhuǎn)換(如加密)
encryptTransform({
secretKey: process.env.REACT_APP_ENCRYPT_KEY
})
]
};
// store/index.js
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer);
export const persistor = persistStore(store);
// App.js
import { PersistGate } from 'redux-persist/integration/react';
function App() {
return (
<Provider store={store}>
<PersistGate loading={<LoadingSpinner />} persistor={persistor}>
<MainApp />
</PersistGate>
</Provider>
);
}
七、數(shù)據(jù)持久化流程圖
小量簡單數(shù)據(jù) 標簽頁隔離數(shù)據(jù) 大量結(jié)構(gòu)化數(shù)據(jù) 可分享狀態(tài) 全局狀態(tài)管理 頁面刷新數(shù)據(jù)恢復決策localStoragesessionStorageIndexedDBURL參數(shù)Redux持久化快速讀取 5-10MB限制會話級存儲 標簽頁獨立大容量存儲 異步操作可分享狀態(tài) URL可見集成Redux 狀態(tài)同步數(shù)據(jù)恢復完成
八、性能與安全最佳實踐
1. 防抖保存避免頻繁寫入
function useDebouncedPersist(key, value, delay = 1000) {
useEffect(() => {
const timer = setTimeout(() => {
localStorage.setItem(key, JSON.stringify(value));
}, delay);
return () => clearTimeout(timer);
}, [key, value, delay]);
}
2. 數(shù)據(jù)壓縮(大數(shù)據(jù)量時)
import { compress, decompress } from 'lz-string';
const compressData = (data) => {
return compress(JSON.stringify(data));
};
const decompressData = (compressed) => {
return JSON.parse(decompress(compressed));
};
3. 安全存儲敏感信息
// 永遠不要這樣做!
localStorage.setItem('token', rawToken);
// 應該:使用HttpOnly Cookie或臨時內(nèi)存存儲
九、方案對比選擇指南
| 方案 | 容量 | 生命周期 | 使用場景 | 優(yōu)缺點 |
|---|---|---|---|---|
| localStorage | 5-10MB | 永久 | 用戶偏好設置 | 簡單易用,但同步阻塞 |
| sessionStorage | 5-10MB | 標簽頁 | 臨時表單數(shù)據(jù) | 標簽頁隔離,關閉即失 |
| IndexedDB | 大量 | 永久 | 離線應用數(shù)據(jù) | 異步大容量,API復雜 |
| URL參數(shù) | 受限 | 頁面級別 | 篩選條件分享 | 可分享,但有長度限制 |
| Redux持久化 | 靈活 | 可配置 | 全局狀態(tài)管理 | 集成度高,需額外配置 |
十、實戰(zhàn):完整表單持久化示例
function PersistentForm() {
// 綜合使用多種方案
const [basicInfo, setBasicInfo] = useLocalStorage('form_basic', {});
const [tempData, setTempData] = useSessionStorage('form_temp', {});
const [filters, setFilters] = useURLState('form_filters', {});
// 自動保存草稿
useDebouncedPersist('form_draft', {
basicInfo,
tempData
}, 2000);
// 離開頁面提示
useBeforeUnload(() => {
if (hasUnsavedChanges) {
return '您有未保存的更改,確定要離開嗎?';
}
});
return (
<div>
{/* 表單內(nèi)容 */}
<button onClick={clearAllStorage}>
清空所有持久化數(shù)據(jù)
</button>
</div>
);
}
總結(jié)
React數(shù)據(jù)持久化沒有銀彈,關鍵在于根據(jù)場景選擇合適的方案:
- 1. 用戶設置 → localStorage
- 2. 臨時表單 → sessionStorage
- 3. 離線數(shù)據(jù) → IndexedDB
- 4. 分享狀態(tài) → URL參數(shù)
- 5. 全局狀態(tài) → Redux持久化
記住一個原則:敏感信息永遠不要在前端持久化!token、密碼等應該通過HttpOnly Cookie或后端session管理。
以上就是React頁面刷新數(shù)據(jù)不丟失的五種方案的詳細內(nèi)容,更多關于React頁面刷新數(shù)據(jù)不丟失的資料請關注腳本之家其它相關文章!
相關文章
使用react在修改state中的數(shù)組和對象數(shù)據(jù)的時候(setState)
這篇文章主要介紹了使用react在修改state中的數(shù)組和對象數(shù)據(jù)的時候(setState),具有很好的參考價值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
react?hooks?d3實現(xiàn)企查查股權(quán)穿透圖結(jié)構(gòu)圖效果詳解
這篇文章主要為大家介紹了react?hooks?d3實現(xiàn)企查查股權(quán)穿透圖結(jié)構(gòu)圖效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
簡易的redux?createStore手寫實現(xiàn)示例
這篇文章主要介紹了簡易的redux?createStore手寫實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
在React項目中使用iframe嵌入一個網(wǎng)站的步驟
本文介紹了如何在React項目中通過iframe嵌入百度網(wǎng)站的步驟,首先創(chuàng)建一個Baidu.js組件,并在該組件中設置iframe來加載百度,然后在App.js中引入并使用Baidu組件,還討論了因安全策略可能無法加載某些網(wǎng)站的問題,需要的朋友可以參考下2024-09-09

