介绍
我们在开发中发起网络请求之后拿到数据经常存储到本地后续使用本地缓存的数据,但是这样会有些缺陷有可能数据更新了但是一直使用本地数据无法拿到最新的数据,并且每次发送请求之后需要手动保存数据到本地太繁琐。
基于上面得问题我们进行总结需要解决的问题:
- 使用本地缓存需要一个过期时间。
- 将本地存储数据和发送网络请求融合,当读取某个本地数据时若没有则自动发送请求并保存。
- 必须是跨框架的通用代码,不仅能在
Vue和React中使用还可以在Uniapp、微信小程序等所有可以运行js代码的框架中使用。
问题已经清楚了那要如何去做呢? 通用 1.我们存储时可以额外存储一个时间戳,并设置一个过期时间,当读取本地数据时先判断是否过期在执行操作。
2.对于本地存储数据和发送网络请求融合我们可以设置一个映射关系(本地存储的Key值 => 请求函数)。
3.要实现通用跨框架代码,只能将更多的操作交给开发者,让开发者告诉我采用哪种本地存储API。
扩展: 本地存储数据和发送网络请求设置的映射关系无法传递参数这要怎么办呢?
我们可以在设置请求参数和本地存储的key的映射关系解决该问题。
代码实现
// 反序列化函数
function parse(data) {
try {
return JSON.parse(data);
} catch (err) {
return data;
}
}
// 定义格式:不管数据有没有 返回的格式总是 {code: 消息码,msg: '',data: 数据}。
function formatResponse(code, msg, data) {
return {code, msg, data}
}
// 默认的本地存储策略。
const defaultStrategy = {
get(key) {
return new Promise((resolve, reject) => {
const localData = parse(window.localStorage.getItem(key));
resolve(formatResponse(0, '获取数据成功', localData));
});
},
set(key, value) {
return new Promise((resolve, reject) => {
window.localStorage.setItem(key, JSON.stringify(value));
resolve(formatResponse(0, '设置数据成功', null));
});
},
remove(key) {
return new Promise((resolve, reject) => {
window.localStorage.removeItem(key);
resolve(formatResponse(0, '删除数据成功', null));
});
}
}
export function useCacheStore(config = {maxAge: 1000 * 30}) {
// 1.定义存储策略,由于是通用的代码所有本地存储API并不确定是什么。
const storageStrategy = config.storageStrategy || defaultStrategy;
// 设置 本地存储的key 和 请求函数的参数 的映射关系。
const storageKey2requestFN = new Map();
// 2.定义缓存策略,只允许获取本地数据,不允许修改本地数据,因为本地数据都是网络请求来的数据,开发者修改也没有意思。
return {
async get(key) {
const localData = await storageStrategy.get(key); // 获取到本地数据。
const request = config.requestMap ? config.requestMap[key] : undefined;
const rest = storageKey2requestFN.get(key) ? storageKey2requestFN.get(key) : [undefined];
if (!request) return Promise.reject('没有找到对应的请求方法');
if (!localData.data || localData.data === '' || localData.data == null) { // 之前没有存储过需要发送网络请求获取数据
const response = await request(...rest);
if (!response) return Promise.reject('函数的返回值为 undefined');
storageStrategy.set(key, [response, Date.now()]);
return formatResponse(0, '新的数据,需要本地存储', response);
} else { // 之前存储过,需要判读是否过期
const [data, timestamp] = localData.data;
const isExpired = (Date.now() - timestamp) > config.maxAge;
if (isExpired) { // 过期了 需要重新存储
const response = await request(...rest);
if (!response) return Promise.reject('函数的返回值为 undefined');
storageStrategy.set(key, [response, Date.now()]);
return formatResponse(0, '数据过期需要重新请求', response);
} else { // 没有过期,重用本地数据。
return formatResponse(0, '复用本地数据', data);
}
}
},
async remove(key) {
storageStrategy.remove(key);
return formatResponse(0, '数据删除成功', null);
},
addRest(key, rest) {
if (config.requestMap[key]) {
storageKey2requestFN.set(key, Array.isArray(rest) ? rest : [rest]);
} else {
throw new Error(`这个键 ${key} 并不存在`);
}
}
}
}
测试代码效果:
import {useCacheStore} from './useCacheStore.js';
const cacheStore = useCacheStore({
maxAge: 1000 * 60 * 1, // 缓存1分钟
requestMap: { // 设置本地存储的key和请求函数的映射关系
'loginInfo': (loginId, loginPwd) => Promise.resolve({loginId, loginPwd}),
'test': (a) => {
return Promise.resolve(a)
}
}
});
// 在发送数据之前如果需要传参需要调用 addRest 方法该对应的 请求函数设置参数。
cacheStore.addRest('loginInfo', ['123', '456']);
cacheStore.addRest('test', '测试');
// 当读取本都数据时,若没有或已过期则自动发送请求。
cacheStore.get('loginInfo').then(res => console.log(res));
cacheStore.get('test').then(res => console.log(res));