store init

This commit is contained in:
abearxiong 2025-03-26 11:52:56 +08:00
parent b5dde4e823
commit 152fda350e
8 changed files with 266 additions and 12 deletions

View File

@ -8,12 +8,14 @@
"type": "module",
"scripts": {
"dev": "rollup -c -w",
"dev:lib": "rollup -c -w",
"build": "npm run clean && rollup -c",
"build:app": "npm run build && rsync dist/* ../deploy/dist",
"clean": "rm -rf dist"
},
"files": [
"dist"
"dist",
"react/dist"
],
"keywords": [
"kevisual",
@ -23,20 +25,21 @@
"license": "ISC",
"description": "",
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/lodash-es": "^4.17.12",
"fast-deep-equal": "^3.1.3",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.9",
"rollup": "^4.27.4",
"rollup-plugin-dts": "^6.1.1",
"nanoid": "^5.1.5",
"rollup": "^4.37.0",
"rollup-plugin-dts": "^6.2.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"zustand": "^5.0.1"
"tsup": "^8.4.0",
"typescript": "^5.8.2",
"zustand": "^5.0.3"
},
"publishConfig": {
"access": "public"
@ -65,6 +68,10 @@
"./web": {
"import": "./dist/web.js",
"require": "./dist/web.js"
},
"./react": {
"import": "./react/dist/store-react.js",
"types": "./react/dist/index.d.ts"
}
},
"dependencies": {

23
react/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "@kevisual/store-react",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"dev:lib": "vite build --watch",
"build:lib": "vite build"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.6.5",
"type": "module",
"peerDependencies": {
"react": "^19.0.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.2.3",
"vite-plugin-dts": "^4.5.3"
}
}

58
react/src/Store.tsx Normal file
View File

@ -0,0 +1,58 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { StateCreator } from '../../src/store';
import { shallow, useShallow } from 'zustand/shallow';
import { useContextKey } from '../../src/web-context';
export const StoreContext = createContext<any>(null);
export const initStoreFn: StateCreator<any, [], [], any> = (set, get, store) => {
return {
mark: '123',
setMark: (mark: string) => set({ mark }),
info: 'info',
setInfo: (info) => set({ info }),
};
};
export const StoreContextProvider = ({
children,
id,
stateCreator,
}: {
children: React.ReactNode;
id: string;
stateCreator?: StateCreator<any, [], [], any>;
}) => {
const store = useContextKey<any>('store');
if (!store) {
console.error('store not found');
return null;
}
const smStore = useMemo(() => {
return store.createIfNotExists(stateCreator || initStoreFn, id);
}, [id]);
const [state, setState] = useState(smStore);
useEffect(() => {
setState(smStore);
}, [smStore]);
return <StoreContext.Provider value={state}>{children}</StoreContext.Provider>;
};
export const useStore = (selector?: any) => {
const store = useContext(StoreContext);
const allState = store.getState();
const selectedState = selector ? selector(allState) : allState;
const [state, setState] = useState(selectedState);
useEffect(() => {
const unsubscribe = store.subscribe((newState: any) => {
const newSelectedState = selector ? selector(newState) : newState;
if (!shallow(state, newSelectedState)) {
setState(newSelectedState);
}
});
return () => unsubscribe();
}, [store, useShallow, state]);
return state;
};

View File

@ -0,0 +1,83 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { StateCreator } from '../../src/store';
import { shallow, useShallow } from 'zustand/shallow';
import { useContextKey } from '../../src/web-context';
export const initStoreFn: StateCreator<any, [], [], any> = (set, get, store) => {
return {
description: 'this is a blank store',
};
};
export const useStoreContext = (id: string, stateCreator?: StateCreator<any, [], [], any>) => {
const StoreContext = createContext<any>(null);
const store = useContextKey<any>('store');
if (!store) {
console.error('store not found');
return null;
}
if (!stateCreator) {
console.error('stateCreator not found');
return null;
}
const StoreContextProvider = ({ children, id, stateCreator }: { children: React.ReactNode; id: string; stateCreator?: StateCreator<any, [], [], any> }) => {
const smStore = useMemo(() => {
console.log('stateCreator', stateCreator);
return store.createIfNotExists(stateCreator || initStoreFn, id);
}, [id]);
const [state, setState] = useState(smStore);
useEffect(() => {
setState(smStore);
}, [smStore]);
console.log('value', smStore.getState());
// console.log('value', state);
return <StoreContext.Provider value={state}>{children}</StoreContext.Provider>;
};
const useStore = (selector?: any) => {
const store = useContext(StoreContext);
const allState = store.getState();
const selectedState = selector ? selector(allState) : allState;
const [state, setState] = useState(selectedState);
useEffect(() => {
const unsubscribe = store.subscribe((newState: any) => {
const newSelectedState = selector ? selector(newState) : newState;
setState(newSelectedState);
});
return () => unsubscribe();
}, [store, useShallow, state]);
};
useEffect(() => {
// console.log('store', store);
// @ts-ignore
window.storeContext = {
// @ts-ignore
...window.storeContext,
[id]: {
StoreContext,
Provider: ({ children }: { children: React.ReactNode }) => {
return (
<StoreContextProvider id={id} stateCreator={stateCreator}>
{children}
</StoreContextProvider>
);
},
},
};
return () => {
// @ts-ignore
delete window.storeContext[id];
};
}, [id]);
return {
StoreContext,
Provider: ({ children }: { children: React.ReactNode }) => {
return (
<StoreContextProvider id={id} stateCreator={stateCreator}>
{children}
</StoreContextProvider>
);
},
useStore,
};
};

1
react/src/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './Store';

43
react/tsconfig.json Normal file
View File

@ -0,0 +1,43 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
// "jsx": "react",
// "jsxFragmentFactory": "Fragment",
// "jsxFactory": "h",
"jsx": "react-jsx",
"baseUrl": "./",
"typeRoots": [
"node_modules/@types",
"node_modules/@kevisual/types",
],
"paths": {
"@/*": [
"src/*"
]
},
/* Linting */
"strict": true,
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
},
"include": [
"src",
"typings.d.ts"
]
}

24
react/vite.config.mjs Normal file
View File

@ -0,0 +1,24 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
export default defineConfig({
build: {
lib: {
entry: './src/index.ts',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
rollupOptions: {
external: ['react', 'react-jsx-runtime', 'zustand'],
},
},
plugins: [
react(),
dts({
insertTypesEntry: true,
outputDir: './dist/types',
}),
],
});

View File

@ -20,7 +20,22 @@ export class StoreManager {
this.stores[key] = createZutandStore(initialStore);
return this.stores[key] as StoreApi<T>;
}
create<T = any, U extends T = any>(initialStore: StateCreator<T, [], [], U>, key: string) {
create<T extends Record<string, any>, U extends T = T>(initialStore: StateCreator<T, [], [], U>, key: string) {
return this.createStore(initialStore, key) as StoreApi<T>;
}
createIfNotExists<T extends Record<string, any>, U extends T = T>(
initialStore: StateCreator<T, [], [], U>,
key: string,
opts?: {
force?: boolean;
},
): StoreApi<T> {
if (this.stores[key] && !opts?.force) {
return this.stores[key];
}
if (this.stores[key] && opts?.force) {
this.removeStore(key);
}
return this.createStore(initialStore, key);
}
getStore(key: string) {
@ -84,8 +99,8 @@ export const sub = <T = any>(fn: FnListener<T>, { path, deep, store }: SubOption
}
return store.subscribe((newState: T, oldState: T) => {
try {
const newPath = get(newState, path);
const oldPath = get(oldState, path);
const newPath = get(newState, path as string);
const oldPath = get(oldState, path as string);
if (!newPath && !oldPath) {
// 都不存在
return;