@alova/scene-react
Version:
scenario react hooks with alova.js
1,575 lines (1,507 loc) • 132 kB
JavaScript
/**
* @alova/scene-react 1.6.2 (https://alova.js.org)
* Copyright 2024 JOU-amjs. All Rights Reserved
* Licensed under MIT (https://alova.js.org/blob/main/LICENSE)
*/
import { Method, getMethodKey, useWatcher, useFetcher, setCache, invalidateCache, queryCache, updateState, matchSnapshotMethod, useRequest } from 'alova';
import { useState, useMemo, useRef, useEffect, useCallback as useCallback$1 } from 'react';
// 以下为减少编译代码量而添加的统一处理函数或变量
const PromiseCls = Promise, undefinedValue = undefined, nullValue = null, trueValue = true, falseValue = false, ObjectCls = Object, NumberCls = Number, StringCls = String, BooleanCls = Boolean, RegExpCls = RegExp, symbolToStringTag = Symbol.toStringTag, STR_VALUE_OF = 'valueOf', DEFAUT_QUEUE_NAME = 'default', BEHAVIOR_SILENT = 'silent', BEHAVIOR_QUEUE = 'queue', BEHAVIOR_STATIC = 'static',
// 是否为服务端渲染,为了兼容浏览器以及非web客户端环境(如小程序),需要再判断一下process
isSSR = typeof window === 'undefined' && typeof process !== 'undefined';
const promiseResolve = (value) => PromiseCls.resolve(value), promiseReject = (value) => PromiseCls.reject(value), promiseThen = (promise, onFulfilled, onrejected) => promise.then(onFulfilled, onrejected), promiseCatch = (promise, onrejected) => promise.catch(onrejected), promiseFinally = (promise, onfinally) => promise.finally(onfinally), forEach = (ary, fn) => ary.forEach(fn), pushItem = (ary, ...item) => ary.push(...item), filterItem = (ary, fn) => ary.filter(fn), map = (ary, fn) => ary.map(fn), includes = (ary, target) => ary.includes(target), len = (data) => data.length, isArray$1 = (target) => Array.isArray(target), shift = (ary) => ary.shift(), splice = (ary, start, deleteCount = 0, ...items) => ary.splice(start, deleteCount, ...items), getContextOptions = (alovaInstance) => alovaInstance.options, getConfig = (methodInstance) => methodInstance.config, getOptions = (methodInstance) => getContextOptions(getContext(methodInstance)), getContext = (methodInstance) => methodInstance.context, getMethodInternalKey = (methodInstance) => methodInstance.__key__, objectKeys = (obj) => ObjectCls.keys(obj), objectValues = (obj) => ObjectCls.values(obj), setTimeoutFn = (fn, delay = 0) => setTimeout(fn, delay), clearTimeoutFn = (timeoutId) => clearTimeout(timeoutId), regexpTest = (reg, str) => reg.test(str);
/**
* 创建同步多次调用只在异步执行一次的执行器
*/
const createSyncOnceRunner = (delay = 0) => {
let timer = undefinedValue;
/**
* 执行多次调用此函数将异步执行一次
*/
return (fn) => {
if (timer) {
clearTimeoutFn(timer);
}
timer = setTimeoutFn(fn, delay);
};
};
/**
* 创建uuid简易版
* @returns uuid
*/
const uuid = () => {
const timestamp = new Date().getTime();
return Math.floor(Math.random() * timestamp).toString(36);
};
/**
* 兼容函数
*/
const noop$1 = () => { };
/**
* 兼容函数,返回自身
* @param value 任意值
* @returns 自身
*/
const __self = (value) => value;
/**
* 兼容函数,抛出参数
* @param error 错误
*/
const __throw = (error) => {
throw error;
};
// 判断是否为某个类的实例
const instanceOf = (arg, cls) => arg instanceof cls;
/**
* 构建格式化的错误消息
* @param prefix 错误前缀
* @param msg 错误消息
* @returns 格式化的错误消息
*/
const buildErrorMsg = (prefix, msg) => `[alova/${prefix}]${msg}`;
/**
* 自定义断言函数,表达式为false时抛出错误
* @param expression 判断表达式,true或false
* @param msg 断言消息
*/
const createAssert = (prefix) => {
return (expression, msg) => {
if (!expression) {
throw newInstance(Error, buildErrorMsg(prefix, msg));
}
};
};
const valueObject = (value, writable = falseValue) => ({
value,
writable
});
/**
* 定义obj属性
* @param o 对象
* @param attrs 值对象
*/
const defineProperty = (o, key, value, isDescriptor = falseValue) => {
ObjectCls.defineProperty(o, key, isDescriptor ? value : valueObject(value, falseValue));
};
/**
* 批量执行时间回调函数,并将args作为参数传入
* @param handlers 事件回调数组
* @param args 函数参数
*/
const runArgsHandler = (handlers, ...args) => {
let ret = undefinedValue;
forEach(handlers, handler => {
const retInner = handler(...args);
ret = retInner !== undefinedValue ? retInner : ret;
});
return ret;
};
/**
* typof冗余函数
* @param arg 任意参数
* @returns 参数类型
*/
const typeOf = (arg) => typeof arg;
/**
* 判断参数是否为函数
* @param fn 任意参数
* @returns 该参数是否为函数
*/
const isFn = (arg) => typeOf(arg) === 'function';
/**
* 判断参数是否为字符串
* @param arg 任意参数
* @returns 该参数是否为字符串
*/
const isString = (arg) => typeOf(arg) === 'string';
/**
* 判断参数是否为对象
* @param arg 任意参数
* @returns 该参数是否为对象
*/
const isObject = (arg) => arg !== nullValue && typeOf(arg) === 'object';
/**
* 判断是否为纯对象或自定义类的对象
* @param arg 任意参数
* @returns 该参数是否为纯对象或自定义类的对象
*/
const isPlainOrCustomObject = (arg) => ObjectCls.prototype.toString.call(arg) === '[object Object]';
const walkObject = (target, callback, preorder = trueValue, key, parent) => {
const callCallback = () => {
if (parent && key) {
target = callback(target, key, parent);
if (target !== parent[key]) {
parent[key] = target;
}
}
};
// 前序遍历
preorder && callCallback();
if (isObject(target)) {
for (const i in target) {
if (!instanceOf(target, StringCls)) {
walkObject(target[i], callback, preorder, i, target);
}
}
}
// 后序遍历
!preorder && callCallback();
return target;
};
/**
* 创建类实例
* @param cls 构造函数
* @param args 构造函数参数
* @returns 类实例
*/
const newInstance = (cls, ...args) => new cls(...args);
/**
* 统一配置
* @param 数据
* @returns 统一的配置
*/
const sloughConfig = (config, args = []) => isFn(config) ? config(...args) : config;
const getTime = (date) => (date ? date.getTime() : Date.now());
/**
* 判断参数是否为数字
* @param arg 任意参数
* @returns 该参数是否为数字
*/
const isNumber = (arg) => typeof arg === 'number' && !isNaN(arg);
/** 三种缓存模式 */
// 只在内存中缓存,默认是此选项
const MEMORY = 'memory',
// 缓存会持久化,但当内存中没有缓存时,持久化缓存只会作为响应数据的占位符,且还会发送请求更新缓存
STORAGE_PLACEHOLDER = 'placeholder',
// 缓存会持久化,且每次刷新会读取持久化缓存到内存中,这意味着内存一直会有缓存
STORAGE_RESTORE = 'restore';
/**
* 获取缓存的配置参数,固定返回{ e: number, m: number, s: boolean, t: string }格式的对象
* e为expire缩写,表示缓存失效时间点(时间戳),单位为毫秒
* m为mode缩写,存储模式
* s为storage缩写,是否存储到本地
* t为tag缩写,持久化存储标签
* @param localCache 本地缓存参数
* @returns 统一的缓存参数对象
*/
const getLocalCacheConfigParam = (methodInstance, localCache) => {
const _localCache = localCache !== undefinedValue ? localCache : methodInstance ? getConfig(methodInstance).localCache : undefinedValue;
const getCacheExpireTs = (_localCache) => isNumber(_localCache) ? getTime() + _localCache : getTime(_localCache);
let cacheMode = MEMORY;
let expire = 0;
let storage = falseValue;
let tag = undefinedValue;
if (!isFn(_localCache)) {
if (isNumber(_localCache) || instanceOf(_localCache, Date)) {
expire = getCacheExpireTs(_localCache);
}
else {
const { mode = MEMORY, expire: configExpire = 0, tag: configTag } = _localCache || {};
cacheMode = mode;
expire = getCacheExpireTs(configExpire);
storage = [STORAGE_PLACEHOLDER, STORAGE_RESTORE].includes(mode);
tag = configTag ? configTag.toString() : undefinedValue;
}
}
return {
e: expire,
m: cacheMode,
s: storage,
t: tag
};
};
/**
* 根据避让策略和重试次数计算重试延迟时间
* @param backoff 避让参数
* @param retryTimes 重试次数
* @returns 重试延迟时间
*/
const delayWithBackoff = (backoff, retryTimes) => {
let { delay, multiplier = 1, startQuiver, endQuiver } = backoff;
let retryDelayFinally = (delay || 0) * Math.pow(multiplier, retryTimes - 1);
// 如果startQuiver或endQuiver有值,则需要增加指定范围的随机抖动值
if (startQuiver || endQuiver) {
startQuiver = startQuiver || 0;
endQuiver = endQuiver || 1;
retryDelayFinally +=
retryDelayFinally * startQuiver + Math.random() * retryDelayFinally * (endQuiver - startQuiver);
retryDelayFinally = Math.floor(retryDelayFinally); // 取整数延迟
}
return retryDelayFinally;
};
/**
* 获取请求方法对象
* @param methodHandler 请求方法句柄
* @param args 方法调用参数
* @returns 请求方法对象
*/
const getHandlerMethod = (methodHandler, args = []) => {
const methodInstance = isFn(methodHandler) ? methodHandler(...args) : methodHandler;
createAssert('scene')(instanceOf(methodInstance, Method), 'hook handler must be a method instance or a function that returns method instance');
return methodInstance;
};
function useCallback(onCallbackChange = noop$1) {
let callbacks = [];
const setCallback = (fn) => {
if (!callbacks.includes(fn)) {
callbacks.push(fn);
onCallbackChange(callbacks);
}
// 返回取消注册函数
return () => {
callbacks = filterItem(callbacks, e => e !== fn);
onCallbackChange(callbacks);
};
};
const triggerCallback = (...args) => {
if (callbacks.length > 0) {
return forEach(callbacks, fn => fn(...args));
}
};
const removeAllCallback = () => {
callbacks = [];
onCallbackChange(callbacks);
};
return [setCallback, triggerCallback, removeAllCallback];
}
function usePromise() {
let _resolve;
let _reject;
const promise = new Promise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return { promise, resolve: _resolve, reject: _reject };
}
/**
* 创建状态
* 当isRef为true时,将会在_$中获得最新的值而不受闭包陷阱影响
* @param initialState 创建状态的数据
* @param {boolean|void} isRef 是否创建为引用值
* @returns
*/
const $ = (initialState, isRef) => {
const state = useState(initialState);
if (isRef) {
const ref = useFlag$();
ref.current = state[0];
state[2] = ref; // 将引用值保存到数组中
}
return state;
};
/**
* 创建计算属性
* 为了兼容$函数中创建的状态可以在exportState中导出,将格式与$返回值靠齐
* @param factory 计算属性计算函数
* @param deps 依赖值
* @param {boolean|void} isRef 是否创建为引用值
* @returns 类$返回值
*/
const $$ = (factory, deps, isRef) => {
const memo = useMemo(factory, deps);
const memoAry = [memo, noop$1];
if (isRef) {
const ref = useFlag$();
ref.current = memo;
pushItem(memoAry, ref);
}
return memoAry;
};
/**
* 脱水普通状态、计算属性或alova导出的状态,返回状态原始值
* 有引用值时返回引用值,否则返回原始值
* @param state 状态
* @returns 状态原始值,即状态对应的数据
*/
const _$ = state => {
if (isArray$1(state) && isFn(state[1])) {
return state[2] ? state[2].current : state[0];
}
return state;
};
/**
* 返回导出值
* 导出状态原始值,一般用于导出use hook的值,或在依赖项使用
* @param state 状态
* @returns 状态原始值
*/
const _exp$ = state => (isArray$1(state) && isFn(state[1]) ? state[0] : state);
/**
* 批量导出状态
* @param state 状态
* @returns 状态原始值
*/
const _expBatch$ = (...states) => map(states, s => _exp$(s));
/**
* 更新状态值
* @param state 更新的状态
* @param newData 新状态值
*/
const upd$ = (state, newData) => {
if (isFn(newData)) {
const oldState = state[2] ? state[2].current : state[0];
newData = newData(isArray$1(oldState) ? [...oldState] : isObject(oldState) ? { ...oldState } : oldState);
}
state[1](newData);
// 如果有引用类型值,也要更新它
if (state[2]) {
state[2].current = newData;
}
};
/**
* 监听状态触发回调
* @param {import('react').DependencyList} states 监听状态
* @param {Function} cb 回调函数
*/
const watch$ = (states, cb) => {
// 当有监听状态时,状态变化再触发
const needEmit = useRef(falseValue);
useEffect(() => {
needEmit.current ? cb() : (needEmit.current = true);
}, states);
};
/**
* 组件挂载执行
* @param {Function} cb 回调函数
*/
const onMounted$ = cb => useEffect(cb, []);
/**
* 组件卸载执行
* @param {Function} cb 回调函数
*/
const onUnmounted$ = cb => {
useEffect(() => cb, []);
};
/**
* 使用标识,一般作为标识
* 在react中每次渲染都会调用hook,如果使用基础数据每次将会获得初始值
* 为解决这个问题,在react中需使用useRef作为标识
* @param initialValue 初始值
*/
const useFlag$ = initialValue => useRef(initialValue);
/**
* 将alova的hook返回状态如loading、data等转换为不受闭包陷阱影响的值
* 由于在react下,use hook返回的loading、data等状态为普遍值,将会受闭包影响
* 因此使用此函数将普通值转换为跨闭包的值
* @param requestState 请求hook状态
*/
const useRequestRefState$ = requestState => {
const requestStateWrapper = [requestState, noop$1];
const ref = useFlag$();
ref.current = requestState;
pushItem(requestStateWrapper, ref);
return requestStateWrapper;
};
/**
* 它返回的回调函数始终为同一份引用,同时callback中访问的状态不受闭包陷阱影响
* @param callback
* @returns
*/
const useMemorizedCallback$ = callback => {
const ref = useFlag$();
ref.current = callback;
return useCallback$1((...args) => {
callback.apply(null, args);
}, []);
};
var createSnapshotMethodsManager = (handler) => {
let methodSnapshots = {};
return {
snapshots: () => methodSnapshots,
save(methodInstance, force = falseValue) {
const key = getMethodKey(methodInstance);
// 因为无法定位缓存中total数据的位置
// 因此这边冗余维护这个字段
if (!methodSnapshots[key] || force) {
methodSnapshots[key] = {
entity: methodInstance
};
}
},
get: (entityOrPage) => methodSnapshots[getMethodKey(instanceOf(entityOrPage, Method) ? entityOrPage : handler(entityOrPage))],
remove(key) {
if (key) {
delete methodSnapshots[key];
}
else {
methodSnapshots = {};
}
}
};
};
const paginationAssert = createAssert('usePagination');
const indexAssert = (index, rawData) =>
paginationAssert(isNumber(index) && index < len(rawData), 'index must be a number that less than list length');
function usePagination_unified (
handler,
{
preloadPreviousPage = trueValue,
preloadNextPage = trueValue,
total: totalGetter = res => res.total,
data: dataGetter = res => res.data,
append = falseValue,
initialPage = 1,
initialPageSize = 10,
watchingStates = [],
initialData,
immediate = trueValue,
middleware = noop$1,
force = noop$1,
...others
},
$,
$$,
upd$,
_$,
_exp$,
_expBatch$,
watch$,
useFlag$,
useRequestRefState$,
useMemorizedCallback$
) {
const handerRef = useFlag$();
handerRef.current = handler;
const isReset = useFlag$(falseValue), // 用于控制是否重置
// 重置期间请求的次数,为了防止重置时重复请求,使用此参数限制请求
requestCountInReseting = useFlag$(0),
page = $(initialPage, trueValue),
pageSize = $(initialPageSize, trueValue),
data = $(initialData ? dataGetter(initialData) || [] : [], trueValue),
// 保存当前hook所使用到的所有method实例快照
{
snapshots: methodSnapshots,
get: getSnapshotMethods,
save: saveSnapshot,
remove: removeSnapshot
} = useFlag$(createSnapshotMethodsManager(page => handerRef.current(page, _$(pageSize)))).current,
listDataGetter = rawData => dataGetter(rawData) || rawData,
getHandlerMethod = (refreshPage = _$(page)) => {
const pageSizeVal = _$(pageSize);
const handlerMethod = handler(refreshPage, pageSizeVal);
// 定义统一的额外名称,方便管理
saveSnapshot(handlerMethod);
return handlerMethod;
};
// 监听状态变化时,重置page为1
watch$(watchingStates, () => {
upd$(page, initialPage);
requestCountInReseting.current = 0;
isReset.current = trueValue;
});
// 兼容react,将需要代理的函数存放在此
// 这样可以在代理函数中调用到最新的操作函数,避免react闭包陷阱
const delegationActions = useFlag$({}),
createDelegationAction =
actionName =>
(...args) =>
delegationActions.current[actionName](...args),
states = useWatcher(getHandlerMethod, [...watchingStates, _exp$(page), _exp$(pageSize)], {
immediate,
initialData,
middleware(ctx, next) {
middleware(
{
...ctx,
delegatingActions: {
refresh: createDelegationAction('refresh'),
insert: createDelegationAction('insert'),
remove: createDelegationAction('remove'),
replace: createDelegationAction('replace'),
reload: createDelegationAction('reload'),
getState: stateKey => {
const states = {
page,
pageSize,
data,
pageCount,
total,
isLastPage
};
return _$(states[stateKey]);
}
}
},
promiseResolve
);
// 监听值改变时将会重置为第一页,此时会触发两次请求,在这边过滤掉一次请求
let requestPromise = promiseResolve();
if (!isReset.current) {
requestPromise = next();
} else if (requestCountInReseting.current === 0) {
requestCountInReseting.current++;
requestPromise = next();
}
return requestPromise;
},
force: (...args) => args[1] || force(...args),
abortLast: false,
...others
}),
{ send } = states,
// 计算data、total、isLastPage参数
total = $(initialData ? totalGetter(initialData) : undefinedValue, trueValue),
pageCount = $$(
() => {
const totalVal = _$(total);
return totalVal !== undefinedValue ? Math.ceil(totalVal / _$(pageSize)) : undefinedValue;
},
_expBatch$(pageSize, total),
trueValue
),
requestDataRef = useRequestRefState$(states.data);
// 判断是否可预加载数据
const canPreload = (
rawData = _$(requestDataRef),
preloadPage,
fetchMethod,
isNextPage = falseValue,
forceRequest
) => {
const { e: expireMilliseconds } = getLocalCacheConfigParam(fetchMethod);
// 如果缓存时间小于等于当前时间,表示没有设置缓存,此时不再预拉取数据
// 或者已经有缓存了也不预拉取
if (expireMilliseconds <= getTime()) {
return falseValue;
}
if (forceRequest) {
return trueValue;
}
if (queryCache(fetchMethod)) {
return falseValue;
}
const pageCountVal = _$(pageCount);
const exceedPageCount = pageCountVal
? preloadPage > pageCountVal
: isNextPage // 如果是判断预加载下一页数据且没有pageCount的情况下,通过最后一页数据量是否达到pageSize来判断
? len(listDataGetter(rawData)) < _$(pageSize)
: falseValue;
return preloadPage > 0 && !exceedPageCount;
};
// 预加载下一页数据
const fetchNextPage = (rawData, force = falseValue) => {
const nextPage = _$(page) + 1;
const fetchMethod = getHandlerMethod(nextPage);
if (preloadNextPage && canPreload(rawData, nextPage, fetchMethod, trueValue, force)) {
promiseCatch(fetch(fetchMethod, force), noop$1);
}
};
// 预加载上一页数据
const fetchPreviousPage = rawData => {
const prevPage = _$(page) - 1;
const fetchMethod = getHandlerMethod(prevPage);
if (preloadPreviousPage && canPreload(rawData, prevPage, fetchMethod)) {
promiseCatch(fetch(fetchMethod), noop$1);
}
};
// 如果返回的数据小于pageSize了,则认定为最后一页了
const isLastPage = $$(
() => {
const dataRaw = _$(requestDataRef);
if (!dataRaw) {
return trueValue;
}
const statesDataVal = listDataGetter(dataRaw);
const pageVal = _$(page);
const pageCountVal = _$(pageCount);
const dataLen = isArray$1(statesDataVal) ? len(statesDataVal) : 0;
return pageCountVal ? pageVal >= pageCountVal : dataLen < _$(pageSize);
},
_expBatch$(page, pageCount, states.data, pageSize),
trueValue
);
// 更新当前页缓存
const updateCurrentPageCache = () => {
const snapshotItem = getSnapshotMethods(_$(page));
snapshotItem &&
setCache(snapshotItem.entity, rawData => {
// 当关闭缓存时,rawData为undefined
if (rawData) {
const cachedListData = listDataGetter(rawData) || [];
splice(cachedListData, 0, len(cachedListData), ..._$(data));
return rawData;
}
});
};
// 初始化fetcher
const fetchStates = useFetcher({
force: forceFetch => forceFetch
});
const { fetching, fetch, abort: abortFetch, onSuccess: onFetchSuccess } = fetchStates;
const fetchingRef = useRequestRefState$(fetching);
onFetchSuccess(({ method, data: rawData }) => {
// 处理当fetch还没响应时就翻页到fetch对应的页码时,需要手动更新列表数据
const snapshotItem = getSnapshotMethods(_$(page));
if (snapshotItem && getMethodKey(snapshotItem.entity) === getMethodKey(method)) {
// 如果追加数据,才更新data
const listData = listDataGetter(rawData); // 更新data参数
if (append) {
// 下拉加载时需要替换当前页数据
const dataRaw = _$(data);
const pageSizeVal = _$(pageSize);
// 当做移除操作时,替换的数量小于pageSize,此时dataRaw % pageSizeVal会大于0
// 当新增操作时,替换的数量等于pageSize,此时dataRaw % pageSizeVal会等于0,此时不需要替换
const replaceNumber = len(dataRaw) % pageSizeVal;
replaceNumber > 0 &&
upd$(data, rawd => {
splice(rawd, (_$(page) - 1) * pageSizeVal, replaceNumber, ...listData);
return rawd;
});
} else {
upd$(data, listData);
}
}
});
states.onSuccess(({ data: rawData, sendArgs: [refreshPage, isRefresh], method }) => {
const { total: cachedTotal } = getSnapshotMethods(method) || {};
upd$(total, cachedTotal !== undefinedValue ? cachedTotal : totalGetter(rawData));
if (!isRefresh) {
fetchPreviousPage(rawData);
fetchNextPage(rawData);
}
const pageSizeVal = _$(pageSize);
const listData = listDataGetter(rawData); // 获取数组
paginationAssert(isArray$1(listData), 'Got wrong array, did you return the correct array of list in `data` function');
// 如果追加数据,才更新data
if (append) {
// 如果是reset则先清空数据
isReset.current && upd$(data, []);
if (refreshPage === undefinedValue) {
upd$(data, [..._$(data), ...listData]);
} else if (refreshPage) {
// 如果是刷新页面,则是替换那一页的数据
upd$(data, rawd => {
splice(rawd, (refreshPage - 1) * pageSizeVal, pageSizeVal, ...listData);
return rawd;
});
}
} else {
upd$(data, listData);
}
});
// 请求成功与否,都要重置它们
states.onComplete(() => {
isReset.current = falseValue;
requestCountInReseting.current = 0;
});
// 获取列表项所在位置
const getItemIndex = item => {
const index = _$(data).indexOf(item);
paginationAssert(index >= 0, 'item is not found in list');
return index;
};
/**
* 刷新指定页码数据,此函数将忽略缓存强制发送请求
* 如果未传入页码则会刷新当前页
* 如果传入一个列表项,将会刷新此列表项所在页,只对append模式有效
* @param pageOrItemPage 刷新的页码或列表项
*/
const refresh = useMemorizedCallback$((pageOrItemPage = _$(page)) => {
let refreshPage = pageOrItemPage;
if (append) {
if (!isNumber(pageOrItemPage)) {
const itemIndex = getItemIndex(pageOrItemPage);
refreshPage = Math.floor(itemIndex / _$(pageSize)) + 1;
}
paginationAssert(refreshPage <= _$(page), "refresh page can't greater than page");
// 更新当前页数据
promiseCatch(send(refreshPage, trueValue), noop$1);
} else {
paginationAssert(isNumber(refreshPage), 'unable to calculate refresh page by item in pagination mode');
// 页数相等,则刷新当前页,否则fetch数据
promiseCatch(
refreshPage === _$(page)
? send(undefinedValue, trueValue)
: fetch(handler(refreshPage, _$(pageSize)), trueValue),
noop$1
);
}
});
const removeSyncRunner = useFlag$(createSyncOnceRunner()).current;
// 删除除此usehook当前页和下一页的所有相关缓存
const invalidatePaginationCache = (all = falseValue) => {
const pageVal = _$(page);
const snapshotObj = methodSnapshots();
let snapshots = objectValues(snapshotObj);
if (all) {
removeSnapshot();
} else {
// 筛选出上一页、当前页、下一页的数据
const excludeSnapshotKeys = map(
filterItem(
[getSnapshotMethods(pageVal - 1), getSnapshotMethods(pageVal), getSnapshotMethods(pageVal + 1)],
Boolean
),
({ entity }) => getMethodKey(entity)
);
snapshots = map(
filterItem(objectKeys(snapshotObj), key => !includes(excludeSnapshotKeys, key)),
key => {
const item = snapshotObj[key];
delete snapshotObj[key];
return item;
}
);
}
invalidateCache(map(snapshots, ({ entity }) => entity));
};
// 单独拿出来的原因是
// 无论同步调用几次insert、remove,或它们组合调用,reset操作只需要异步执行一次
const resetSyncRunner = useFlag$(createSyncOnceRunner()).current;
const resetCache = () =>
resetSyncRunner(() => {
_$(fetchingRef) && abortFetch();
// 缓存失效
invalidatePaginationCache();
// 当下一页的数据量不超过pageSize时,强制请求下一页,因为有请求共享,需要在中断请求后异步执行拉取操作
setTimeoutFn(() => {
const snapshotItem = getSnapshotMethods(_$(page) + 1);
if (snapshotItem) {
const cachedListData = listDataGetter(queryCache(snapshotItem.entity) || {}) || [];
fetchNextPage(undefinedValue, len(cachedListData) < _$(pageSize));
}
});
});
// 统一更新总条数
const updateTotal = offset => {
// 更新当前页
const totalVal = _$(total);
if (isNumber(totalVal)) {
const offsetedTotal = Math.max(totalVal + offset, 0);
upd$(total, offsetedTotal);
const pageVal = _$(page);
// 更新冗余的total字段
forEach([getSnapshotMethods(pageVal - 1), getSnapshotMethods(pageVal), getSnapshotMethods(pageVal + 1)], item => {
item && (item.total = offsetedTotal);
});
}
};
/**
* 插入一条数据
* 如果未传入index,将默认插入到最前面
* 如果传入一个列表项,将插入到这个列表项的后面,如果列表项未在列表数据中将会抛出错误
* @param item 插入项
* @param position 插入位置(索引)或列表项
*/
const insert = useMemorizedCallback$((item, position = 0) => {
const index = isNumber(position) ? position : getItemIndex(position) + 1;
let popItem = undefinedValue;
upd$(data, rawd => {
// 当前展示的项数量刚好是pageSize的倍数时,才需要去掉一项数据,保证操作页的数量为pageSize
if (len(_$(data)) % _$(pageSize) === 0) {
popItem = rawd.pop();
}
// 插入位置为空默认插到最前面
splice(rawd, index, 0, item);
return rawd;
});
updateTotal(1);
// 当前页的缓存同步更新
updateCurrentPageCache();
// 如果有pop项,将它放到下一页缓存的头部,与remove的操作保持一致
// 这样在同步调用insert和remove时表现才一致
if (popItem) {
const snapshotItem = getSnapshotMethods(_$(page) + 1);
snapshotItem &&
setCache(snapshotItem.entity, rawData => {
if (rawData) {
const cachedListData = listDataGetter(rawData) || [];
cachedListData.unshift(popItem);
cachedListData.pop();
return rawData;
}
});
}
// 插入项后也需要让缓存失效,以免不同条件下缓存未更新
resetCache();
});
/**
* 移除一条数据
* 如果传入的是列表项,将移除此列表项,如果列表项未在列表数据中将会抛出错误
* @param position 移除的索引或列表项
*/
let tempData; // 临时保存的数据,以便当需要重新加载时用于数据恢复
const remove = useMemorizedCallback$(position => {
const index = isNumber(position) ? position : getItemIndex(position);
indexAssert(index, _$(data));
const pageVal = _$(page);
const nextPage = pageVal + 1;
const snapshotItem = getSnapshotMethods(nextPage);
let fillingItem = undefinedValue; // 补位数据项
snapshotItem &&
setCache(snapshotItem.entity, rawData => {
if (rawData) {
const cachedListData = listDataGetter(rawData);
// 从下一页列表的头部开始取补位数据
fillingItem = shift(cachedListData || []);
return rawData;
}
});
const isLastPageVal = _$(isLastPage);
if (fillingItem || isLastPageVal) {
// 如果有下一页数据则通过缓存数据补位
if (!tempData) {
tempData = [..._$(data)];
}
if (index >= 0) {
upd$(data, rawd => {
splice(rawd, index, 1);
// 如果有下一页的补位数据才去补位,因为有可能是最后一页才进入删除的
fillingItem && pushItem(rawd, fillingItem);
return rawd;
});
}
} else if (tempData) {
upd$(data, tempData); // 当移除项数都用完时还原数据,减少不必要的视图渲染
}
updateTotal(-1);
// 当前页的缓存同步更新
updateCurrentPageCache();
// 如果没有下一页数据,或同步删除的数量超过了pageSize,则恢复数据并重新加载本页
// 需异步操作,因为可能超过pageSize后还有remove函数被同步执行
resetCache();
removeSyncRunner(() => {
// 移除最后一页数据时,就不需要再刷新了
if (!fillingItem && !isLastPageVal) {
refresh(pageVal);
}
if (!append && isLastPageVal && len(_$(data)) <= 0) {
upd$(page, pageVal => pageVal - 1); // 翻页模式下,如果是最后一页且全部项被删除了,则往前翻一页
}
tempData = undefinedValue;
});
});
/**
* 替换一条数据
* 如果position传入的是列表项,将替换此列表项,如果列表项未在列表数据中将会抛出错误
* @param item 替换项
* @param position 替换位置(索引)或列表项
*/
const replace = useMemorizedCallback$((item, position) => {
paginationAssert(position !== undefinedValue, 'must specify replace position');
const index = isNumber(position) ? position : getItemIndex(position);
indexAssert(index, _$(data));
upd$(data, rawd => {
splice(rawd, index, 1, item);
return rawd;
});
// 当前页的缓存同步更新
updateCurrentPageCache();
});
/**
* 从第${initialPage}页开始重新加载列表,并清空缓存
*/
const reload = useMemorizedCallback$(() => {
invalidatePaginationCache(trueValue);
isReset.current = trueValue;
_$(page) === initialPage ? promiseCatch(send(), noop$1) : upd$(page, initialPage);
});
// 兼容react,每次缓存最新的操作函数,避免闭包陷阱
delegationActions.current = {
refresh,
insert,
remove,
replace,
reload
};
/** @Returns */
return {
...states,
/**
* 将data的更新指向导出的data
* @param newFrontStates 新状态对象
*/
update: newFrontStates => {
const { data: newData } = newFrontStates;
if (newData) {
upd$(data, newData);
delete newFrontStates.data;
}
states.update(newFrontStates);
},
fetching: fetchStates.fetching,
onFetchSuccess: fetchStates.onSuccess,
onFetchError: fetchStates.onError,
onFetchComplete: fetchStates.onComplete,
page,
pageSize,
data: _exp$(data),
pageCount: _exp$(pageCount),
total: _exp$(total),
isLastPage: _exp$(isLastPage),
refresh,
insert,
remove,
replace,
reload
};
}
var createHookEvent = (eventType, method, behavior, silentMethod, queueName, retryTimes, retryDelay, sendArgs, data, vDataResponse, error, status) => {
const allPropsEvent = {
/** 事件对应的请求行为 */
behavior,
/** 当前的method实例 */
method,
/** 当前的silentMethod实例,当behavior为static时没有值 */
silentMethod,
/** 已重试的次数,在beforePush和pushed事件中没有值 */
retryTimes,
/** 重试的延迟时间 */
retryDelay,
/** 通过send触发请求时传入的参数 */
sendArgs,
/** 响应数据,只在成功时有值 */
data,
/** 虚拟数据和实际值的集合 */
vDataResponse,
/** 失败时抛出的错误,只在失败时有值 */
error,
/** 响应状态 */
status,
/** silentMethod所在的队列名,全局事件有值 */
queueName
};
const sqEvent = {};
forEach(objectKeys(allPropsEvent), key => {
allPropsEvent[key] !== undefinedValue &&
(sqEvent[key] = allPropsEvent[key]);
});
// 将此类的对象重新命名,让它看上去是由不同的类生成的对象
// 以此来对应typescript中定义的类型
const typeName = [
'GlobalSQEvent',
'GlobalSQSuccessEvent',
'GlobalSQErrorEvent',
'GlobalSQFailEvent',
'ScopedSQEvent',
'ScopedSQSuccessEvent',
'ScopedSQErrorEvent',
'ScopedSQCompleteEvent',
'ScopedSQRetryEvent',
'RetriableRetryEvent',
'RetriableFailEvent',
'SSEOpenEvent',
'SSEMessageEvent',
'SSEErrorEvent' // 13
][eventType];
typeName && defineProperty(sqEvent, symbolToStringTag, typeName);
return sqEvent;
};
/**
* 全局的虚拟数据收集数组
* 它只会在method创建时为数组,其他时间为undefined
*
* 解释:收集虚拟数据的目的为了判断某个method实例内是否使用了虚拟数据
* 包括以下形式:
* useSQRequest((vDataId) => createMethod({ vDataId }) // 引用函数参数
* useSQRequest(() => createMethod({ vDataId }) // 直接引用作用域参数
*
* 甚至是:
* function createMethod(obj) {
* return alovaInst.Get('/list', {
* params: { status: obj.vDataId ? 1 : 0 }
* })
* }
* useSQRequest(() => createMethod(obj) // 直接引用作用域参数
*
* 使用虚拟数据的方式包含:
* 1. 直接作为参数赋值
* 2. 使用虚拟数据id
* 3. 间接使用虚拟数据,如
* vData ? 1 : 0
* !!vData
* vData + 1
* 等作为计算参数参与的形式
*/
let vDataIdCollectBasket;
const setVDataIdCollectBasket = (value) => (vDataIdCollectBasket = value);
/**
* 依赖的alova实例,它的存储适配器、请求适配器等将用于存取SilentMethod实例,以及发送静默提交
*/
let dependentAlovaInstance;
const setDependentAlova = (alovaInst) => {
dependentAlovaInstance = alovaInst;
};
/**
* 设置自定义的序列化器
*/
let customSerializers = {};
const setCustomSerializers = (serializers = {}) => {
customSerializers = serializers;
};
/**
* silentFactory状态
* 0表示未启动
* 1表示进行中,调用bootSilentFactory后变更
* 2表示请求失败,即按重试规则请求达到最大次数时,或不匹配重试规则时变更
*/
let silentFactoryStatus = 0;
const setSilentFactoryStatus = (status) => (silentFactoryStatus = status);
/**
* silentQueue内的请求等待时间,单位为毫秒(ms)
* 它表示即将发送请求的silentMethod的等待时间
* 如果未设置,或设置为0表示立即触发silentMethod请求
*
* Tips:
* 1. 直接设置时默认对default queue有效
* 2. 如果需要对其他queue设置可指定为对象,如:
* [
* 表示对名为customName的队列设置请求等待5000ms
* { name: 'customName', wait: 5000 },
*
* // 表示前缀为prefix的所有队列中,method实例名为xxx的请求设置等待5000ms
* { name: /^prefix/, wait: silentMethod => silentMethod.entity.config.name === 'xxx' ? 5000 : 0 },
* ]
*
* >>> 它只在请求成功时起作用,如果失败则会使用重试策略参数
*/
let queueRequestWaitSetting = [];
const setQueueRequestWaitSetting = (requestWaitSetting = 0) => {
queueRequestWaitSetting = isArray$1(requestWaitSetting)
? requestWaitSetting
: [
{
queue: DEFAUT_QUEUE_NAME,
wait: requestWaitSetting
}
];
};
/** 全局的silent事件回调函数 */
const bootHandlers = [];
const beforeHandlers = [];
const successHandlers = [];
const errorHandlers = [];
const failHandlers = [];
/** silentAssert */
const silentAssert = createAssert('useSQHook');
var dateSerializer = {
forward: data => (instanceOf(data, Date) ? data.getTime() : undefinedValue),
backward: ts => newInstance(Date, ts)
};
var regexpSerializer = {
forward: data => (instanceOf(data, RegExpCls) ? data.source : undefined),
backward: source => newInstance(RegExpCls, source)
};
const createSerializerPerformer = (customSerializers = {}) => {
/**
* 合并内置序列化器和自定义序列化器
*/
const serializers = {
date: dateSerializer,
regexp: regexpSerializer,
...customSerializers
};
/**
* 序列化数据
*/
const serialize = (payload) => {
if (isObject(payload)) {
payload = walkObject(isArray$1(payload) ? [...payload] : { ...payload }, value => {
let finallyApplySerializerName = undefinedValue;
// 找到匹配的序列化器并进行值的序列化,未找到则返回原值
const serializedValue = objectKeys(serializers).reduce((currentValue, serializerName) => {
if (!finallyApplySerializerName) {
const serializedValueItem = serializers[serializerName].forward(currentValue);
if (serializedValueItem !== undefinedValue) {
finallyApplySerializerName = serializerName;
currentValue = serializedValueItem;
}
}
return currentValue;
}, value);
// 需要用原始值判断,否则像new Number(1)等包装类也会是[object Object]
const toStringTag = ObjectCls.prototype.toString.call(value);
if (toStringTag === '[object Object]') {
value = { ...value };
}
else if (isArray$1(value)) {
value = [...value];
}
return finallyApplySerializerName !== undefinedValue ? [finallyApplySerializerName, serializedValue] : value;
});
}
return payload;
};
/**
* 反序列化数据
*/
const deserialize = (payload) => isObject(payload)
? walkObject(payload, value => {
if (isArray$1(value) && len(value) === 2) {
const foundSerializer = serializers[value[0]];
value = foundSerializer ? foundSerializer.backward(value[1]) : value;
}
return value;
}, falseValue)
: payload;
return {
serialize,
deserialize
};
};
const symbolVDataId = Symbol('vdid'), symbolOriginal = Symbol('original'), regVDataId = /\[vd:([0-9a-z]+)\]/;
/**
* 统一的vData收集函数
* 它将在以下4个位置被调用
* 1. 访问子属性时
* 2. 参与计算并触发[Symbol.toPrimitive]时
* 3. 获取vData的id时
* 4. 获取其原始值时
*
* @param returnValue 返回值,如果是函数则调用它
* @returns 收集函数
*/
const vDataCollectUnified = (target) => {
const vDataId = target === null || target === void 0 ? void 0 : target[symbolVDataId];
vDataId && vDataIdCollectBasket && (vDataIdCollectBasket[vDataId] = undefinedValue);
};
// export const vDataGetter = (key: string) => vDataCollectGetter((thisObj: any) => thisObj.__proto__[key].call(thisObj));
/**
* 虚拟数据字符串化,如果参数不是虚拟数据则返回原数据
* @param target 虚拟数据
* @param returnOriginalIfNotVData 如果不是虚拟数据则返回原值
* @returns 虚拟数据id或原数据
*/
const stringifyVData = (target, returnOriginalIfNotVData = trueValue) => {
vDataCollectUnified(target);
const vDataIdRaw = target === null || target === void 0 ? void 0 : target[symbolVDataId];
const vDataId = vDataIdRaw ? `[vd:${vDataIdRaw}]` : undefinedValue;
return vDataId || (returnOriginalIfNotVData ? target : undefinedValue);
};
/**
* 创建虚拟数据id收集的getter函数
* @param valueReturnFn 返回值函数
* @returns getter函数
*/
function stringifyWithThis() {
return stringifyVData(this);
}
/**
* Null包装类实现
*/
const Null = function () { };
Null.prototype = ObjectCls.create(nullValue, {
[STR_VALUE_OF]: valueObject(stringifyWithThis)
});
/**
* Undefined包装类实现
*/
const Undefined = function () { };
Undefined.prototype = ObjectCls.create(nullValue, {
[STR_VALUE_OF]: valueObject(stringifyWithThis)
});
/**
* 创建虚拟响应数据
* @returns 虚拟响应数据代理实例
*/
var createVirtualResponse = (structure, vDataId = uuid()) => {
const transform2VData = (value, vDataIdInner = uuid()) => {
if (value === nullValue) {
value = newInstance(Null);
}
else if (value === undefinedValue) {
value = newInstance(Undefined);
}
else {
const newValue = ObjectCls(value);
defineProperty(newValue, STR_VALUE_OF, stringifyWithThis);
defineProperty(newValue, symbolOriginal, value);
value = newValue;
}
defineProperty(value, symbolVDataId, vDataIdInner);
return value;
};
const virtualResponse = transform2VData(structure, vDataId);
if (isPlainOrCustomObject(virtualResponse) || isArray$1(virtualResponse)) {
walkObject(virtualResponse, value => transform2VData(value));
}
return virtualResponse;
};
!!(process.env.NODE_ENV !== "production") ? Object.freeze({}) : {};
!!(process.env.NODE_ENV !== "production") ? Object.freeze([]) : [];
const isArray = Array.isArray;
/**
* 获取带虚拟数据变量的原始值
* 此函数也将会进行vData收集
* @param target 目标值
* @param deepDehydrate 是否深度脱水值
* @returns 具有原始类型的目标值
*/
const dehydrateVDataUnified = (target, deepDehydrate = trueValue) => {
const dehydrateItem = (value) => {
vDataCollectUnified(value);
if (value === null || value === void 0 ? void 0 : value[symbolVDataId]) {
if (instanceOf(value, Undefined)) {
value = undefinedValue;
}
else if (instanceOf(value, Null)) {
value = nullValue;
}
else if (instanceOf(value, NumberCls) || instanceOf(value, StringCls) || instanceOf(value, BooleanCls)) {
value = value[symbolOriginal];
}
}
return value;
};
const newTarget = dehydrateItem(target);
// 如果是对象或数组,需要深度遍历获取虚拟数据值
if (deepDehydrate && (isPlainOrCustomObject(newTarget) || isArray(newTarget))) {
walkObject(newTarget, value => dehydrateItem(value));
}
return newTarget;
};
/**
* 上面函数deepDehydrate为true的版本
*/
var dehydrateVData = (target) => dehydrateVDataUnified(target);
const vDataKey = '__$k', vDataValueKey = '__$v';
const getAlovaStorage = () => {
// 未启动silentFactory时提供提示
silentAssert(!!dependentAlovaInstance, 'alova instance is not found, Do you forget to set `alova` or call `bootSilentFactory`?');
return dependentAlovaInstance.storage;
};
let serializerPerformer = undefinedValue;
const silentMethodIdQueueMapStorageKey = 'alova.SQ', // silentMethod实例id组成的队列集合缓存key
silentMethodStorageKeyPrefix = 'alova.SM.', // silentMethod实例缓存key前缀
/**
* 持久化带虚拟数据和可序列化的数据集合
* @param key 持久化key
* @param payload 持久化数据
*/
storageSetItem = (key, payload) => {
const storage = getAlovaStorage();
if (isObject(payload)) {
payload = walkObject(isArray$1(payload) ? [...payload] : { ...payload }, (value, key, parent) => {
var _a;
if (key === vDataValueKey && parent[vDataKey]) {
return value;
}
// 如果序列化的是silentMethod实例,则过滤掉alova实例
if (key === 'context' && ((_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'Alova') {
return undefinedValue;
}
const vDataId = value === null || value === void 0 ? void 0 : value[symbolVDataId];
let primitiveValue = dehydrateVDataUnified(value, falseValue);
// 需要用原始值判断,否则像new Number(1)等包装类也会是[object Object]
const toStringTag = ObjectCls.prototype.toString.call(primitiveValue);
if (toStringTag === '[object Object]') {
value = { ...value };
primitiveValue = {};
}
else if (isArray$1(value)) {
value = [...value];
primitiveValue = [];
}
if (vDataId) {
const valueWithVData = {
[vDataKey]: vDataId,
// 对于对象和数组来说,它内部的属性会全部通过`...value`放到外部,因此内部的不需要再进行遍历转换了
// 因此将数组或对象置空,这样既避免了重复转换,又避免了污染原对象
[vDataValueKey]: primitiveValue,
...value
};
// 如果是String类型,将会有像数组一样的如0、1、2为下标,值为字符的项,需将他们过滤掉
if (instanceOf(value, StringCls)) {
for (let i = 0; i < len(value); i++) {
valueWithVData === null || valueWithVData === void 0 ? true : delete valueWithVData[i];
}
}
// 如果转换成了虚拟数据,则将转换值赋给它内部,并在下面逻辑中统一由value处理
value = valueWithVData;
}
return value;
});
}
serializerPerformer = serializerPerformer || createSerializerPerformer(customSerializers);
storage.set(key, serializerPerformer.serialize(payload));
},
/**
* 取出持久化数据,并数据转换虚拟数据和已序列化数据
* @param key 持久化数据的key
*/
storageGetItem = (key) => {
const storagedResponse = getAlovaStorage().get(key);
serializerPerformer = serializerPerformer || createSerializerPerformer(customSerializers);
return isObject(storagedResponse)
? walkObject(serializerPerformer.deserialize(storagedResponse), value => {
// 将虚拟数据格式转换回虚拟数据实例
if (isObject(value) && (value === null || value === void 0 ? void 0 : value[vDataKey])) {
const vDataId = value[vDataKey];
const vDataValue = createVirtualResponse(value[vDataValueKey], vDataId);
forEach(objectKeys(value), key => {
if (!includes([vDataKey, vDataValueKey], key)) {
vDataValue[key] = value[key];
}
});
value = vDataValue;
}
return value;
}, falseValue)
: storagedResponse;
},
/**
* 移除持久化数据
* @param key 持久化数据的key
*/
storageRemoveItem = (key) => {
getAlovaStorage().remove(key);
};
/**
* 序列化并保存silentMethod实例
* @param silentMethodInstance silentMethod实例
*/
const persistSilentMethod = (silentMethodInstance) => {
storageSetItem(silentMethodStorageKeyPrefix + silentMethodInstance.id, silentMethodInstance);
};
/**
* 将静默请求的配置信息放入对应storage中
* 逻辑:通过构造一个key,并用这个key将静默方法的配置信息放入对应storage中,然后将key存入统一管理key的存储中
* @param silentMethod SilentMethod实例
* @param queue 操作的队列名
*/
const push2PersistentSilentQueue = (silentMethodInstance, queueName) => {
persistSilentMethod(silentMethodInstance);
// 将silentMethod实例id保存到queue存储中
const silentMethodIdQueueMap = (storageGetItem(silentMethodIdQueueMapStorageKey) ||
{});
const currentQueue = (silentMethodIdQueueMap[queueName] = silentMethodIdQueueMap[queueName] || []);
pushItem(currentQueue, silentMethodInstance.id);
storageSetItem(silentMethodIdQueueMapStorageKey, silentMethodIdQueueMap);
};
/**
* 对缓存中的silentMethod实例移除或替换
* @param queue 操作的队列名
* @param targetSilentMethodId 目标silentMethod实例id
* @param newSilentMethod 替换的新silentMethod实例,未传则表示删除
*/
const spliceStorageSilentMethod = (queueName, targetSilentMethodId, newSilentMethod) => {
// 将silentMethod实例id从queue中移除
const silentMethodIdQueueMap = (storageGetItem(silentMethodIdQueueMapStorageKey) ||
{});
const currentQueue = silentMethodIdQueueMap[queueName] || [];
const index = currentQueue.findIndex(id => id === targetSilentMethodId);
if (index >= 0) {
if (newSilentMethod) {
splice(currentQueue, index, 1, newSilentMethod.id);
persistSilentMethod(newSilentMethod);
}
else {
splice(currentQueue, index, 1);
}
storageRemoveItem(silentMethodStorageKeyPrefix + targetSilentMethodId);
// 队列为空时删除此队列
len(currentQueue) <= 0 && delete silentMethodIdQueueMap[queueName];
if (len(objectKeys(silentMethodIdQueueMap)) > 0) {
storageSetItem(silentMethodIdQueueMapStorageKey, silentMethodIdQueueMap);
}
else {
// 队列集合为空时移除它
storageRemoveItem(silentMethodIdQueueMapStorageKey);
}
}
};
/** 静默方法队列集合 */
let silentQueueMap = {};
/**
* 合并queueMap到silentMethod队列集合
* @param queueMap silentMethod队列集合
*/
const merge2SilentQueueMap = (queueMap) => {
forEach(objectKeys(queueMap), targetQueueName => {
const currentQueue = (silentQueueMap[targetQueueName] = silentQueueMap[targetQueueName] || []);
pushItem(currentQueue, ...queueMap[targetQueueName]);
});
};
/**
* 深层遍历目标数据,并将虚拟数据替换为实际数据
* @param target 目标数据
* @param vDataResponse 虚拟数据和实际数据的集合
* @returns 是否有替换数据
*/
const deepReplaceVData = (target, vDataResponse) => {
// 搜索单一值并将虚拟数据对象或虚拟数据id替换为实际值
const replaceVData = (value) => {
const vData = stringifyVData(value);
// 如果直接是虚拟数据对象并且在vDataResponse中,则使用vDataResponse中的值替换Map
// 如果是字符串,则里面可能包含虚拟数据id并且在vDataResponse中,也需将它替换为实际值Map
// 不在本次vDataResponse中的虚拟数据将不变,它可能是下一次请求的虚拟数据Map
if (vData in vDataResponse) {
return vDataResponse[vData];
}
else if (isString(value)) {
return value.replace(newInstance(RegExpCls, regVDataId.source, 'g'), mat => mat in vDataResponse ? vDataResponse[mat] : mat);
}
return value;
};
if (isObject(target) && !stringifyVData(target, falseValue)) {
walkObject(target, replaceVData);
}
else {
target = replaceVData(target);
}
return target;
};
/**
* 更新队列内的method实例,将虚拟数据替换为实际数据
* @param vDataResponse 虚拟id和对应真实数据的集合
* @param targetQueue 目标队列
*/
const updateQueueMethodEntities = (vDataResponse, targetQueue) => {
forEach(targetQueue, silentMethodItem => {
// 深层遍历entity对象,如果发现有虚拟数据或虚拟数据id,则替换为实际数据
deepReplaceVData(silentMethodItem.entity, vDataResponse);
// 如果method实例有更新,则重新持久化此silentMethod实例
silentMethodItem.cache && persistSilentMethod(silentMethodItem);
});
};
/**
* 使用响应数据替换虚拟数据
* @param response 真实响应数据
* @param virtualResponse 虚拟响应数据
* @returns 虚拟数据id所构成的对应真实数据集合
*/
const replaceVirtualResponseWithResponse = (virtualResponse, response) => {
let vDataResponse = {};
const vDataId = stringifyVData(virtualResponse, falseValue);
vDataId && (vDataResponse[vDataId] = response);
if (isObject(virtualResponse)) {
for (const i in virtualResponse) {
vDataResponse = {
...vDataResponse,
...replaceVirtualResponseWithResponse(virtualResponse[i], response === null || response === void 0 ? void 0 : response[i])
};
}
}
return vDataResponse;
};
/**
* 启动SilentMethod队列
* 1. 静默提交将会放入队列中,并按顺序发送请求,只有等到前一个请求响应后才继续发送后面的请求
* 2. 重试次数只有在未响应时才触发,在服务端响应错误或断网情况下,不会重试
* 3. 在达到重试次数仍未成功时,当设置了nextRound(下一轮)时延迟nextRound指定的时间后再次请求,否则将在刷新后再次尝试
* 4. 如果有resolveHandler和rejectHandler,将在请求完成后(无论成功还是失败)调用,通知对应的请求继续响应
*
* @param queue SilentMethod队列
*/
const setSilentMethodActive = (silentMethodInstance, active) => {
if (active) {
silentMethodInstance.active = active;
}
else {
delete silentMethodInstance.active;
}
};
const defaultBackoffDelay = 1000;
const bootSilentQueue = (queue, queueName) => {
/**
* 根据请求等待参数控制回调函数的调用,如果未设置或小于等于0则立即触发
* @param queueName 队列名称
* @param callback 回调函数
*/
const emitWithRequestDelay = (queueName) => {
const nextSilentMethod = queue[0];
if (nextSilentMethod) {
const targetSetting = queueRequestWaitSetting.find(({ queue }) => instanceOf(queue, RegExpCls) ? regexpTest(queue, queueName) : queue === queueName);
const callback = () => queue[0] && silentMethodRequest(queue[0]);
const delay = (targetSetting === null || targetSetting === void 0 ? void 0 : targetSetting.wait) ? sloughConfig(targetSetting.wait, [nextSilentMe