React頁(yè)面刷新數(shù)據(jù)不丟失的五種方案
一、問(wèn)題場(chǎng)景:為什么數(shù)據(jù)會(huì)丟失?
我們先通過(guò)一個(gè)簡(jiǎn)單例子感受一下問(wèn)題:
function UserForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
preferences: {
theme: 'light',
notifications: true
}
});
// 用戶輸入數(shù)據(jù)...
// 頁(yè)面刷新 → 所有數(shù)據(jù)歸零 ??
return (
<form>
<input
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
{/* 其他表單項(xiàng) */}
</form>
);
}
數(shù)據(jù)丟失的根本原因是:React狀態(tài)(state)存儲(chǔ)在內(nèi)存中,頁(yè)面刷新時(shí)內(nèi)存被清空。
二、方案一:localStorage(最常用)
基本實(shí)現(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īng)顟B(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
})}
/>
{/* 保存后刷新頁(yè)面,數(shù)據(jù)還在! */}
</div>
);
}
進(jìn)階:自動(dòng)過(guò)期和加密
class SecureStorage {
constructor(namespace) {
this.namespace = namespace;
}
set(key, value, ttl = null) {
const item = {
value,
timestamp: Date.now(),
ttl
};
// 簡(jiǎn)單加密(生產(chǎn)環(huán)境應(yīng)使用更安全的方案)
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));
// 檢查是否過(guò)期
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小時(shí)過(guò)期
三、方案二:sessionStorage(標(biāo)簽頁(yè)級(jí)別)
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];
}
// 特性:不同標(biāo)簽頁(yè)數(shù)據(jù)隔離,關(guān)閉標(biāo)簽頁(yè)自動(dòng)清除
四、方案三:IndexedDB(大量數(shù)據(jù))
當(dāng)需要存儲(chǔ)大量結(jié)構(gòu)化數(shù)據(jù)時(shí),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ù)(分享友好型)
適合篩選條件等需要分享的場(chǎng)景:
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會(huì)顯示 ?filters=%7B%22status%22%3A%22active%22%7D
function FilterComponent() {
const [filters, setFilters] = useURLState('filters', { status: 'all' });
// 分享當(dāng)前URL,別人打開(kāi)能看到同樣的篩選結(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ù)持久化流程圖
小量簡(jiǎn)單數(shù)據(jù) 標(biāo)簽頁(yè)隔離數(shù)據(jù) 大量結(jié)構(gòu)化數(shù)據(jù) 可分享狀態(tài) 全局狀態(tài)管理 頁(yè)面刷新數(shù)據(jù)恢復(fù)決策localStoragesessionStorageIndexedDBURL參數(shù)Redux持久化快速讀取 5-10MB限制會(huì)話級(jí)存儲(chǔ) 標(biāo)簽頁(yè)獨(dú)立大容量存儲(chǔ) 異步操作可分享狀態(tài) URL可見(jiàn)集成Redux 狀態(tài)同步數(shù)據(jù)恢復(fù)完成
八、性能與安全最佳實(shí)踐
1. 防抖保存避免頻繁寫(xiě)入
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ù)量時(shí))
import { compress, decompress } from 'lz-string';
const compressData = (data) => {
return compress(JSON.stringify(data));
};
const decompressData = (compressed) => {
return JSON.parse(decompress(compressed));
};
3. 安全存儲(chǔ)敏感信息
// 永遠(yuǎn)不要這樣做!
localStorage.setItem('token', rawToken);
// 應(yīng)該:使用HttpOnly Cookie或臨時(shí)內(nèi)存存儲(chǔ)
九、方案對(duì)比選擇指南
| 方案 | 容量 | 生命周期 | 使用場(chǎng)景 | 優(yōu)缺點(diǎn) |
|---|---|---|---|---|
| localStorage | 5-10MB | 永久 | 用戶偏好設(shè)置 | 簡(jiǎn)單易用,但同步阻塞 |
| sessionStorage | 5-10MB | 標(biāo)簽頁(yè) | 臨時(shí)表單數(shù)據(jù) | 標(biāo)簽頁(yè)隔離,關(guān)閉即失 |
| IndexedDB | 大量 | 永久 | 離線應(yīng)用數(shù)據(jù) | 異步大容量,API復(fù)雜 |
| URL參數(shù) | 受限 | 頁(yè)面級(jí)別 | 篩選條件分享 | 可分享,但有長(zhǎng)度限制 |
| Redux持久化 | 靈活 | 可配置 | 全局狀態(tài)管理 | 集成度高,需額外配置 |
十、實(shí)戰(zhàn):完整表單持久化示例
function PersistentForm() {
// 綜合使用多種方案
const [basicInfo, setBasicInfo] = useLocalStorage('form_basic', {});
const [tempData, setTempData] = useSessionStorage('form_temp', {});
const [filters, setFilters] = useURLState('form_filters', {});
// 自動(dòng)保存草稿
useDebouncedPersist('form_draft', {
basicInfo,
tempData
}, 2000);
// 離開(kāi)頁(yè)面提示
useBeforeUnload(() => {
if (hasUnsavedChanges) {
return '您有未保存的更改,確定要離開(kāi)嗎?';
}
});
return (
<div>
{/* 表單內(nèi)容 */}
<button onClick={clearAllStorage}>
清空所有持久化數(shù)據(jù)
</button>
</div>
);
}
總結(jié)
React數(shù)據(jù)持久化沒(méi)有銀彈,關(guān)鍵在于根據(jù)場(chǎng)景選擇合適的方案:
- 1. 用戶設(shè)置 → localStorage
- 2. 臨時(shí)表單 → sessionStorage
- 3. 離線數(shù)據(jù) → IndexedDB
- 4. 分享狀態(tài) → URL參數(shù)
- 5. 全局狀態(tài) → Redux持久化
記住一個(gè)原則:敏感信息永遠(yuǎn)不要在前端持久化!token、密碼等應(yīng)該通過(guò)HttpOnly Cookie或后端session管理。
以上就是React頁(yè)面刷新數(shù)據(jù)不丟失的五種方案的詳細(xì)內(nèi)容,更多關(guān)于React頁(yè)面刷新數(shù)據(jù)不丟失的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React組件對(duì)子組件children進(jìn)行加強(qiáng)的方法
這篇文章主要給大家介紹了關(guān)于React組件中對(duì)子組件children進(jìn)行加強(qiáng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用React具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
使用react在修改state中的數(shù)組和對(duì)象數(shù)據(jù)的時(shí)候(setState)
這篇文章主要介紹了使用react在修改state中的數(shù)組和對(duì)象數(shù)據(jù)的時(shí)候(setState),具有很好的參考價(jià)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
React如何利用相對(duì)于根目錄進(jìn)行引用組件詳解
這篇文章主要給大家介紹了關(guān)于React如何使用相對(duì)于根目錄進(jìn)行引用組件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
react?hooks?d3實(shí)現(xiàn)企查查股權(quán)穿透圖結(jié)構(gòu)圖效果詳解
這篇文章主要為大家介紹了react?hooks?d3實(shí)現(xiàn)企查查股權(quán)穿透圖結(jié)構(gòu)圖效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
簡(jiǎn)易的redux?createStore手寫(xiě)實(shí)現(xiàn)示例
這篇文章主要介紹了簡(jiǎn)易的redux?createStore手寫(xiě)實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
在React項(xiàng)目中使用iframe嵌入一個(gè)網(wǎng)站的步驟
本文介紹了如何在React項(xiàng)目中通過(guò)iframe嵌入百度網(wǎng)站的步驟,首先創(chuàng)建一個(gè)Baidu.js組件,并在該組件中設(shè)置iframe來(lái)加載百度,然后在App.js中引入并使用Baidu組件,還討論了因安全策略可能無(wú)法加載某些網(wǎng)站的問(wèn)題,需要的朋友可以參考下2024-09-09
基于react?vant實(shí)現(xiàn)彈窗搜索功能
這篇文章主要為大家詳細(xì)介紹了如何基于react?vant實(shí)現(xiàn)彈窗搜索功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-01-01

