Skip to content

Commit d14e1b8

Browse files
shudinghuozhi
andauthored
Code structure refactoring (#1303)
* refactor cache code structure * simply mutate * rearrange assignments * build all packages * use global state type guard and Revalidator type Co-authored-by: Jiachi Liu <[email protected]>
1 parent 7afe29b commit d14e1b8

File tree

5 files changed

+108
-122
lines changed

5 files changed

+108
-122
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- run: yarn install
2121
- run: yarn types:check
2222
- run: yarn lint
23-
- run: yarn build:core
23+
- run: yarn build
2424
- run: yarn test
2525
env:
2626
CI: true

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ export interface RevalidatorOptions {
168168
}
169169

170170
export type Revalidator = (
171-
revalidateOpts: RevalidatorOptions
172-
) => Promise<boolean>
171+
revalidateOpts?: RevalidatorOptions
172+
) => Promise<boolean> | void
173173

174174
export interface Cache<Data = any> {
175175
get(key: Key): Data | null | undefined

src/use-swr.ts

Lines changed: 56 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useCallback, useRef, useDebugValue } from 'react'
22
import defaultConfig from './utils/config'
3-
import { provider as defaultProvider } from './utils/web-preset'
4-
import { wrapCache } from './utils/cache'
3+
import { wrapCache, SWRGlobalState, GlobalState } from './utils/cache'
54
import { IS_SERVER, rAF, useIsomorphicLayoutEffect } from './utils/env'
65
import { serialize } from './utils/serialize'
76
import { isUndefined, UNDEFINED } from './utils/helper'
@@ -22,48 +21,13 @@ import {
2221
Cache,
2322
ScopedMutator,
2423
SWRHook,
24+
Revalidator,
2525
ProviderOptions
2626
} from './types'
2727

28-
type Revalidator = (...args: any[]) => void
29-
3028
// Generate strictly increasing timestamps.
3129
let __timestamp = 0
3230

33-
// Global state used to deduplicate requests and store listeners
34-
const SWRGlobalState = new WeakMap<Cache, any>()
35-
const getGlobalState = (cache: Cache) => {
36-
if (!SWRGlobalState.has(cache)) {
37-
SWRGlobalState.set(cache, [{}, {}, {}, {}, {}, {}, {}])
38-
}
39-
return SWRGlobalState.get(cache) as [
40-
Record<string, Revalidator[]>, // FOCUS_REVALIDATORS
41-
Record<string, Revalidator[]>, // RECONNECT_REVALIDATORS
42-
Record<string, Updater[]>, // CACHE_REVALIDATORS
43-
Record<string, number>, // MUTATION_TS
44-
Record<string, number>, // MUTATION_END_TS
45-
Record<string, any>, // CONCURRENT_PROMISES
46-
Record<string, number> // CONCURRENT_PROMISES_TS
47-
]
48-
}
49-
50-
function setupGlobalEvents(cache: Cache, _opts: Partial<ProviderOptions> = {}) {
51-
if (IS_SERVER) return
52-
const opts = { ...defaultProvider, ..._opts }
53-
const [FOCUS_REVALIDATORS, RECONNECT_REVALIDATORS] = getGlobalState(cache)
54-
const revalidate = (revalidators: Record<string, Revalidator[]>) => {
55-
for (const key in revalidators) {
56-
if (revalidators[key][0]) revalidators[key][0]()
57-
}
58-
}
59-
60-
opts.setupOnFocus(() => revalidate(FOCUS_REVALIDATORS))
61-
opts.setupOnReconnect(() => revalidate(RECONNECT_REVALIDATORS))
62-
}
63-
64-
// Setup DOM events listeners for `focus` and `reconnect` actions
65-
setupGlobalEvents(defaultConfig.cache)
66-
6731
const broadcastState: Broadcaster = (
6832
cache: Cache,
6933
key,
@@ -72,7 +36,7 @@ const broadcastState: Broadcaster = (
7236
isValidating,
7337
shouldRevalidate = false
7438
) => {
75-
const [, , CACHE_REVALIDATORS] = getGlobalState(cache)
39+
const [, , CACHE_REVALIDATORS] = SWRGlobalState.get(cache) as GlobalState
7640
const updaters = CACHE_REVALIDATORS[key]
7741
const promises = []
7842
if (updaters) {
@@ -94,7 +58,9 @@ async function internalMutate<Data = any>(
9458
const [key, , keyErr] = serialize(_key)
9559
if (!key) return UNDEFINED
9660

97-
const [, , , MUTATION_TS, MUTATION_END_TS] = getGlobalState(cache)
61+
const [, , , MUTATION_TS, MUTATION_END_TS] = SWRGlobalState.get(
62+
cache
63+
) as GlobalState
9864

9965
// if there is no new data to update, let's just revalidate the key
10066
if (isUndefined(_data)) {
@@ -108,18 +74,14 @@ async function internalMutate<Data = any>(
10874
)
10975
}
11076

111-
// update global timestamps
112-
MUTATION_TS[key] = ++__timestamp
113-
MUTATION_END_TS[key] = 0
114-
115-
// track timestamps before await asynchronously
116-
const beforeMutationTs = MUTATION_TS[key]
117-
11877
let data: any, error: unknown
119-
let isAsyncMutation = false
78+
79+
// Update global timestamps.
80+
const beforeMutationTs = (MUTATION_TS[key] = ++__timestamp)
81+
MUTATION_END_TS[key] = 0
12082

12183
if (typeof _data === 'function') {
122-
// `_data` is a function, call it passing current cache value
84+
// `_data` is a function, call it passing current cache value.
12385
try {
12486
_data = (_data as MutatorCallback<Data>)(cache.get(key))
12587
} catch (err) {
@@ -130,8 +92,7 @@ async function internalMutate<Data = any>(
13092
}
13193

13294
if (_data && typeof (_data as Promise<Data>).then === 'function') {
133-
// `_data` is a promise
134-
isAsyncMutation = true
95+
// `_data` is a promise/thenable, resolve the final data.
13596
try {
13697
data = await _data
13798
} catch (err) {
@@ -141,32 +102,25 @@ async function internalMutate<Data = any>(
141102
data = _data
142103
}
143104

144-
const shouldAbort = (): boolean | void => {
145-
// check if other mutations have occurred since we've started this mutation
146-
if (beforeMutationTs !== MUTATION_TS[key]) {
147-
if (error) throw error
148-
return true
149-
}
150-
}
105+
// Check if other mutations have occurred since we've started this mutation.
106+
const shouldAbort = beforeMutationTs !== MUTATION_TS[key]
151107

152-
// If there's a race we don't update cache or broadcast change, just return the data
153-
if (shouldAbort()) return data
108+
// If there's a race we don't update cache or broadcast change, just return the data.
109+
if (shouldAbort) {
110+
if (error) throw error
111+
return data
112+
}
154113

155114
if (!isUndefined(data)) {
156115
// update cached data
157116
cache.set(key, data)
158117
}
159-
// Always update or reset the error
118+
// Always update or reset the error.
160119
cache.set(keyErr, error)
161120

162121
// Reset the timestamp to mark the mutation has ended
163122
MUTATION_END_TS[key] = ++__timestamp
164123

165-
if (!isAsyncMutation) {
166-
// We skip broadcasting if there's another mutation happened synchronously
167-
if (shouldAbort()) return data
168-
}
169-
170124
// Update existing SWR Hooks' internal states:
171125
return broadcastState(
172126
cache,
@@ -185,9 +139,9 @@ async function internalMutate<Data = any>(
185139
// Add a callback function to a list of keyed revalidation functions and returns
186140
// the unregister function.
187141
const addRevalidator = (
188-
revalidators: Record<string, Revalidator[]>,
142+
revalidators: Record<string, (Revalidator | Updater<any>)[]>,
189143
key: string,
190-
callback: Revalidator
144+
callback: Revalidator | Updater<any>
191145
) => {
192146
if (!revalidators[key]) {
193147
revalidators[key] = [callback]
@@ -223,6 +177,7 @@ export function useSWRHandler<Data = any, Error = any>(
223177
refreshWhenHidden,
224178
refreshWhenOffline
225179
} = config
180+
226181
const [
227182
FOCUS_REVALIDATORS,
228183
RECONNECT_REVALIDATORS,
@@ -231,7 +186,7 @@ export function useSWRHandler<Data = any, Error = any>(
231186
MUTATION_END_TS,
232187
CONCURRENT_PROMISES,
233188
CONCURRENT_PROMISES_TS
234-
] = getGlobalState(cache)
189+
] = SWRGlobalState.get(cache) as GlobalState
235190

236191
// `key` is the identifier of the SWR `data` state.
237192
// `keyErr` and `keyValidating` are identifiers of `error` and `isValidating`
@@ -248,11 +203,8 @@ export function useSWRHandler<Data = any, Error = any>(
248203
const configRef = useRef(config)
249204

250205
// Get the current state that SWR should return.
251-
const resolveData = () => {
252-
const cachedData = cache.get(key)
253-
return isUndefined(cachedData) ? initialData : cachedData
254-
}
255-
const data = resolveData()
206+
const cachedData = cache.get(key)
207+
const data = isUndefined(cachedData) ? initialData : cachedData
256208
const error = cache.get(keyErr)
257209

258210
// A revalidation must be triggered when mounted if:
@@ -278,10 +230,7 @@ export function useSWRHandler<Data = any, Error = any>(
278230
}
279231
const isValidating = resolveValidating()
280232

281-
const [stateRef, stateDependenciesRef, setState] = useStateWithDeps<
282-
Data,
283-
Error
284-
>(
233+
const [stateRef, stateDependencies, setState] = useStateWithDeps<Data, Error>(
285234
{
286235
data,
287236
error,
@@ -293,14 +242,15 @@ export function useSWRHandler<Data = any, Error = any>(
293242
// The revalidation function is a carefully crafted wrapper of the original
294243
// `fetcher`, to correctly handle the many edge cases.
295244
const revalidate = useCallback(
296-
async (revalidateOpts: RevalidatorOptions = {}): Promise<boolean> => {
245+
async (revalidateOpts?: RevalidatorOptions): Promise<boolean> => {
297246
if (!key || !fn || unmountedRef.current || configRef.current.isPaused()) {
298247
return false
299248
}
300249

301-
const { retryCount, dedupe } = revalidateOpts
302-
250+
let newData: Data
251+
let startAt: number
303252
let loading = true
253+
const { retryCount, dedupe } = revalidateOpts || {}
304254
const shouldDeduping = !isUndefined(CONCURRENT_PROMISES[key]) && dedupe
305255

306256
// Do unmount check for callbacks:
@@ -329,9 +279,6 @@ export function useSWRHandler<Data = any, Error = any>(
329279
)
330280
}
331281

332-
let newData: Data
333-
let startAt: number
334-
335282
if (shouldDeduping) {
336283
// There's already an ongoing request, this one needs to be
337284
// deduplicated.
@@ -362,8 +309,9 @@ export function useSWRHandler<Data = any, Error = any>(
362309

363310
// trigger the success event,
364311
// only do this for the original request.
365-
if (isCallbackSafe())
312+
if (isCallbackSafe()) {
366313
configRef.current.onSuccess(newData, key, config)
314+
}
367315
}
368316

369317
// if there're other ongoing request(s), started after the current one,
@@ -486,6 +434,16 @@ export function useSWRHandler<Data = any, Error = any>(
486434
[key]
487435
)
488436

437+
// `mutate`, but bound to the current key.
438+
const boundMutate: SWRResponse<Data, Error>['mutate'] = useCallback(
439+
(newData, shouldRevalidate) => {
440+
return internalMutate(cache, keyRef.current, newData, shouldRevalidate)
441+
},
442+
// `cache` isn't allowed to change during the lifecycle
443+
// eslint-disable-next-line react-hooks/exhaustive-deps
444+
[]
445+
)
446+
489447
// Always update config.
490448
useIsomorphicLayoutEffect(() => {
491449
configRef.current = config
@@ -525,13 +483,13 @@ export function useSWRHandler<Data = any, Error = any>(
525483
}
526484
}
527485

528-
const isVisible = () =>
486+
const isActive = () =>
529487
configRef.current.isDocumentVisible() && configRef.current.isOnline()
530488

531489
// Add event listeners.
532490
let pending = false
533491
const onFocus = () => {
534-
if (configRef.current.revalidateOnFocus && !pending && isVisible()) {
492+
if (configRef.current.revalidateOnFocus && !pending && isActive()) {
535493
pending = true
536494
softRevalidate()
537495
setTimeout(
@@ -541,8 +499,8 @@ export function useSWRHandler<Data = any, Error = any>(
541499
}
542500
}
543501

544-
const onReconnect = () => {
545-
if (configRef.current.revalidateOnReconnect && isVisible()) {
502+
const onReconnect: Revalidator = () => {
503+
if (configRef.current.revalidateOnReconnect && isActive()) {
546504
softRevalidate()
547505
}
548506
}
@@ -625,6 +583,9 @@ export function useSWRHandler<Data = any, Error = any>(
625583
}
626584
}, [refreshInterval, refreshWhenHidden, refreshWhenOffline, revalidate])
627585

586+
// Display debug info in React DevTools.
587+
useDebugValue(data)
588+
628589
// In Suspense mode, we can't return the empty `data` state.
629590
// If there is `error`, the `error` needs to be thrown to the error boundary.
630591
// If there is no `error`, the `revalidation` promise needs to be thrown to
@@ -633,21 +594,6 @@ export function useSWRHandler<Data = any, Error = any>(
633594
throw isUndefined(error) ? revalidate({ dedupe: true }) : error
634595
}
635596

636-
// `mutate`, but bound to the current key.
637-
const boundMutate: SWRResponse<Data, Error>['mutate'] = useCallback(
638-
(newData, shouldRevalidate) => {
639-
return internalMutate(cache, keyRef.current, newData, shouldRevalidate)
640-
},
641-
// `cache` isn't allowed to change during the lifecycle
642-
// eslint-disable-next-line react-hooks/exhaustive-deps
643-
[]
644-
)
645-
646-
// Display debug info in React DevTools.
647-
useDebugValue(data)
648-
649-
const currentStateDependencies = stateDependenciesRef.current
650-
651597
// Define the SWR state.
652598
// `revalidate` will be deprecated in the 1.x release
653599
// because `mutate()` covers the same use case of `revalidate()`.
@@ -660,21 +606,21 @@ export function useSWRHandler<Data = any, Error = any>(
660606
{
661607
data: {
662608
get: function() {
663-
currentStateDependencies.data = true
609+
stateDependencies.data = true
664610
return data
665611
},
666612
enumerable: true
667613
},
668614
error: {
669615
get: function() {
670-
currentStateDependencies.error = true
616+
stateDependencies.error = true
671617
return error
672618
},
673619
enumerable: true
674620
},
675621
isValidating: {
676622
get: function() {
677-
currentStateDependencies.isValidating = true
623+
stateDependencies.isValidating = true
678624
return isValidating
679625
},
680626
enumerable: true
@@ -690,7 +636,7 @@ export const SWRConfig = Object.defineProperty(ConfigProvider, 'default', {
690636
}
691637

692638
export const mutate = internalMutate.bind(
693-
null,
639+
UNDEFINED,
694640
defaultConfig.cache
695641
) as ScopedMutator
696642

@@ -701,11 +647,10 @@ export function createCache<Data>(
701647
cache: Cache
702648
mutate: ScopedMutator<Data>
703649
} {
704-
const cache = wrapCache<Data>(provider)
705-
setupGlobalEvents(cache, options)
650+
const cache = wrapCache<Data>(provider, options)
706651
return {
707652
cache,
708-
mutate: internalMutate.bind(null, cache) as ScopedMutator<Data>
653+
mutate: internalMutate.bind(UNDEFINED, cache) as ScopedMutator<Data>
709654
}
710655
}
711656

0 commit comments

Comments
 (0)