跨框架状态管理方案:FlexState (第一版)

48 阅读2分钟

下面我将设计一个名为 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>