Skip to content

Commit 9967505

Browse files
committed
feat: remove levels cache and built-in levels
No longer needed as the backend has been stable and fast for a while, and the frontend caching is quite problematic.
1 parent a034fbf commit 9967505

File tree

5 files changed

+9
-157
lines changed

5 files changed

+9
-157
lines changed

public/levels.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/App.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Effects } from 'components/Effects'
66
import { GlobalErrorBoundary } from './components/GlobalErrorBoundary'
77
import { FCC } from './types'
88
import { request } from './utils/fetcher'
9-
import { localStorageProvider, swrCacheMiddleware } from './utils/swr-cache'
109

1110
export const App: FCC = ({ children }) => {
1211
return (
@@ -15,11 +14,9 @@ export const App: FCC = ({ children }) => {
1514
<SWRConfig
1615
value={{
1716
fetcher: request,
18-
provider: localStorageProvider,
1917
focusThrottleInterval: 1000 * 60,
2018
errorRetryInterval: 1000 * 3,
2119
errorRetryCount: 3,
22-
use: [swrCacheMiddleware],
2320
}}
2421
>
2522
<GlobalErrorBoundary>

src/apis/arknights.ts

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type { Level } from 'models/operation'
66

77
import { withoutUnusedLevels } from '../models/level'
88
import { request } from '../utils/fetcher'
9-
import { useSWRCache } from '../utils/swr-cache'
109

1110
const ONE_DAY = 1000 * 60 * 60 * 24
1211

@@ -20,57 +19,18 @@ export const useLevels = ({ suspense }: { suspense?: boolean } = {}) => {
2019
const url = '/arknights/level'
2120
type LevelResponse = Response<Level[]>
2221

23-
useSWRCache(
24-
url,
25-
// discard the cache if the level data has no stageId
26-
({ data }) => {
27-
const firstLevel = (data as LevelResponse)?.data?.[0]
28-
return !!firstLevel && 'stageId' in firstLevel
29-
},
30-
)
31-
32-
const response = useSWR<LevelResponse>(url, {
22+
return useSWR<LevelResponse>(url, {
3323
focusThrottleInterval: ONE_DAY,
3424
dedupingInterval: ONE_DAY,
3525
suspense,
3626
fetcher: async (input: string, init?: RequestInit) => {
37-
let res: LevelResponse
38-
39-
try {
40-
res = await request<LevelResponse>(input, init)
41-
} catch (e) {
42-
// fallback to built-in levels while retaining the error
43-
res = await requestBuiltInLevels(init)
44-
;(res as any).__serverError = e
45-
}
27+
const res = await request<LevelResponse>(input, init)
4628

4729
res.data = withoutUnusedLevels(res.data)
4830

4931
return res
5032
},
5133
})
52-
53-
if ((response.data as any)?.__serverError) {
54-
return {
55-
...response,
56-
error: (response.data as any).__serverError,
57-
}
58-
}
59-
60-
return response
61-
}
62-
63-
const requestBuiltInLevels = async (
64-
init?: RequestInit,
65-
): Promise<Response<Level[]>> => {
66-
const res = await fetch('/levels.json', init)
67-
const data = await res.json()
68-
return {
69-
data,
70-
statusCode: 200,
71-
message: 'OK',
72-
traceId: '',
73-
}
7434
}
7535

7636
export const useOperators = () => {

src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ReactDOM from 'react-dom/client'
1111
import { Route, Routes } from 'react-router-dom'
1212

1313
import { ViewPage } from 'pages/view'
14+
import { clearOutdatedSwrCache } from 'utils/swr-cache'
1415

1516
import { App } from './App'
1617
import { AppLayout } from './layouts/AppLayout'
@@ -46,6 +47,8 @@ if (navigator.userAgent.includes('Win')) {
4647
document.documentElement.classList.add('platform--non-windows')
4748
}
4849

50+
clearOutdatedSwrCache()
51+
4952
ReactDOM.createRoot(document.getElementById('root')!).render(
5053
<React.StrictMode>
5154
<App>

src/utils/swr-cache.ts

Lines changed: 4 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,7 @@
1-
import { useEffect, useRef } from 'react'
2-
import { Middleware, SWRHook, State, useSWRConfig } from 'swr'
3-
4-
type SwrCache = [string, State][]
5-
61
const STORAGE_KEY = 'copilot-swr'
72

8-
const cachedKeys = new Set<string>()
9-
10-
// keys that have been validated (even if no validator is provided for it)
11-
const validatedKeys = new Set<string>()
12-
13-
/**
14-
* This hook should be used before the corresponding useSWR() call,
15-
* otherwise SWR will consider the fetched data stale and send a new request.
16-
*
17-
* @param key - **the key is not supposed to be dynamic.**
18-
* @param validate - validates the cached state, if it returns false, the state will be discarded.
19-
* This function will only run once for each key (because we want it to validate cached data, not fresh data).
20-
*/
21-
export function useSWRCache(
22-
key: string,
23-
validate?: (state: State<unknown>) => boolean,
24-
) {
25-
cachedKeys.add(key)
26-
27-
const { cache, mutate } = useSWRConfig()
28-
29-
const validated = useRef(false)
30-
31-
// Only run once. We cannot use useEffect() here because useSWR() fires request synchronously,
32-
// if we mutate() the state in useEffect(), SWR will think the fetched data is stale,
33-
// discard it, and attempt to fire a new request, which will be blocked if dedupingInterval is set.
34-
if (!validated.current) {
35-
validated.current = true
36-
37-
if (!validatedKeys.has(key)) {
38-
validatedKeys.add(key)
39-
40-
if (validate) {
41-
const state = cache.get(key)
42-
43-
if (state) {
44-
let isValid: boolean
45-
46-
try {
47-
isValid = validate(state)
48-
} catch (e) {
49-
isValid = false
50-
console.warn(e)
51-
}
52-
53-
if (!isValid) {
54-
console.log('[SWR cache]: invalid cache, discarding:', key)
55-
56-
mutate(key, undefined, { revalidate: false })
57-
}
58-
}
59-
}
60-
}
61-
}
62-
}
63-
64-
/**
65-
* The only thing this middleware does is to borrow the <SWRConfig>'s lifecycle
66-
* and clear validatedKeys when it unmounts (happens during hot reload).
67-
* It should be used in the topmost <SWRConfig>.
68-
*/
69-
export const swrCacheMiddleware: Middleware =
70-
(useSWRNext: SWRHook) => (key, fetcher, config) => {
71-
useEffect(
72-
() => () => {
73-
validatedKeys.clear()
74-
75-
// however, do not clear cachedKeys!!
76-
// because this unmount callback also runs before the page unloads,
77-
// and if there's no keys, no cache will be persisted.
78-
},
79-
[],
80-
)
81-
82-
return useSWRNext(key, fetcher, config)
83-
}
84-
85-
// https://swr.vercel.app/docs/advanced/cache#localstorage-based-persistent-cache
86-
export function localStorageProvider() {
87-
const map = new Map<string, any>(
88-
JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'),
89-
)
90-
91-
window.addEventListener('beforeunload', () => {
92-
const cache = Object.fromEntries(
93-
JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') as SwrCache,
94-
)
95-
96-
// overwrite cache with map entries
97-
Array.from(map.entries())
98-
.filter(
99-
([key, state]) => cachedKeys.has(key) && state.data && !state.error,
100-
)
101-
.forEach(([key, state]) => (cache[key] = state))
102-
103-
// remove entries that are no longer needed
104-
Object.keys(cache).forEach((key) => {
105-
if (!cachedKeys.has(key)) {
106-
delete cache[key]
107-
}
108-
})
109-
110-
localStorage.setItem(STORAGE_KEY, JSON.stringify(Object.entries(cache)))
111-
})
112-
113-
return map
3+
// 清理旧版本留下的缓存数据
4+
// TODO: 等大部分用户都升级到新版本,就删除这个函数,大概在三个月之后?
5+
export function clearOutdatedSwrCache() {
6+
localStorage.removeItem(STORAGE_KEY)
1147
}

0 commit comments

Comments
 (0)