diff --git a/apps/admin/package.json b/apps/admin/package.json index 284be08..d39dca3 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -22,7 +22,8 @@ "immer": "^10.1.1", "lodash": "^4.17.21", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zustand": "^4.5.2" }, "devDependencies": { "@3rapp/code-config": "workspace:*", diff --git a/apps/admin/src/components/store/index.ts b/apps/admin/src/components/store/index.ts new file mode 100644 index 0000000..f0c4eaa --- /dev/null +++ b/apps/admin/src/components/store/index.ts @@ -0,0 +1,2 @@ +export * from './utils'; +export * from './types'; diff --git a/apps/admin/src/components/store/types.ts b/apps/admin/src/components/store/types.ts new file mode 100644 index 0000000..8d1d002 --- /dev/null +++ b/apps/admin/src/components/store/types.ts @@ -0,0 +1,8 @@ +export type ZustandHookSelectors = { + [Key in keyof StateType as `use${Capitalize}`]: () => StateType[Key]; +}; +export interface ZustandGetterSelectors { + getters: { + [key in keyof StateType]: () => StateType[key]; + }; +} diff --git a/apps/admin/src/components/store/utils.ts b/apps/admin/src/components/store/utils.ts new file mode 100644 index 0000000..36a4bcb --- /dev/null +++ b/apps/admin/src/components/store/utils.ts @@ -0,0 +1,160 @@ +import { capitalize } from 'lodash'; +import { create, Mutate, StateCreator, StoreApi, UseBoundStore } from 'zustand'; +import { + subscribeWithSelector, + devtools, + persist, + PersistOptions, + DevtoolsOptions, + redux, +} from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; + +import { ZustandGetterSelectors, ZustandHookSelectors } from './types'; + +/** + * 创建包含订阅,immer以及devtoools功能的普通状态商店 + * @param creator + * @param devtoolsOptions + */ +export const createStore = ( + creator: StateCreator< + T, + [ + ['zustand/subscribeWithSelector', never], + ['zustand/immer', never], + ['zustand/devtools', never], + ] + >, + devtoolsOptions?: DevtoolsOptions, +) => { + return create()(subscribeWithSelector(immer(devtools(creator, devtoolsOptions)))); +}; + +/** + * 创建包含订阅,immer以及devtoools功能的普通状态商店 + * 同时支持自动存储到客户端,默认存储到localstorage + * @param creator + * @param persistOptions + * @param devtoolsOptions + */ +export const createPersistStore = ( + creator: StateCreator< + T, + [ + ['zustand/subscribeWithSelector', never], + ['zustand/immer', never], + ['zustand/devtools', never], + ['zustand/persist', P], + ] + >, + persistOptions: PersistOptions, + devtoolsOptions?: DevtoolsOptions, +) => { + return create()( + subscribeWithSelector( + immer(devtools(persist(creator as unknown as any, persistOptions), devtoolsOptions)), + ), + ); +}; + +/** + * 创建包含订阅,immer以及devtoools功能的reducer状态商店 + * 同时支持自动存储到客户端,默认存储到localstorage + * @param reducer + * @param initialState + * @param devtoolsOptions + */ +export const createReduxStore = < + T extends object, + A extends { + type: string; + }, +>( + reducer: (state: T, action: A) => T, + initialState: T, + devtoolsOptions?: DevtoolsOptions, +) => create(subscribeWithSelector(immer(devtools(redux(reducer, initialState), devtoolsOptions)))); + +/** + * 创建包含订阅,immer以及devtoools功能的reducer状态商店 + * @param reducer + * @param initialState + * @param persistOptions + * @param devtoolsOptions + */ +export const createPersistReduxStore = < + T extends object, + A extends { + type: string; + }, + P = T, +>( + reducer: (state: T, action: A) => T, + initialState: T, + persistOptions: PersistOptions, + devtoolsOptions?: DevtoolsOptions, +) => + create( + subscribeWithSelector( + immer( + devtools( + persist(redux(reducer, initialState), persistOptions as any), + devtoolsOptions, + ), + ), + ), + ); + +/** + * 直接通过getters获取状态值,比如store.getters.xxx() + * @param store + */ +export function createStoreGetters( + store: UseBoundStore< + Mutate< + StoreApi, + [ + ['zustand/subscribeWithSelector', never], + ['zustand/immer', never], + ['zustand/devtools', never], + ] + > + >, +) { + const storeIn = store as any; + + storeIn.getters = {}; + Object.keys(storeIn.getState()).forEach((key) => { + const selector = (state: T) => state[key as keyof T]; + storeIn.getters[key] = () => storeIn(selector); + }); + + return storeIn as typeof store & ZustandGetterSelectors; +} + +/** + * 直接通过类似hooks的方法获取状态值,比如store.useXxx() + * @param store + */ +export function createStoreHooks>( + store: UseBoundStore< + Mutate< + StoreApi, + [ + ['zustand/subscribeWithSelector', never], + ['zustand/immer', never], + ['zustand/devtools', never], + ] + > + >, +) { + const storeIn = store as any; + + Object.keys(storeIn.getState()).forEach((key) => { + const selector = (state: T) => state[key as keyof T]; + storeIn[`use${capitalize(key)}`] = () => storeIn(selector); + }); + + return storeIn as typeof store & ZustandHookSelectors; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a5200d..b52fa42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + zustand: + specifier: ^4.5.2 + version: 4.5.2(@types/react@18.3.1)(immer@10.1.1)(react@18.3.1) devDependencies: '@3rapp/code-config': specifier: workspace:* @@ -5432,6 +5435,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5606,6 +5614,21 @@ packages: engines: {node: '>=8.0.0'} hasBin: true + zustand@4.5.2: + resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -11360,6 +11383,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} uuid@9.0.1: {} @@ -11553,3 +11580,11 @@ snapshots: validator: 13.12.0 optionalDependencies: commander: 9.5.0 + + zustand@4.5.2(@types/react@18.3.1)(immer@10.1.1)(react@18.3.1): + dependencies: + use-sync-external-store: 1.2.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.1 + immer: 10.1.1 + react: 18.3.1