外部状态管理 #

一、概述 #

1.1 为什么需要外部状态管理 #

场景 说明
复杂应用 大量共享状态
跨组件通信 深层嵌套组件
状态持久化 本地存储同步
调试需求 时间旅行调试

1.2 常用状态管理库 #

特点
Redux 可预测、中间件丰富
Zustand 轻量、简单
Jotai 原子化状态
MobX 响应式

二、Redux 集成 #

2.1 安装 #

bash
npm install @reduxjs/toolkit react-redux

2.2 配置 compat #

javascript
// vite.config.js
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';

export default defineConfig({
  plugins: [preact()],
  resolve: {
    alias: {
      'react': 'preact/compat',
      'react-dom': 'preact/compat'
    }
  }
});

2.3 创建 Store #

jsx
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

2.4 使用 Redux #

jsx
import { Provider, useSelector, useDispatch } from 'react-redux';
import { store, increment, decrement } from './store';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

2.5 异步 Action #

jsx
import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

// 使用
function UserProfile({ userId }) {
  const { data, loading, error } = useSelector(state => state.user);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchUser(userId));
  }, [userId]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  
  return <div>{data.name}</div>;
}

三、Zustand 集成 #

3.1 安装 #

bash
npm install zustand

3.2 创建 Store #

jsx
import { create } from 'zustand';

const useStore = create((set, get) => ({
  count: 0,
  
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  incrementByAmount: (amount) => set((state) => ({ 
    count: state.count + amount 
  })),
  
  reset: () => set({ count: 0 }),
  
  getCount: () => get().count
}));

3.3 使用 Store #

jsx
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  const decrement = useStore((state) => state.decrement);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

3.4 选择器优化 #

jsx
// 选择特定状态
const count = useStore((state) => state.count);

// 选择多个状态
const { count, increment } = useStore((state) => ({
  count: state.count,
  increment: state.increment
}));

// 使用 shallow 比较
import { shallow } from 'zustand/shallow';

const { count, increment } = useStore(
  (state) => ({ count: state.count, increment: state.increment }),
  shallow
);

3.5 异步操作 #

jsx
const useUserStore = create((set) => ({
  user: null,
  loading: false,
  error: null,
  
  fetchUser: async (userId) => {
    set({ loading: true, error: null });
    
    try {
      const response = await fetch(`/api/users/${userId}`);
      const user = await response.json();
      set({ user, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
}));

// 使用
function UserProfile({ userId }) {
  const { user, loading, error, fetchUser } = useUserStore();

  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  
  return <div>{user.name}</div>;
}

3.6 持久化 #

jsx
import { persist } from 'zustand/middleware';

const useCartStore = create(
  persist(
    (set) => ({
      items: [],
      
      addItem: (item) => set((state) => ({
        items: [...state.items, item]
      })),
      
      removeItem: (id) => set((state) => ({
        items: state.items.filter(item => item.id !== id)
      })),
      
      clearCart: () => set({ items: [] })
    }),
    {
      name: 'cart-storage',
      getStorage: () => localStorage
    }
  )
);

四、Jotai 集成 #

4.1 安装 #

bash
npm install jotai

4.2 基本用法 #

jsx
import { atom, useAtom } from 'jotai';

const countAtom = atom(0);

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

4.3 派生 Atom #

jsx
const firstNameAtom = atom('John');
const lastNameAtom = atom('Doe');

const fullNameAtom = atom((get) => {
  return `${get(firstNameAtom)} ${get(lastNameAtom)}`;
});

function NameDisplay() {
  const [fullName] = useAtom(fullNameAtom);
  
  return <p>Full Name: {fullName}</p>;
}

4.4 异步 Atom #

jsx
const userIdAtom = atom(1);

const userAtom = atom(async (get) => {
  const userId = get(userIdAtom);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

function UserProfile() {
  const [user] = useAtom(userAtom);
  
  return <div>{user?.name}</div>;
}

五、选择指南 #

5.1 对比 #

体积 学习曲线 适用场景
Redux 较大 陡峭 大型企业应用
Zustand 很小 平缓 中小型应用
Jotai 很小 平缓 原子化状态
Signals 很小 平缓 Preact 原生

5.2 选择建议 #

jsx
// 小型项目:Signals 或 Zustand
// 中型项目:Zustand 或 Jotai
// 大型项目:Redux Toolkit
// Preact 项目:优先使用 Signals

六、最佳实践 #

6.1 状态分离 #

jsx
// 分离不同领域的状态
const useUserStore = create(/* ... */);
const useCartStore = create(/* ... */);
const useUIStore = create(/* ... */);

6.2 类型安全 #

typescript
import { create } from 'zustand';

interface UserState {
  user: User | null;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

const useUserStore = create<UserState>((set) => ({
  user: null,
  login: async (credentials) => { /* ... */ },
  logout: () => set({ user: null })
}));

6.3 DevTools #

jsx
// Redux DevTools
import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools((set) => ({
    // ...
  }))
);

七、总结 #

特点 推荐场景
Redux 可预测、中间件 大型应用
Zustand 轻量、简单 中小型应用
Jotai 原子化 精细控制
Signals Preact 原生 Preact 项目

核心原则:

  • 根据项目规模选择
  • 保持状态结构清晰
  • 合理拆分 Store
  • 使用 TypeScript 增强类型安全
最后更新:2026-03-28