UNPKG

@alova/scene-react

Version:

scenario react hooks with alova.js

1,575 lines (1,507 loc) 132 kB
/** * @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