下面我将设计一个名为 FlexState 的状态管理库,它同时支持 Vue 和 React,借鉴了 Zustand 的简洁性和 Vuex 的结构化特点。
核心设计
1. 基本架构
// flexstate.ts
type Listener<T> = (state: T) => void;
type SetState<T> = (partial: Partial<T> | ((state: T) => Partial<T>)) => void;
type GetState<T> = () => T;
type Subscribe<T> = (listener: Listener<T>) => () => void;
type Destroy = () => void;
interface StoreApi<T> {
setState: SetState<T>;
getState: GetState<T>;
subscribe: Subscribe<T>;
destroy: Destroy;
}
interface CreateStoreOptions<T> {
name?: string;
state: T;
actions?: (set: SetState<T>, get: GetState<T>) => Record<string, (...args: any[]) => any>;
persist?: {
key: string;
storage?: 'local' | 'session';
paths?: (keyof T)[];
};
}
function createStore<T extends object>(options: CreateStoreOptions<T>): StoreApi<T> {
// 实现代码在下文
}
2. 完整实现
// flexstate.ts
function createStore<T extends object>(options: CreateStoreOptions<T>): StoreApi<T> {
const { name = 'store', state, actions, persist } = options;
let listeners: Listener<T>[] = [];
let currentState: T = { ...state };
// 持久化逻辑
if (persist) {
const storage = persist.storage === 'session' ? sessionStorage : localStorage;
const storageKey = `flexstate:${persist.key}`;
try {
const savedState = storage.getItem(storageKey);
if (savedState) {
const parsedState = JSON.parse(savedState);
if (persist.paths) {
persist.paths.forEach(key => {
if (parsedState[key] !== undefined) {
(currentState as any)[key] = parsedState[key];
}
});
} else {
currentState = { ...currentState, ...parsedState };
}
}
} catch (e) {
console.warn(`Failed to load persisted state for ${name}`, e);
}
}
const setState: SetState<T> = (partial) => {
const nextPartial = typeof partial === 'function' ? partial(currentState) : partial;
currentState = { ...currentState, ...nextPartial };
// 触发所有监听器
listeners.forEach(listener => listener(currentState));
// 持久化逻辑
if (persist) {
const storage = persist.storage === 'session' ? sessionStorage : localStorage;
const storageKey = `flexstate:${persist.key}`;
try {
if (persist.paths) {
const partialState: Partial<T> = {};
persist.paths.forEach(key => {
partialState[key] = currentState[key];
});
storage.setItem(storageKey, JSON.stringify(partialState));
} else {
storage.setItem(storageKey, JSON.stringify(currentState));
}
} catch (e) {
console.warn(`Failed to persist state for ${name}`, e);
}
}
};
const getState: GetState<T> = () => currentState;
const subscribe: Subscribe<T> = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
const destroy: Destroy = () => {
listeners = [];
};
// 绑定actions
const boundActions = actions ? actions(setState, getState) : {};
const store = { setState, getState, subscribe, destroy, ...boundActions };
return store;
}
// React Hook
function useStore<T, U>(store: StoreApi<T>, selector: (state: T) => U): U {
// 实现代码在下文
}
// Vue Composition API
function useVueStore<T, U>(store: StoreApi<T>, selector: (state: T) => U): Ref<U> {
// 实现代码在下文
}
React 集成实现
// react.ts
import { useState, useEffect, useRef, useMemo } from 'react';
function useStore<T, U>(store: StoreApi<T>, selector: (state: T) => U): U {
const [state, setState] = useState(() => selector(store.getState()));
const selectorRef = useRef(selector);
const stateRef = useRef(state);
useEffect(() => {
selectorRef.current = selector;
stateRef.current = state;
}, [selector, state]);
useEffect(() => {
const unsubscribe = store.subscribe(() => {
const nextState = selectorRef.current(store.getState());
if (nextState !== stateRef.current) {
setState(nextState);
}
});
return unsubscribe;
}, [store]);
return state;
}
// 创建React绑定
function createReactStore<T extends object>(options: CreateStoreOptions<T>) {
const store = createStore(options);
function useStoreHook<U>(selector: (state: T) => U): U {
return useStore(store, selector);
}
return {
...store,
useStore: useStoreHook,
};
}
Vue 集成实现
// vue.ts
import { ref, watchEffect, onUnmounted } from 'vue';
function useVueStore<T, U>(store: StoreApi<T>, selector: (state: T) => U): Ref<U> {
const state = ref(selector(store.getState())) as Ref<U>;
const unsubscribe = store.subscribe(() => {
const nextState = selector(store.getState());
if (JSON.stringify(nextState) !== JSON.stringify(state.value)) {
state.value = nextState;
}
});
onUnmounted(() => {
unsubscribe();
});
return state;
}
// 创建Vue绑定
function createVueStore<T extends object>(options: CreateStoreOptions<T>) {
const store = createStore(options);
function useStore<U>(selector: (state: T) => U): Ref<U> {
return useVueStore(store, selector);
}
return {
...store,
useStore,
};
}
使用示例
React 中使用
// store/counterStore.ts
import { createReactStore } from 'flexstate';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const counterStore = createReactStore<CounterState>({
name: 'counter',
state: {
count: 0,
},
actions: (set, get) => ({
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}),
persist: {
key: 'counter',
paths: ['count'],
},
});
// CounterComponent.tsx
import React from 'react';
import { counterStore } from './store/counterStore';
export const CounterComponent = () => {
const count = counterStore.useStore(state => state.count);
const { increment, decrement, reset } = counterStore;
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
};
Vue 中使用
/ store/counterStore.ts
import { createVueStore } from 'flexstate';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const counterStore = createVueStore<CounterState>({
name: 'counter',
state: {
count: 0,
},
actions: (set, get) => ({
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}),
persist: {
key: 'counter',
paths: ['count'],
},
});
// CounterComponent.vue
<template>
<div>
<h1>Count: {{ count }}</h1>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup>
import { counterStore } from './store/counterStore';
const count = counterStore.useStore(state => state.count);
const { increment, decrement, reset } = counterStore;
</script>