From 2800b5c28bd961b81931bdaec501ea68a84167b9 Mon Sep 17 00:00:00 2001 From: promer94 Date: Fri, 5 Mar 2021 14:36:19 +0800 Subject: [PATCH 1/4] strict mode --- jest.config.js | 7 +- src/config.ts | 56 ++++++++-------- src/index.ts | 2 +- src/libs/web-preset.ts | 2 +- src/types.ts | 17 +++-- src/use-swr-infinite.ts | 88 ++++++++++++------------- src/use-swr.ts | 142 +++++++++++++++++++++------------------- tsconfig.json | 1 + 8 files changed, 165 insertions(+), 150 deletions(-) diff --git a/jest.config.js b/jest.config.js index 9f913bd92..f7c06411a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,5 +2,10 @@ module.exports = { preset: 'ts-jest', testRegex: '/test/.*\\.test\\.tsx$', modulePathIgnorePatterns: ['/examples/'], - setupFilesAfterEnv: ['/jest-setup.ts'] + setupFilesAfterEnv: ['/jest-setup.ts'], + globals: { + 'ts-jest': { + diagnostics: false + } + } } diff --git a/src/config.ts b/src/config.ts index 3d7c8c74c..a8a6a8bd4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,11 +12,11 @@ const cache = new Cache() // error retry function onErrorRetry( - _, - __, - config: ConfigInterface, + _: unknown, + __: string, + config: Readonly>, revalidate: revalidateType, - opts: RevalidateOptionInterface + opts: Required ): void { if (!config.isDocumentVisible()) { // if it's hidden, stop @@ -32,7 +32,7 @@ function onErrorRetry( } // exponential backoff - const count = Math.min(opts.retryCount || 0, 8) + const count = Math.min(opts.retryCount, 8) const timeout = ~~((Math.random() + 0.5) * (1 << count)) * config.errorRetryInterval setTimeout(revalidate, timeout, opts) @@ -43,36 +43,36 @@ function onErrorRetry( // slow connection (<= 70Kbps) const slowConnection = typeof window !== 'undefined' && + // @ts-ignore navigator['connection'] && + // @ts-ignore ['slow-2g', '2g'].indexOf(navigator['connection'].effectiveType) !== -1 // config -const defaultConfig: ConfigInterface = Object.assign( - { - // events - onLoadingSlow: () => {}, - onSuccess: () => {}, - onError: () => {}, - onErrorRetry, +const defaultConfig = { + // events + onLoadingSlow: () => {}, + onSuccess: () => {}, + onError: () => {}, + onErrorRetry, - errorRetryInterval: (slowConnection ? 10 : 5) * 1000, - focusThrottleInterval: 5 * 1000, - dedupingInterval: 2 * 1000, - loadingTimeout: (slowConnection ? 5 : 3) * 1000, + errorRetryInterval: (slowConnection ? 10 : 5) * 1000, + focusThrottleInterval: 5 * 1000, + dedupingInterval: 2 * 1000, + loadingTimeout: (slowConnection ? 5 : 3) * 1000, - refreshInterval: 0, - revalidateOnFocus: true, - revalidateOnReconnect: true, - refreshWhenHidden: false, - refreshWhenOffline: false, - shouldRetryOnError: true, - suspense: false, - compare: dequal, + refreshInterval: 0, + revalidateOnFocus: true, + revalidateOnReconnect: true, + refreshWhenHidden: false, + refreshWhenOffline: false, + shouldRetryOnError: true, + suspense: false, + compare: dequal, - isPaused: () => false - }, - webPreset -) + isPaused: () => false, + ...webPreset +} as const export { cache } export default defaultConfig diff --git a/src/index.ts b/src/index.ts index 44daca680..9b6f62c11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ export { revalidateType, RevalidateOptionInterface, keyInterface, - responseInterface, + ResponseInterface as responseInterface, CacheInterface } from './types' export default useSWR diff --git a/src/libs/web-preset.ts b/src/libs/web-preset.ts index c57ed7be2..f31d4287a 100644 --- a/src/libs/web-preset.ts +++ b/src/libs/web-preset.ts @@ -19,7 +19,7 @@ const isDocumentVisible = () => { return true } -const fetcher = url => fetch(url).then(res => res.json()) +const fetcher = (url: string) => fetch(url).then(res => res.json()) const registerOnFocus = (cb: () => void) => { if ( diff --git a/src/types.ts b/src/types.ts index 86aef5875..541d825b7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,23 +23,26 @@ export interface ConfigInterface< isOnline: () => boolean isDocumentVisible: () => boolean isPaused: () => boolean - onLoadingSlow: (key: string, config: ConfigInterface) => void + onLoadingSlow: ( + key: string, + config: Readonly>> + ) => void onSuccess: ( data: Data, key: string, - config: ConfigInterface + config: Readonly>> ) => void onError: ( err: Error, key: string, - config: ConfigInterface + config: Readonly>> ) => void onErrorRetry: ( err: Error, key: string, - config: ConfigInterface, + config: Readonly>>, revalidate: revalidateType, - revalidateOpts: RevalidateOptionInterface + revalidateOpts: Required ) => void registerOnFocus?: (cb: () => void) => void registerOnReconnect?: (cb: () => void) => void @@ -80,7 +83,7 @@ export type broadcastStateInterface = ( error?: Error, isValidating?: boolean ) => void -export type responseInterface = { +export interface ResponseInterface { data?: Data error?: Error revalidate: () => Promise @@ -94,7 +97,7 @@ export type revalidateType = ( revalidateOpts: RevalidateOptionInterface ) => Promise -export type actionType = { +export type actionType = { data?: Data error?: Error isValidating?: boolean diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 63d8b88bc..041d2ae28 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -4,25 +4,26 @@ import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' import useSWR from './use-swr' -import { keyType, fetcherFn, ConfigInterface, responseInterface } from './types' +import { + keyType, + fetcherFn, + ConfigInterface, + ResponseInterface, + mutateCallback +} from './types' type KeyLoader = ( index: number, previousPageData: Data | null ) => keyType -type SWRInfiniteConfigInterface = ConfigInterface< - Data[], - Error, - fetcherFn -> & { +interface SWRInfiniteConfigInterface + extends ConfigInterface> { initialSize?: number revalidateAll?: boolean persistSize?: boolean } -type SWRInfiniteResponseInterface = responseInterface< - Data[], - Error -> & { +interface SWRInfiniteResponseInterface + extends ResponseInterface { size: number setSize: ( size: number | ((size: number) => number) @@ -30,51 +31,47 @@ type SWRInfiniteResponseInterface = responseInterface< } function useSWRInfinite( - getKey: KeyLoader -): SWRInfiniteResponseInterface -function useSWRInfinite( - getKey: KeyLoader, - config?: Partial> -): SWRInfiniteResponseInterface -function useSWRInfinite( - getKey: KeyLoader, - fn?: fetcherFn, - config?: Partial> -): SWRInfiniteResponseInterface -function useSWRInfinite( - getKey: KeyLoader, - ...options: any[] + ...args: + | readonly [KeyLoader] + | readonly [KeyLoader, fetcherFn] + | readonly [ + KeyLoader, + Partial> + ] + | readonly [ + KeyLoader, + fetcherFn, + Partial> + ] ): SWRInfiniteResponseInterface { - let _fn: fetcherFn | undefined, - _config: Partial> = {} - - if (options.length > 1) { - _fn = options[0] - _config = options[1] - } else { - if (typeof options[0] === 'function') { - _fn = options[0] - } else if (typeof options[0] === 'object') { - _config = options[0] - } - } + const getKey = args[0] - const config: SWRInfiniteConfigInterface = Object.assign( + const config = Object.assign( {}, defaultConfig, useContext(SWRConfigContext), - _config + args.length > 2 + ? args[2] + : args.length === 2 && typeof args[1] === 'object' + ? args[1] + : {} ) - let { + // in typescript args.length > 2 is not same as args.lenth === 3 + // we do a safe type assertion here + // args.length === 3 + const fn = (args.length > 2 + ? args[1] + : args.length === 2 && typeof args[1] === 'function' + ? args[1] + : config.fetcher) as fetcherFn + + const { initialSize = 1, revalidateAll = false, persistSize = false, - fetcher: defaultFetcher, ...extraConfig } = config - const fn = typeof _fn !== 'undefined' ? _fn : defaultFetcher - // get the serialized key of the first page let firstPageKey: string | null = null try { @@ -185,7 +182,10 @@ function useSWRInfinite( }, [swr.data]) const mutate = useCallback( - (data, shouldRevalidate = true) => { + ( + data: Data[] | Promise | mutateCallback | undefined, + shouldRevalidate = true + ) => { if (shouldRevalidate && typeof data !== 'undefined') { // we only revalidate the pages that are changed const originalData = dataRef.current diff --git a/src/use-swr.ts b/src/use-swr.ts index 0407e986c..a5e316946 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -18,7 +18,7 @@ import { fetcherFn, keyInterface, mutateInterface, - responseInterface, + ResponseInterface, RevalidateOptionInterface, triggerInterface, updaterInterface @@ -39,14 +39,16 @@ const rAF = IS_SERVER // useLayoutEffect in the browser. const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect +type revalidatorInterface = (...args: any[]) => void + // global state managers -const CONCURRENT_PROMISES = {} -const CONCURRENT_PROMISES_TS = {} -const FOCUS_REVALIDATORS = {} -const RECONNECT_REVALIDATORS = {} -const CACHE_REVALIDATORS = {} -const MUTATION_TS = {} -const MUTATION_END_TS = {} +const CONCURRENT_PROMISES: Record = {} +const CONCURRENT_PROMISES_TS: Record = {} +const FOCUS_REVALIDATORS: Record = {} +const RECONNECT_REVALIDATORS: Record = {} +const CACHE_REVALIDATORS: Record = {} +const MUTATION_TS: Record = {} +const MUTATION_END_TS: Record = {} // generate strictly increasing timestamps const now = (() => { @@ -56,7 +58,7 @@ const now = (() => { // setup DOM events listeners for `focus` and `reconnect` actions if (!IS_SERVER) { - const revalidate = revalidators => { + const revalidate = (revalidators: Record) => { if (!defaultConfig.isDocumentVisible() || !defaultConfig.isOnline()) return for (const key in revalidators) { @@ -136,7 +138,7 @@ const mutate: mutateInterface = async ( const beforeMutationTs = MUTATION_TS[key] const beforeConcurrentPromisesTs = CONCURRENT_PROMISES_TS[key] - let data, error + let data: any, error: unknown let isAsyncMutation = false if (_data && typeof _data === 'function') { @@ -213,35 +215,41 @@ const mutate: mutateInterface = async ( } function useSWR( - key: keyInterface -): responseInterface -function useSWR( - key: keyInterface, - config?: Partial> -): responseInterface -function useSWR( - key: keyInterface, - // `null` is used for a hack to manage shared state with SWR - // https://github.com/vercel/swr/pull/918 - fn?: fetcherFn | null, - config?: Partial> -): responseInterface -function useSWR( - _key: keyInterface, - ...options: any[] -): responseInterface { - let _fn: fetcherFn | undefined, - _config: Partial> = {} - if (options.length > 1) { - _fn = options[0] - _config = options[1] - } else { - if (typeof options[0] === 'function') { - _fn = options[0] - } else if (typeof options[0] === 'object') { - _config = options[0] - } - } + ...args: + | readonly [keyInterface] + | readonly [keyInterface, fetcherFn | null] + | readonly [keyInterface, Partial>] + | readonly [ + keyInterface, + fetcherFn | null, + Partial> + ] +): ResponseInterface { + const _key = args[0] + const config = Object.assign( + {}, + defaultConfig, + useContext(SWRConfigContext), + args.length > 2 + ? args[2] + : args.length === 2 && typeof args[1] === 'object' + ? args[1] + : {} + ) + // in typescript args.length > 2 is not same as args.lenth === 3 + // we do a safe type assertion here + // args.length === 3 + const fn = (args.length > 2 + ? args[1] + : args.length === 2 && typeof args[1] === 'function' + ? args[1] + : /** + pass fn as null will disable revalidate + https://paco.sh/blog/shared-hook-state-with-swr + */ + args[1] === null + ? args[1] + : config.fetcher) as fetcherFn | null // we assume `key` as the identifier of the request // `key` can change but `fn` shouldn't @@ -249,20 +257,11 @@ function useSWR( // `keyErr` is the cache key for error objects const [key, fnArgs, keyErr, keyValidating] = cache.serializeKey(_key) - const config: ConfigInterface = Object.assign( - {}, - defaultConfig, - useContext(SWRConfigContext), - _config - ) - const configRef = useRef(config) useIsomorphicLayoutEffect(() => { configRef.current = config }) - const fn = typeof _fn !== 'undefined' ? _fn : config.fetcher - const resolveData = () => { const cachedData = cache.get(key) return typeof cachedData === 'undefined' ? config.initialData : cachedData @@ -289,16 +288,19 @@ function useSWR( // display the data label in the React DevTools next to SWR hooks useDebugValue(stateRef.current.data) - const [, rerender] = useState(null) + const rerender = useState(null)[1] + let dispatch = useCallback( (payload: actionType) => { let shouldUpdateState = false for (let k in payload) { + // @ts-ignore if (stateRef.current[k] === payload[k]) { continue } - + // @ts-ignore stateRef.current[k] = payload[k] + // @ts-ignore if (stateDependencies.current[k]) { shouldUpdateState = true } @@ -329,19 +331,23 @@ function useSWR( if (unmountedRef.current) return if (!initialMountedRef.current) return if (key !== keyRef.current) return + // @ts-ignore configRef.current[event](...params) }, [key] ) - const boundMutate: responseInterface['mutate'] = useCallback( + const boundMutate: ResponseInterface['mutate'] = useCallback( (data, shouldRevalidate) => { return mutate(keyRef.current, data, shouldRevalidate) }, [] ) - const addRevalidator = (revalidators, callback) => { + const addRevalidator = ( + revalidators: Record, + callback: revalidatorInterface + ) => { if (!callback) return if (!revalidators[key]) { revalidators[key] = [callback] @@ -350,7 +356,10 @@ function useSWR( } } - const removeRevalidator = (revlidators, callback) => { + const removeRevalidator = ( + revlidators: Record, + callback: revalidatorInterface + ) => { if (revlidators[key]) { const revalidators = revlidators[key] const index = revalidators.indexOf(callback) @@ -371,11 +380,11 @@ function useSWR( if (!key || !fn) return false if (unmountedRef.current) return false if (configRef.current.isPaused()) return false - revalidateOpts = Object.assign({ dedupe: false }, revalidateOpts) + const { retryCount = 0, dedupe = false } = revalidateOpts let loading = true let shouldDeduping = - typeof CONCURRENT_PROMISES[key] !== 'undefined' && revalidateOpts.dedupe + typeof CONCURRENT_PROMISES[key] !== 'undefined' && dedupe // start fetching try { @@ -521,15 +530,10 @@ function useSWR( eventsCallback('onError', err, key, config) if (config.shouldRetryOnError) { // when retrying, we always enable deduping - const retryCount = (revalidateOpts.retryCount || 0) + 1 - eventsCallback( - 'onErrorRetry', - err, - key, - config, - revalidate, - Object.assign({ dedupe: true }, revalidateOpts, { retryCount }) - ) + eventsCallback('onErrorRetry', err, key, config, revalidate, { + retryCount: retryCount + 1, + dedupe: true + }) } } @@ -584,6 +588,8 @@ function useSWR( if (typeof latestKeyedData !== 'undefined' && !IS_SERVER) { // delay revalidate if there's cache // to not block the rendering + + //@ts-ignore it's safe to use requestAnimationFrame in browser rAF(softRevalidate) } else { softRevalidate() @@ -674,7 +680,7 @@ function useSWR( }, [key, revalidate]) useIsomorphicLayoutEffect(() => { - let timer = null + let timer: any = null const tick = async () => { if ( !stateRef.current.error && @@ -709,8 +715,8 @@ function useSWR( ]) // suspense - let latestData - let latestError + let latestData: Data | undefined + let latestError: unknown if (config.suspense) { // in suspense mode, we can't return empty state // (it should be suspended) @@ -762,7 +768,7 @@ function useSWR( // revalidate will be deprecated in the 1.x release // because mutate() covers the same use case of revalidate(). // This remains only for backward compatibility - const state = { revalidate, mutate: boundMutate } as responseInterface< + const state = { revalidate, mutate: boundMutate } as ResponseInterface< Data, Error > diff --git a/tsconfig.json b/tsconfig.json index 55b7c7fec..19a4326f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "types": [ "node", "jest" ], + "strict": true, "target": "es5", "typeRoots": [ "./types", From 26d945f4fd771750724a94d8b48ed028e348095b Mon Sep 17 00:00:00 2001 From: promer94 Date: Fri, 5 Mar 2021 16:26:08 +0800 Subject: [PATCH 2/4] disbale strict in test --- jest.config.js | 4 +++- tsconfig.json | 22 ++++------------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/jest.config.js b/jest.config.js index f7c06411a..d45404e17 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,9 @@ module.exports = { setupFilesAfterEnv: ['/jest-setup.ts'], globals: { 'ts-jest': { - diagnostics: false + tsconfig: { + strict: false + } } } } diff --git a/tsconfig.json b/tsconfig.json index 19a4326f0..a70776aff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,7 @@ "declaration": true, "esModuleInterop": true, "jsx": "react", - "lib": [ - "esnext", "dom" - ], + "lib": ["esnext", "dom"], "module": "commonjs", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, @@ -13,27 +11,15 @@ "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "./dist", - "types": [ - "node", "jest" - ], + "types": ["node", "jest"], "strict": true, "target": "es5", - "typeRoots": [ - "./types", - "./node_modules/@types" - ] + "typeRoots": ["./types", "./node_modules/@types"] }, - "include": [ - "src/**/*", - "./jest-setup.ts" - ], + "include": ["src/**/*", "./jest-setup.ts"], "watchOptions": { - // Use native file system events for files and directories "watchFile": "useFsEvents", "watchDirectory": "useFsEvents", - - // Poll files for updates more frequently - // when they're updated a lot. "fallbackPolling": "dynamicPriority" } } From f95a338ceb00453348e715ee753b2fd17a76771a Mon Sep 17 00:00:00 2001 From: Yixuan Xu Date: Mon, 8 Mar 2021 22:27:17 +0800 Subject: [PATCH 3/4] merge master --- src/cache.ts | 18 ++-- src/index.ts | 30 +++++-- src/types.ts | 180 ++++++++++++++++++++++++++++++++-------- src/use-swr-infinite.ts | 54 ++++-------- src/use-swr.ts | 87 +++++++++---------- 5 files changed, 228 insertions(+), 141 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 027ab1b03..8561f5af8 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,21 +1,21 @@ -import { CacheInterface, keyInterface, cacheListener } from './types' +import { Cache as CacheType, Key, CacheListener } from './types' import hash from './libs/hash' -export default class Cache implements CacheInterface { +export default class Cache implements CacheType { private __cache: Map - private __listeners: cacheListener[] + private __listeners: CacheListener[] constructor(initialData: any = {}) { this.__cache = new Map(Object.entries(initialData)) this.__listeners = [] } - get(key: keyInterface): any { + get(key: Key): any { const [_key] = this.serializeKey(key) return this.__cache.get(_key) } - set(key: keyInterface, value: any): any { + set(key: Key, value: any): any { const [_key] = this.serializeKey(key) this.__cache.set(_key, value) this.notify() @@ -25,7 +25,7 @@ export default class Cache implements CacheInterface { return Array.from(this.__cache.keys()) } - has(key: keyInterface) { + has(key: Key) { const [_key] = this.serializeKey(key) return this.__cache.has(_key) } @@ -35,14 +35,14 @@ export default class Cache implements CacheInterface { this.notify() } - delete(key: keyInterface) { + delete(key: Key) { const [_key] = this.serializeKey(key) this.__cache.delete(_key) this.notify() } // TODO: introduce namespace for the cache - serializeKey(key: keyInterface): [string, any, string, string] { + serializeKey(key: Key): [string, any, string, string] { let args = null if (typeof key === 'function') { try { @@ -68,7 +68,7 @@ export default class Cache implements CacheInterface { return [key, args, errorKey, isValidatingKey] } - subscribe(listener: cacheListener) { + subscribe(listener: CacheListener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } diff --git a/src/index.ts b/src/index.ts index 9b6f62c11..fa36ab725 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,31 @@ -export * from './use-swr' +// `useSWR` and related APIs import { default as useSWR } from './use-swr' -export { - useSWRInfinite, - SWRInfiniteConfigInterface, - SWRInfiniteResponseInterface -} from './use-swr-infinite' +export default useSWR +export * from './use-swr' + +// `useSWRInfinite` +export { useSWRInfinite } from './use-swr-infinite' + +// Cache related, to be replaced by the new APIs export { cache } from './config' + +// Types export { + SWRConfiguration, + SWRInfiniteConfiguration, + SWRInfiniteResponse, + Revalidator, + RevalidatorOptions, + Key, + SWRResponse, + Cache, + // Legacy, for backwards compatibility ConfigInterface, + SWRInfiniteConfigInterface, + SWRInfiniteResponseInterface, revalidateType, RevalidateOptionInterface, keyInterface, - ResponseInterface as responseInterface, + responseInterface, CacheInterface } from './types' -export default useSWR diff --git a/src/types.ts b/src/types.ts index 541d825b7..7f758ddd3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,8 @@ -export type fetcherFn = (...args: any) => Data | Promise -export interface ConfigInterface< +export type Fetcher = (...args: any) => Data | Promise +export interface Configuration< Data = any, Error = any, - Fn extends fetcherFn = fetcherFn + Fn extends Fetcher = Fetcher > { errorRetryInterval: number errorRetryCount?: number @@ -25,22 +25,22 @@ export interface ConfigInterface< isPaused: () => boolean onLoadingSlow: ( key: string, - config: Readonly>> + config: Readonly>> ) => void onSuccess: ( data: Data, key: string, - config: Readonly>> + config: Readonly>> ) => void onError: ( err: Error, key: string, - config: Readonly>> + config: Readonly>> ) => void onErrorRetry: ( err: Error, key: string, - config: Readonly>>, + config: Readonly>>, revalidate: revalidateType, revalidateOpts: Required ) => void @@ -55,63 +55,171 @@ export interface RevalidateOptionInterface { dedupe?: boolean } -export type keyType = string | any[] | null -type keyFunction = () => keyType -export type keyInterface = keyFunction | keyType -export type updaterInterface = ( +export type ValueKey = string | any[] | null + +export type Updater = ( shouldRevalidate?: boolean, data?: Data, error?: Error, shouldDedupe?: boolean, dedupe?: boolean ) => boolean | Promise -export type triggerInterface = ( - key: keyInterface, - shouldRevalidate?: boolean -) => Promise -export type mutateCallback = ( +export type Trigger = (key: Key, shouldRevalidate?: boolean) => Promise + +export type MutatorCallback = ( currentValue: undefined | Data ) => Promise | undefined | Data -export type mutateInterface = ( - key: keyInterface, - data?: Data | Promise | mutateCallback, + +export type Mutator = ( + key: Key, + data?: Data | Promise | MutatorCallback, shouldRevalidate?: boolean ) => Promise -export type broadcastStateInterface = ( +export type Broadcaster = ( key: string, data: Data, error?: Error, isValidating?: boolean ) => void -export interface ResponseInterface { + +export type Action = { + data?: Data + error?: Error + isValidating?: boolean +} + +export type CacheListener = () => void + +// Public types + +/** + * @deprecated `ConfigInterface` will be renamed to `SWRConfiguration`. + */ +export type ConfigInterface< + Data = any, + Error = any, + Fn extends Fetcher = Fetcher +> = Partial> +export type SWRConfiguration< + Data = any, + Error = any, + Fn extends Fetcher = Fetcher +> = Partial> + +/** + * @deprecated `keyInterface` will be renamed to `Key`. + */ +export type keyInterface = ValueKey | (() => ValueKey) +export type Key = ValueKey | (() => ValueKey) + +/** + * @deprecated `responseInterface` will be renamed to `SWRResponse`. + */ +export type responseInterface = { data?: Data error?: Error revalidate: () => Promise mutate: ( - data?: Data | Promise | mutateCallback, + data?: Data | Promise | MutatorCallback, shouldRevalidate?: boolean ) => Promise isValidating: boolean } -export type revalidateType = ( - revalidateOpts: RevalidateOptionInterface -) => Promise - -export type actionType = { +export type SWRResponse = { data?: Data error?: Error - isValidating?: boolean + revalidate: () => Promise + mutate: ( + data?: Data | Promise | MutatorCallback, + shouldRevalidate?: boolean + ) => Promise + isValidating: boolean } +/** + * @deprecated `SWRInfiniteConfigInterface` will be renamed to `SWRInfiniteConfiguration`. + */ +export type SWRInfiniteConfigInterface< + Data = any, + Error = any +> = SWRConfiguration> & { + initialSize?: number + revalidateAll?: boolean + persistSize?: boolean +} +export type SWRInfiniteConfiguration< + Data = any, + Error = any +> = SWRConfiguration> & { + initialSize?: number + revalidateAll?: boolean + persistSize?: boolean +} + +/** + * @deprecated `SWRInfiniteResponseInterface` will be renamed to `SWRInfiniteResponse`. + */ +export type SWRInfiniteResponseInterface = SWRResponse< + Data[], + Error +> & { + size: number + setSize: ( + size: number | ((size: number) => number) + ) => Promise +} +export type SWRInfiniteResponse = SWRResponse< + Data[], + Error +> & { + size: number + setSize: ( + size: number | ((size: number) => number) + ) => Promise +} + +/** + * @deprecated `RevalidateOptionInterface` will be renamed to `RevalidatorOptions`. + */ +export interface RevalidateOptionInterface { + retryCount?: number + dedupe?: boolean +} +export interface RevalidatorOptions { + retryCount?: number + dedupe?: boolean +} + +/** + * @deprecated `revalidateType` will be renamed to `Revalidator`. + */ +export type revalidateType = ( + revalidateOpts: RevalidatorOptions +) => Promise +export type Revalidator = ( + revalidateOpts: RevalidatorOptions +) => Promise + +/** + * @deprecated `CacheInterface` will be renamed to `Cache`. + */ export interface CacheInterface { - get(key: keyInterface): any - set(key: keyInterface, value: any): any + get(key: Key): any + set(key: Key, value: any): any keys(): string[] - has(key: keyInterface): boolean - delete(key: keyInterface): void + has(key: Key): boolean + delete(key: Key): void clear(): void - serializeKey(key: keyInterface): [string, any, string, string] - subscribe(listener: cacheListener): () => void + serializeKey(key: Key): [string, any, string, string] + subscribe(listener: CacheListener): () => void +} +export interface Cache { + get(key: Key): any + set(key: Key, value: any): any + keys(): string[] + has(key: Key): boolean + delete(key: Key): void + clear(): void + serializeKey(key: Key): [string, any, string, string] + subscribe(listener: CacheListener): () => void } - -export type cacheListener = () => void diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 041d2ae28..0bd15419e 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -1,3 +1,4 @@ +// TODO: use @ts-expect-error import { useContext, useRef, useState, useEffect, useCallback } from 'react' import defaultConfig, { cache } from './config' @@ -5,45 +6,29 @@ import SWRConfigContext from './swr-config-context' import useSWR from './use-swr' import { - keyType, - fetcherFn, - ConfigInterface, - ResponseInterface, - mutateCallback + ValueKey, + Fetcher, + SWRInfiniteConfiguration, + SWRInfiniteResponse, + MutatorCallback } from './types' type KeyLoader = ( index: number, previousPageData: Data | null -) => keyType -interface SWRInfiniteConfigInterface - extends ConfigInterface> { - initialSize?: number - revalidateAll?: boolean - persistSize?: boolean -} -interface SWRInfiniteResponseInterface - extends ResponseInterface { - size: number - setSize: ( - size: number | ((size: number) => number) - ) => Promise -} +) => ValueKey function useSWRInfinite( ...args: | readonly [KeyLoader] - | readonly [KeyLoader, fetcherFn] - | readonly [ - KeyLoader, - Partial> - ] + | readonly [KeyLoader, Fetcher] + | readonly [KeyLoader, Partial>] | readonly [ KeyLoader, - fetcherFn, - Partial> + Fetcher, + Partial> ] -): SWRInfiniteResponseInterface { +): SWRInfiniteResponse { const getKey = args[0] const config = Object.assign( @@ -63,7 +48,7 @@ function useSWRInfinite( ? args[1] : args.length === 2 && typeof args[1] === 'function' ? args[1] - : config.fetcher) as fetcherFn + : config.fetcher) as Fetcher const { initialSize = 1, @@ -182,10 +167,7 @@ function useSWRInfinite( }, [swr.data]) const mutate = useCallback( - ( - data: Data[] | Promise | mutateCallback | undefined, - shouldRevalidate = true - ) => { + (data: MutatorCallback, shouldRevalidate = true) => { if (shouldRevalidate && typeof data !== 'undefined') { // we only revalidate the pages that are changed const originalData = dataRef.current @@ -241,11 +223,7 @@ function useSWRInfinite( enumerable: true } }) - return swrInfinite as SWRInfiniteResponseInterface + return (swrInfinite as unknown) as SWRInfiniteResponse } -export { - useSWRInfinite, - SWRInfiniteConfigInterface, - SWRInfiniteResponseInterface -} +export { useSWRInfinite } diff --git a/src/use-swr.ts b/src/use-swr.ts index c176bb3a8..637bac3d5 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -1,3 +1,4 @@ +// TODO: use @ts-expect-error import { useCallback, useContext, @@ -12,16 +13,16 @@ import { import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' import { - actionType, - broadcastStateInterface, - ConfigInterface, - fetcherFn, - keyInterface, - mutateInterface, - ResponseInterface, - RevalidateOptionInterface, - triggerInterface, - updaterInterface + Action, + Broadcaster, + Fetcher, + Key, + Mutator, + SWRResponse, + RevalidatorOptions, + Trigger, + Updater, + SWRConfiguration } from './types' const IS_SERVER = @@ -39,14 +40,14 @@ const rAF = IS_SERVER // useLayoutEffect in the browser. const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect -type revalidatorInterface = (...args: any[]) => void +type Revalidator = (...args: any[]) => void // global state managers const CONCURRENT_PROMISES: Record = {} const CONCURRENT_PROMISES_TS: Record = {} -const FOCUS_REVALIDATORS: Record = {} -const RECONNECT_REVALIDATORS: Record = {} -const CACHE_REVALIDATORS: Record = {} +const FOCUS_REVALIDATORS: Record = {} +const RECONNECT_REVALIDATORS: Record = {} +const CACHE_REVALIDATORS: Record = {} const MUTATION_TS: Record = {} const MUTATION_END_TS: Record = {} @@ -58,7 +59,7 @@ const now = (() => { // setup DOM events listeners for `focus` and `reconnect` actions if (!IS_SERVER) { - const revalidate = (revalidators: Record) => { + const revalidate = (revalidators: Record) => { if (!defaultConfig.isDocumentVisible() || !defaultConfig.isOnline()) return for (const key in revalidators) { @@ -75,7 +76,7 @@ if (!IS_SERVER) { } } -const trigger: triggerInterface = (_key, shouldRevalidate = true) => { +const trigger: Trigger = (_key, shouldRevalidate = true) => { // we are ignoring the second argument which correspond to the arguments // the fetcher will receive when key is an array const [key, , keyErr, keyValidating] = cache.serializeKey(_key) @@ -105,12 +106,7 @@ const trigger: triggerInterface = (_key, shouldRevalidate = true) => { return Promise.resolve(cache.get(key)) } -const broadcastState: broadcastStateInterface = ( - key, - data, - error, - isValidating -) => { +const broadcastState: Broadcaster = (key, data, error, isValidating) => { const updaters = CACHE_REVALIDATORS[key] if (key && updaters) { for (let i = 0; i < updaters.length; ++i) { @@ -119,11 +115,7 @@ const broadcastState: broadcastStateInterface = ( } } -const mutate: mutateInterface = async ( - _key, - _data, - shouldRevalidate = true -) => { +const mutate: Mutator = async (_key, _data, shouldRevalidate = true) => { const [key, , keyErr] = cache.serializeKey(_key) if (!key) return @@ -216,15 +208,11 @@ const mutate: mutateInterface = async ( function useSWR( ...args: - | readonly [keyInterface] - | readonly [keyInterface, fetcherFn | null] - | readonly [keyInterface, Partial>] - | readonly [ - keyInterface, - fetcherFn | null, - Partial> - ] -): ResponseInterface { + | readonly [Key] + | readonly [Key, Fetcher | null] + | readonly [Key, SWRConfiguration] + | readonly [Key, Fetcher | null, SWRConfiguration] +): SWRResponse { const _key = args[0] const config = Object.assign( {}, @@ -236,6 +224,7 @@ function useSWR( ? args[1] : {} ) + // in typescript args.length > 2 is not same as args.lenth === 3 // we do a safe type assertion here // args.length === 3 @@ -249,7 +238,7 @@ function useSWR( */ args[1] === null ? args[1] - : config.fetcher) as fetcherFn | null + : config.fetcher) as Fetcher | null // we assume `key` as the identifier of the request // `key` can change but `fn` shouldn't @@ -302,7 +291,7 @@ function useSWR( const rerender = useState(null)[1] let dispatch = useCallback( - (payload: actionType) => { + (payload: Action) => { let shouldUpdateState = false for (let k in payload) { // @ts-ignore @@ -348,7 +337,7 @@ function useSWR( [key] ) - const boundMutate: ResponseInterface['mutate'] = useCallback( + const boundMutate: SWRResponse['mutate'] = useCallback( (data, shouldRevalidate) => { return mutate(keyRef.current, data, shouldRevalidate) }, @@ -356,8 +345,8 @@ function useSWR( ) const addRevalidator = ( - revalidators: Record, - callback: revalidatorInterface + revalidators: Record, + callback: Revalidator ) => { if (!callback) return if (!revalidators[key]) { @@ -368,8 +357,8 @@ function useSWR( } const removeRevalidator = ( - revlidators: Record, - callback: revalidatorInterface + revlidators: Record, + callback: Revalidator ) => { if (revlidators[key]) { const revalidators = revlidators[key] @@ -385,9 +374,7 @@ function useSWR( // start a revalidation const revalidate = useCallback( - async ( - revalidateOpts: RevalidateOptionInterface = {} - ): Promise => { + async (revalidateOpts: RevalidatorOptions = {}): Promise => { if (!key || !fn) return false if (unmountedRef.current) return false if (configRef.current.isPaused()) return false @@ -488,7 +475,7 @@ function useSWR( cache.set(keyValidating, false) // new state for the reducer - const newState: actionType = { + const newState: Action = { isValidating: false } @@ -622,7 +609,7 @@ function useSWR( } // register global cache update listener - const onUpdate: updaterInterface = ( + const onUpdate: Updater = ( shouldRevalidate = true, updatedData, updatedError, @@ -630,7 +617,7 @@ function useSWR( dedupe = true ) => { // update hook state - const newState: actionType = {} + const newState: Action = {} let needUpdate = false if ( @@ -776,7 +763,7 @@ function useSWR( // revalidate will be deprecated in the 1.x release // because mutate() covers the same use case of revalidate(). // This remains only for backward compatibility - const state = { revalidate, mutate: boundMutate } as ResponseInterface< + const state = { revalidate, mutate: boundMutate } as SWRResponse< Data, Error > From 512a3485c928b8dfe16f4b2e45d7cb5234021d17 Mon Sep 17 00:00:00 2001 From: promer94 Date: Tue, 9 Mar 2021 17:29:20 +0800 Subject: [PATCH 4/4] fix name --- src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index e12c90719..d4a60c9a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,8 +41,8 @@ export interface Configuration< err: Error, key: string, config: Readonly>>, - revalidate: revalidateType, - revalidateOpts: Required + revalidate: Revalidator, + revalidateOpts: Required ) => void registerOnFocus?: (cb: () => void) => void registerOnReconnect?: (cb: () => void) => void