Last active
February 12, 2019 06:51
-
-
Save cgfeel/22387f5cc5788931c23822d78a1a8e71 to your computer and use it in GitHub Desktop.
React Native 本地存储优化,基于`react-native-storage`,提供:① 缓存数据,定期删除;② 本地没有数据的时候请求远程数据;③ 后台静默更新数据
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 数据存储工具 | |
*/ | |
import Storage from 'react-native-storage'; | |
import {AsyncStorage} from 'react-native'; | |
const storage = new Storage({ | |
// 最大容量,默认值1000条数据循环存储 | |
size: 1000, | |
// 存储引擎:对于RN使用AsyncStorage,对于web使用window.localStorage | |
// 如果不指定则数据只会保存在内存中,重启后即丢失 | |
storageBackend: AsyncStorage, | |
// 数据过期时间,默认一整天(1000 * 3600 * 24 毫秒),设为null则永不过期 | |
defaultExpires: 1000 * 3600 * 24, | |
// 读写时在内存中缓存数据。默认启用 | |
enableCache: true, | |
// 你可以在构造函数这里就写好sync的方法 | |
// 或是在任何时候,直接对storage.sync进行赋值修改 | |
// 或是写到另一个文件里,这里require引入 | |
// 如果storage中没有相应数据,或数据已过期, | |
// 则会调用相应的sync方法,无缝返回最新数据 | |
}); | |
// 如果请求的`key`不存在,且`storage.sync`也不存在,那么返回一个错误 | |
/** | |
* 加载缓存,请求参数 | |
* init:请求参数 | |
* point|id|expires: 作为存储数据的值得,其中`point`作为存储的`key` | |
* body: 作为向web请求的值,所有非`body`的键名都作为存储数据时候的值 | |
* reback: 钩子参数,如果是`true`,将删除本地数据重新获取,如果是对象参数如下 | |
* remove: 如果是`true`,将删除本地数据重新获取,禁止后台更新 | |
* flag: 数据处理钩子,在线上获取到数据后,本地进行处理 | |
* uptime: 后台定时更新,目的在数据没有过期的情况下定时检查线上最新数据 | |
*/ | |
storage.loadSync = (init, remove) => { | |
let data = {key: init.point}; | |
if (init.id) { | |
data.id = init.id; | |
} | |
const reback = remove||{}; | |
const flush = reback.remove||reback === true; | |
flush && storage.remove(data); | |
let option = {body: init.body||{}}; | |
init.method = 'POST'; | |
for (let i in init) { | |
if (i === 'body') { | |
continue; | |
} else if (['point', 'id', 'expires'].indexOf(i) < 0) { | |
option[i] = init[i]; | |
} else { | |
option.body[i] = init[i]; | |
} | |
} | |
const method = { | |
key: init.point, | |
id: init.id||undefined, | |
autoSync: true, | |
syncParams: { | |
extraFetchOptions: option, | |
someFlag: reback.flag, | |
expires: init.expires | |
} | |
}; | |
return storage.load(method).then(info => { | |
// 后台定时更新,要求 | |
// 没有被强制更新;有设置更新时间;有存在更新方法;当前时间已经超过了更新时间 | |
const uptime = info.uptime||0; | |
if (!flush && reback.uptime && storage.sync[init.point] && | |
(new Date().getTime() - uptime) > reback.uptime) { | |
storage.sync[init.point](method); | |
} | |
return info; | |
}).catch(err => { | |
err.error = 1; | |
switch(err.name) { | |
case 'NotFoundError': | |
data.log = '请求了一个不存在的`key`'; | |
break; | |
case 'ExpiredError': | |
err.log = '数据可能过期了'; | |
break; | |
default: | |
err.log = '请求失败了'; | |
} | |
return err; | |
}); | |
}; | |
/** | |
* 获取数据集合,参数按照`storage.loadSync`来,有一点不同 | |
* 如果需要存储远程数据,`reback.flag` 返回的值一定是{id: object}对象 | |
* 其中`id`是存储的ID,`object`是存储的数据 | |
*/ | |
storage.allDataSync = async (init, remove) => { | |
let data = {key: init.point}; | |
if (init.id) { | |
data.id = init.id; | |
} | |
const reback = remove||{}; | |
const flush = reback.remove||reback === true; | |
let ids = []; | |
let allsync = {error: 1, cost: 0, uptime: 0}; | |
if (flush) { | |
storage.remove(data); | |
} else { | |
allsync = await storage.loadSync({ | |
point: init.point + 'AllSync' | |
}); | |
ids = await storage.getIdsForKey(init.point); | |
} | |
let results = []; | |
const cache = !flush && allsync.error !== 1 && ids.length && allsync.cost === ids.length; | |
if (cache) | |
{ | |
results = await storage.getAllDataForKey(init.point); | |
} | |
// 如果有缓存的情况下,没有后台更新时间或者是还未到更新时间,直接返还缓存数据 | |
const uptime = reback.uptime && (new Date().getTime() - allsync.uptime) > reback.uptime; | |
if (cache && !uptime) { | |
return results; | |
} | |
let option = {body: init.body||{}}; | |
init.method = 'POST'; | |
for (let i in init) { | |
if (i === 'body') { | |
continue; | |
} else if (['point', 'id', 'expires'].indexOf(i) < 0) { | |
option[i] = init[i]; | |
} else { | |
option.body[i] = init[i]; | |
} | |
} | |
// 如果是启用缓存数据,则后台更新返回缓存数据,否则加载远程数据 | |
if (cache) { | |
fetchAllData({ | |
extraFetchOptions: option, | |
someFlag: reback.flag, | |
expires: init.expires | |
}); | |
} else { | |
results = await fetchAllData({ | |
extraFetchOptions: option, | |
someFlag: reback.flag, | |
expires: init.expires | |
}); | |
} | |
return results; | |
}; | |
storage.userToken = call => { | |
storage.loadSync({ | |
point: 'userToken' | |
}).then(call); | |
}; | |
storage.URL = 'https://example.com/api/'; | |
storage.SECONDS = 1000; | |
storage.MINUTE_IN_SECONDS = storage.SECONDS * 60; | |
storage.HOUR_IN_SECONDS = storage.MINUTE_IN_SECONDS * 60; | |
storage.DAY_IN_SECONDS = storage.HOUR_IN_SECONDS * 24; | |
storage.WEEK_IN_SECONDS = storage.DAY_IN_SECONDS * 7; | |
storage.MONTH_IN_SECONDS = storage.DAY_IN_SECONDS * 30; | |
storage.YEAR_IN_SECONDS = storage.DAY_IN_SECONDS * 365; | |
const fetchData = async params => { | |
const { | |
id, | |
syncParams: { | |
extraFetchOptions, someFlag, expires | |
} | |
} = params; | |
const point = extraFetchOptions.body.point; | |
extraFetchOptions.body = JSON.stringify(extraFetchOptions.body); | |
const options = Object.assign({ | |
method: "POST", | |
headers: { | |
"Accept": "application/json", | |
"Content-Type": "application/json" | |
} | |
}, extraFetchOptions); | |
const response = await fetch(storage.URL, options); | |
const responseText = await response.text(); | |
// 处理需要的数据 | |
let json = JSON.parse(responseText); | |
let method = { | |
key: point, | |
expires: expires === null ? null : (expires||86400) * 1000 | |
}; | |
if (json) { | |
json.uptime = new Date().getTime(); | |
if (someFlag) { | |
json = someFlag(json); | |
} | |
method.data = json; | |
if (id) { | |
method.id = id; | |
} | |
storage.save(method); | |
return json; | |
} else { | |
// 出错抛出异常 | |
throw new Error(`error syncing buyer`); | |
} | |
}; | |
const fetchAllData = async params => { | |
const { | |
extraFetchOptions, someFlag, expires | |
} = params; | |
const point = extraFetchOptions.body.point; | |
extraFetchOptions.body = JSON.stringify(extraFetchOptions.body); | |
const options = Object.assign({ | |
method: "POST", | |
headers: { | |
"Accept": "application/json", | |
"Content-Type": "application/json" | |
} | |
}, extraFetchOptions); | |
const response = await fetch(storage.URL, options); | |
const responseText = await response.text(); | |
// 处理需要的数据 | |
let json = JSON.parse(responseText); | |
let method = { | |
key: point, | |
expires: expires === null ? null : (expires||86400) * 1000 | |
}; | |
if (json) { | |
let list = []; | |
if (someFlag) { | |
const saveData = someFlag(json); | |
for (let i in saveData) { | |
list.push(saveData[i]); | |
method.data = saveData[i]; | |
method.id = i; | |
storage.save(method); | |
} | |
// 当前值记录一个数据,以便更新使用 | |
const total = Object.keys(saveData).length; | |
if (total) { | |
storage.save({ | |
key: point + 'AllSync', | |
expires: expires === null ? null : (expires||86400) * 1000, | |
data: { | |
uptime: new Date().getTime(), | |
cost: total, | |
error: 0 | |
} | |
}); | |
} | |
} else { | |
return json; | |
} | |
return list; | |
} else { | |
// 出错抛出异常 | |
throw new Error(`error syncing buyer`); | |
} | |
}; | |
storage.sync = { | |
buyerList: fetchData, | |
imageCode: fetchData | |
} | |
global.Storage = storage; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment