diff --git a/shared/components/Provider.tsx b/shared/components/Provider.tsx new file mode 100644 index 00000000..fb8fd7a6 --- /dev/null +++ b/shared/components/Provider.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; + +const queryClient = new QueryClient(); + +export const PawProvider: React.FC = React.memo((props) => { + return ( + + {props.children} + + ); +}); +PawProvider.displayName = 'PawProvider'; diff --git a/shared/hooks/useCache.ts b/shared/hooks/useCache.ts new file mode 100644 index 00000000..38d23614 --- /dev/null +++ b/shared/hooks/useCache.ts @@ -0,0 +1,17 @@ +import { useQuery } from 'react-query'; +import { fetchUserInfo, UserBaseInfo } from '../model/user'; + +function buildCacheFactory( + scope: string, + fetcher: (id: string) => Promise +) { + return (id: string): T | Record => { + const { data } = useQuery([scope, id], () => fetcher(id)); + return data ?? {}; + }; +} + +export const useUserInfo = buildCacheFactory( + 'user', + fetchUserInfo +); diff --git a/shared/index.tsx b/shared/index.tsx index 092d3966..41d761ce 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -14,6 +14,7 @@ export type { export { regField } from './components/FastForm/field'; export { regFormContainer } from './components/FastForm/container'; export type { FastFormContainerComponent } from './components/FastForm/container'; +export { PawProvider } from './components/Provider'; // i18n export { t, setLanguage, useTranslation } from './i18n'; @@ -22,6 +23,7 @@ export { t, setLanguage, useTranslation } from './i18n'; export { useAsync } from './hooks/useAsync'; export { useAsyncFn } from './hooks/useAsyncFn'; export { useAsyncRequest } from './hooks/useAsyncRequest'; +export { useUserInfo } from './hooks/useCache'; export { useMountedState } from './hooks/useMountedState'; export { useRafState } from './hooks/useRafState'; @@ -54,10 +56,6 @@ export { // redux export { useAppSelector, useAppDispatch } from './redux/hooks/useAppSelector'; -export { - useCachedUserInfo, - useCachedUserInfoList, -} from './redux/hooks/useReduxCache'; export { userActions } from './redux/slices'; export { setupRedux } from './redux/setup'; export { createStore } from './redux/store'; diff --git a/shared/package.json b/shared/package.json index 9e4acd29..723679df 100644 --- a/shared/package.json +++ b/shared/package.json @@ -16,6 +16,7 @@ "lodash": "^4.17.21", "react-i18next": "^11.11.0", "react-native-storage": "npm:@trpgengine/react-native-storage@^1.0.1", + "react-query": "^3.18.1", "react-redux": "^7.2.4", "redux": "^4.1.0", "str2int": "^1.0.0", diff --git a/shared/redux/hooks/useReduxCache.ts b/shared/redux/hooks/useReduxCache.ts deleted file mode 100644 index b4705071..00000000 --- a/shared/redux/hooks/useReduxCache.ts +++ /dev/null @@ -1,141 +0,0 @@ -import _get from 'lodash/get'; -import _set from 'lodash/set'; -import _isNil from 'lodash/isNil'; -import { useEffect, useMemo, useRef } from 'react'; -import { fetchUserInfo, UserBaseInfo } from '../../model/user'; -import { cacheActions } from '../slices'; -import type { CacheKey } from '../slices/cache'; -import { useAppDispatch, useAppSelector } from './useAppSelector'; - -// 检查是否需要跳过处理 -const isSkipId = (id: string) => - _isNil(id) || - id === '' || - typeof id !== 'string' || - id.toString().startsWith('_'); - -interface CacheHookOptions { - forceFetch?: boolean; -} - -type GetCacheDataFn = (id: string) => Promise; - -function reduxHookCacheFactory( - cacheScope: CacheKey, - getCacheData: GetCacheDataFn -) { - const isFetchingDataIdQueue: string[] = []; // 正在请求的id列表 - - return function useReduxCache( - id: string, - options?: CacheHookOptions - ): Partial { - const data = useAppSelector( - (state) => _get(state, ['cache', cacheScope, id]) as any - ); - const dispatch = useAppDispatch(); - const forceFetchRef = useRef(options?.forceFetch ?? false); - - useEffect(() => { - if ((_isNil(data) || forceFetchRef.current === true) && !isSkipId(id)) { - // 如果没有数据或设置了强制重新获取 且 不是内置的UUID - // 从服务端获取缓存信息 - if (isFetchingDataIdQueue.indexOf(id) === -1) { - // 没有正在获取缓存信息 - console.log(`缓存[${cacheScope}: ${id}]不存在, 自动获取`); - - getCacheData(id).then((data) => { - // 从列表中移除 - const index = isFetchingDataIdQueue.indexOf(id); - if (index !== -1) { - isFetchingDataIdQueue.splice(index, 1); - } - forceFetchRef.current = false; // 不论怎么样都置为false 表示已经获取过了 - - dispatch( - cacheActions.setCache({ - scope: cacheScope, - id, - data, - }) - ); - }); - - isFetchingDataIdQueue.push(id); - } - } - }, [data]); - - return data ?? {}; - }; -} - -export const useCachedUserInfo = reduxHookCacheFactory( - 'user', - (userId) => fetchUserInfo(userId) -); - -/** - * redux 的批量获取hooks的构造器 - * 用于列表 - * @param cacheScope 缓存的域 - * @param getCacheDispatch 请求缓存的dispatch - */ -function reduxHookCacheListFactory( - cacheScope: CacheKey, - getCacheData: GetCacheDataFn -) { - const isFetchingDataIdQueue: string[] = []; // 正在请求的UUID列表 - - return function hook = Record>( - idList: string[] - ): R { - const cacheList = useAppSelector( - (state) => _get(state, ['cache', cacheScope]) as any - ); - const dispatch = useAppDispatch(); - - const resMap = useMemo(() => { - const map = {} as R; - for (const id of idList) { - if (_isNil(cacheList[id]) && !isSkipId(id)) { - // 如果没有数据则请求数据 - // 从服务端获取缓存信息 - if (isFetchingDataIdQueue.indexOf(id) === -1) { - // 没有正在获取缓存信息 - console.log(`缓存[${cacheScope}: ${id}]不存在, 自动获取`); - getCacheData(id).then((data) => { - // 从列表中移除 - const index = isFetchingDataIdQueue.indexOf(id); - if (index !== -1) { - isFetchingDataIdQueue.splice(index, 1); - } - - dispatch( - cacheActions.setCache({ - scope: cacheScope, - id, - data, - }) - ); - }); - - isFetchingDataIdQueue.push(id); - } - continue; - } - - // 加入返回的map中 - _set(map, id, cacheList[id]); - } - - return map; - }, [cacheList, idList.join(',')]); - - return resMap; - }; -} -export const useCachedUserInfoList = reduxHookCacheListFactory( - 'user', - (userId) => fetchUserInfo(userId) -); diff --git a/shared/redux/slices/cache.ts b/shared/redux/slices/cache.ts deleted file mode 100644 index 3dcd8876..00000000 --- a/shared/redux/slices/cache.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import type { UserBaseInfo } from '../../model/user'; -import _set from 'lodash/set'; - -interface CacheState { - user: Record; -} - -export type CacheKey = keyof CacheState; - -const initialState: CacheState = { user: {} }; - -const cacheSlice = createSlice({ - name: 'cache', - initialState, - reducers: { - setCache( - state, - action: PayloadAction<{ - scope: CacheKey; - id: string; - data: unknown; - }> - ) { - const { scope, id, data } = action.payload; - _set(state, [scope, id], data); - }, - }, -}); - -export const cacheActions = cacheSlice.actions; -export const cacheReducer = cacheSlice.reducer; diff --git a/shared/redux/slices/index.ts b/shared/redux/slices/index.ts index bba90e14..a6bd1dcc 100644 --- a/shared/redux/slices/index.ts +++ b/shared/redux/slices/index.ts @@ -1,13 +1,10 @@ import { combineReducers } from '@reduxjs/toolkit'; -import { cacheReducer } from './cache'; import { userReducer } from './user'; export const appReducer = combineReducers({ - cache: cacheReducer, user: userReducer, }); export type AppState = ReturnType; -export { cacheActions } from './cache'; export { userActions } from './user'; diff --git a/web/src/App.tsx b/web/src/App.tsx index dd873229..036c2cbb 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; -import { useStorage } from 'pawchat-shared'; +import { PawProvider, useStorage } from 'pawchat-shared'; import clsx from 'clsx'; import { Loadable } from './components/Loadable'; @@ -13,7 +13,11 @@ const EntryRoute = Loadable(() => ); const AppProvider: React.FC = React.memo((props) => { - return {props.children}; + return ( + + {props.children} + + ); }); AppProvider.displayName = 'AppProvider'; diff --git a/web/src/components/UserListItem.tsx b/web/src/components/UserListItem.tsx index 678a9802..2b6bf1c8 100644 --- a/web/src/components/UserListItem.tsx +++ b/web/src/components/UserListItem.tsx @@ -3,7 +3,7 @@ import { Avatar } from './Avatar'; import _isNil from 'lodash/isNil'; import { Skeleton, Space } from 'antd'; // import { openUserProfile } from './modals/UserProfile'; -import { useCachedUserInfo } from 'pawchat-shared'; +import { useUserInfo } from 'pawchat-shared'; // const UserAvatar = styled(Avatar)` // cursor: pointer !important; @@ -21,7 +21,7 @@ interface UserListItemProps { } export const UserListItem: React.FC = React.memo((props) => { const { actions = [] } = props; - const userInfo = useCachedUserInfo(props.userId); + const userInfo = useUserInfo(props.userId); const userName = userInfo.nickname; const handleClick = useCallback(() => { diff --git a/yarn.lock b/yarn.lock index 6a8ab16c..ad7dc95a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -314,7 +314,7 @@ core-js-pure "^3.15.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.14.6" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== @@ -2097,6 +2097,11 @@ batch@0.6.1: resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= +big-integer@^1.6.16: + version "1.6.48" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -2183,6 +2188,20 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -3073,7 +3092,7 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -detect-node@^2.0.4: +detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== @@ -5533,6 +5552,11 @@ joycon@^3.0.1: resolved "https://registry.npmjs.org/joycon/-/joycon-3.0.1.tgz#9074c9b08ccf37a6726ff74a18485f85efcaddaf" integrity sha512-SJcJNBg32dGgxhPtM0wQqxqV0ax9k/9TaUskGDSJkSFSQOEWWvQ3zzWdGQRIUry2j1zA5+ReH13t0Mf3StuVZA== +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5973,6 +5997,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +match-sorter@^6.0.2: + version "6.3.0" + resolved "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.0.tgz#454a1b31ed218cddbce6231a0ecb5fdc549fed01" + integrity sha512-efYOf/wUpNb8FgNY+cOD2EIJI1S5I7YPKsw0LBp7wqPh5pmMS6i/wr3ZWwfwrAw1NvqTA2KUReVRWDX84lUcOQ== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -6050,6 +6082,11 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + mime-db@1.48.0, "mime-db@>= 1.43.0 < 2": version "1.48.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" @@ -6208,6 +6245,13 @@ nan@^2.12.1: resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoclone@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" @@ -6471,6 +6515,11 @@ object.values@^1.1.4: define-properties "^1.1.3" es-abstract "^1.18.2" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -7533,6 +7582,15 @@ react-is@^17.0.1: opencollective "^1.0.3" opencollective-postinstall "^2.0.2" +react-query@^3.18.1: + version "3.18.1" + resolved "https://registry.npmjs.org/react-query/-/react-query-3.18.1.tgz#893b5475a7b4add099e007105317446f7a2cd310" + integrity sha512-17lv3pQxU9n+cB5acUv0/cxNTjo9q8G+RsedC6Ax4V9D8xEM7Q5xf9xAbCPdEhDrrnzPjTls9fQEABKRSi7OJA== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-redux@^7.2.4: version "7.2.4" resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225" @@ -7720,6 +7778,11 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + remove-bom-buffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" @@ -7898,6 +7961,13 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@^2.6.3: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -7905,13 +7975,6 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - run-async@^2.2.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -9093,6 +9156,14 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"