跳转至

状态管理

状态管理

📚 章节目标

本章节将全面介绍前端状态管理的各种方案,包括Redux、MobX、Zustand、Recoil、状态机等,帮助学习者掌握不同状态管理方案的选型和使用。

学习目标

  1. 深入理解状态管理的核心概念
  2. 掌握Redux及其生态工具
  3. 掌握MobX响应式状态管理
  4. 掌握Zustand轻量级状态管理
  5. 掌握Recoil原子状态管理
  6. 了解状态机在状态管理中的应用

🎯 状态管理概述

1. 什么是状态管理

JavaScript
// 状态管理示例
// 不使用状态管理
function App() {
  const [user, setUser] = useState(null);
  const [todos, setTodos] = useState([]);
  const [theme, setTheme] = useState('light');

  // 状态分散在各个组件中
  return (
    <div>
      <Header user={user} theme={theme} />
      <TodoList todos={todos} setTodos={setTodos} />
      <Footer theme={theme} setTheme={setTheme} />
    </div>
  );
}

// 使用状态管理
function App() {
  return (
    <Provider store={store}>
      <Header />
      <TodoList />
      <Footer />
    </Provider>
  );
}

2. 状态管理的必要性

2.1 什么时候需要状态管理

JavaScript
// 需要状态管理的场景
// 1. 多个组件需要共享状态
// 2. 组件层级很深,传递props困难
// 3. 状态需要持久化
// 4. 需要时间旅行调试
// 5. 需要中间件处理异步逻辑

// 示例:多组件共享状态
// Header组件需要用户信息
function Header() {
  const user = useStore(state => state.user);
  return <div>Welcome, {user?.name}</div>;
}

// Sidebar组件需要用户信息
function Sidebar() {
  const user = useStore(state => state.user);
  return <div>{user?.avatar}</div>;
}

// Profile组件需要用户信息
function Profile() {
  const user = useStore(state => state.user);
  return <div>{user?.bio}</div>;
}

2.2 状态管理方案对比

方案 适用场景 学习成本 性能 生态
Context API 简单状态共享 原生
Zustand 中等复杂度 良好
Redux Toolkit 大型应用 优秀
MobX 响应式需求 良好
Recoil 原子状态 良好
XState 复杂状态机 良好

🗃️ Redux深度实践

1. Redux基础

1.1 核心概念

JavaScript
// Action
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const SET_FILTER = 'SET_FILTER';

// Action Creator
function addTodo(text) {
  return {
    type: ADD_TODO,
    payload: { text },
  };
}

function toggleTodo(id) {
  return {
    type: TOGGLE_TODO,
    payload: { id },
  };
}

function setFilter(filter) {
  return {
    type: SET_FILTER,
    payload: { filter },
  };
}

// Reducer
const initialState = {
  todos: [],
  filter: 'all',
};

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload.text,
            completed: false,
          },
        ],
      };
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };
    case SET_FILTER:
      return {
        ...state,
        filter: action.payload.filter,
      };
    default:
      return state;
  }
}

// Store
import { createStore } from 'redux';

const store = createStore(todoReducer);

// Dispatch Action
store.dispatch(addTodo('Learn Redux'));
store.dispatch(toggleTodo(1));
store.dispatch(setFilter('active'));

// Subscribe to Changes
store.subscribe(() => {
  console.log(store.getState());
});

1.2 Redux Toolkit

JavaScript
// 使用Redux Toolkit简化Redux代码
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Slice
const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: [],
    filter: 'all',
  },
  reducers: {
    addTodo: (state, action) => {
      state.todos.push({
        id: Date.now(),
        text: action.payload,
        completed: false,
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.todos.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    setFilter: (state, action) => {
      state.filter = action.payload;
    },
  },
});

// Extract actions
export const { addTodo, toggleTodo, setFilter } = todoSlice.actions;

// Create store
const store = configureStore({
  reducer: {
    todos: todoSlice.reducer,
  },
});

// Use in React
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos.todos);
  const filter = useSelector(state => state.todos.filter);
  const dispatch = useDispatch();

  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });

  return (
    <div>
      <ul>
        {filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => dispatch(toggleTodo(todo.id))}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={() => dispatch(addTodo('New Todo'))}>
        Add Todo
      </button>
    </div>
  );
}

2. Redux中间件

2.1 Thunk中间件

JavaScript
import { createSlice, configureStore } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';

// Async Thunk
export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('/api/todos');
    return response.json();
  }
);

export const addTodoAsync = createAsyncThunk(
  'todos/addTodo',
  async (text) => {
    const response = await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify({ text }),
    });
    return response.json();
  }
);

// Slice with extraReducers
const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: [],
    loading: false,
    error: null,
  },
  reducers: {
    toggleTodo: (state, action) => {
      const todo = state.todos.find(todo => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.todos = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })
      .addCase(addTodoAsync.fulfilled, (state, action) => {
        state.todos.push(action.payload);
      });
  },
});

// Create store with thunk middleware
const store = configureStore({
  reducer: {
    todos: todoSlice.reducer,
  },
});

// Use in component
function TodoList() {
  const dispatch = useDispatch();
  const { todos, loading, error } = useSelector(state => state.todos);

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

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

  return (
    <div>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

2.2 自定义中间件

JavaScript
// Logger中间件
// Redux中间件采用柯里化结构:store => next => action
// 每层函数接收不同参数,形成中间件链
const loggerMiddleware = store => next => action => {
  console.log('Dispatching:', action);  // 记录发出的action
  const result = next(action);          // 调用下一个中间件或reducer
  console.log('Next State:', store.getState());  // 记录更新后的状态
  return result;
};

// 使用中间件
const store = configureStore({
  reducer: {
    todos: todoSlice.reducer,
  },
  // getDefaultMiddleware()包含thunk等默认中间件,concat追加自定义中间件
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware),
});

// 异步中间件:当action是函数时,直接执行而不传递给reducer
const asyncMiddleware = store => next => action => {
  if (typeof action === 'function') {
    return action(store.dispatch, store.getState);
  }
  return next(action);
};

3. Redux最佳实践

3.1 模块化

JavaScript
// store/modules/todos.js
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    filter: 'all',
  },
  reducers: {
    // reducers
  },
});

export const { addTodo, toggleTodo } = todoSlice.actions;
export default todoSlice.reducer;

// store/modules/users.js
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'users',
  initialState: {
    currentUser: null,
    users: [],
  },
  reducers: {
    // reducers
  },
});

export const { login, logout } = userSlice.actions;
export default userSlice.reducer;

// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import todos from './modules/todos';
import users from './modules/users';

const store = configureStore({
  reducer: {
    todos,
    users,
  },
});

export default store;

3.2 选择器优化

JavaScript
// 基础选择器:从全局state中提取特定片段
const selectTodos = state => state.todos.items;
const selectFilter = state => state.todos.filter;

// 记忆化选择器:只有输入变化时才重新计算,避免不必要的重渲染
import { createSelector } from '@reduxjs/toolkit';

const selectFilteredTodos = createSelector(
  [selectTodos, selectFilter],  // 输入选择器(依赖)
  (todos, filter) => {          // 输出函数(仅在依赖变化时执行)
    switch (filter) {
      case 'active':
        return todos.filter(todo => !todo.completed);
      case 'completed':
        return todos.filter(todo => todo.completed);
      default:
        return todos;
    }
  }
);

// 使用选择器
function TodoList() {
  const filteredTodos = useSelector(selectFilteredTodos);
  return <ul>{filteredTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}

🎭 MobX响应式状态管理

1. MobX基础

1.1 核心概念

JavaScript
import { makeAutoObservable, observable, action, computed, makeObservable } from 'mobx';

// 方式1: makeAutoObservable
class TodoStore {
  todos = [];
  filter = 'all';

  constructor() {
    makeAutoObservable(this);
  }

  get filteredTodos() {
    switch (this.filter) {
      case 'active':
        return this.todos.filter(todo => !todo.completed);
      case 'completed':
        return this.todos.filter(todo => todo.completed);
      default:
        return this.todos;
    }
  }

  addTodo(text) {
    this.todos.push({
      id: Date.now(),
      text,
      completed: false,
    });
  }

  toggleTodo(id) {
    const todo = this.todos.find(todo => todo.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }

  setFilter(filter) {
    this.filter = filter;
  }
}

// 方式2: 手动装饰器
class TodoStore {
  @observable todos = [];
  @observable filter = 'all';

  @computed get filteredTodos() {
    switch (this.filter) {
      case 'active':
        return this.todos.filter(todo => !todo.completed);
      case 'completed':
        return this.todos.filter(todo => todo.completed);
      default:
        return this.todos;
    }
  }

  @action addTodo(text) {
    this.todos.push({
      id: Date.now(),
      text,
      completed: false,
    });
  }

  @action toggleTodo(id) {
    const todo = this.todos.find(todo => todo.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }

  @action setFilter(filter) {
    this.filter = filter;
  }
}

// 创建store实例
const todoStore = new TodoStore();

1.2 在React中使用MobX

JavaScript
import { observer } from 'mobx-react-lite';

// 使用observer包装组件
const TodoList = observer(() => {
  const { filteredTodos, addTodo, toggleTodo, setFilter } = todoStore;

  return (
    <div>
      <ul>
        {filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={() => addTodo('New Todo')}>Add Todo</button>
      <select onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>
    </div>
  );
});

// 使用useLocalObservable
import { useLocalObservable } from 'mobx-react-lite';

function TodoList() {
  const store = useLocalObservable(() => ({
    todos: [],
    filter: 'all',
    get filteredTodos() {
      switch (this.filter) {
        case 'active':
          return this.todos.filter(todo => !todo.completed);
        case 'completed':
          return this.todos.filter(todo => todo.completed);
        default:
          return this.todos;
      }
    },
    addTodo(text) {
      this.todos.push({
        id: Date.now(),
        text,
        completed: false,
      });
    },
    toggleTodo(id) {
      const todo = this.todos.find(todo => todo.id === id);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    setFilter(filter) {
      this.filter = filter;
    },
  }));

  return (
    <div>
      <ul>
        {store.filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => store.toggleTodo(todo.id)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={() => store.addTodo('New Todo')}>Add Todo</button>
    </div>
  );
}

2. MobX高级特性

2.1 异步操作

JavaScript
import { makeAutoObservable, runInAction } from 'mobx';

class UserStore {
  users = [];
  loading = false;
  error = null;

  constructor() {
    makeAutoObservable(this);
  }

  async fetchUsers() {
    this.loading = true;   // 同步修改可以直接赋值
    this.error = null;

    try {
      const response = await fetch('/api/users');
      const users = await response.json();

      // 异步操作后必须用runInAction包裹状态修改
      // 否则MobX无法追踪这些变更,也无法触发响应式更新
      runInAction(() => {
        this.users = users;
        this.loading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.error = error.message;
        this.loading = false;
      });
    }
  }

  async addUser(user) {
    this.loading = true;
    this.error = null;

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(user),
      });
      const newUser = await response.json();

      // 同理,await之后的状态修改需要runInAction
      runInAction(() => {
        this.users.push(newUser);
        this.loading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.error = error.message;
        this.loading = false;
      });
    }
  }
}

const userStore = new UserStore();

2.2 reaction和when

JavaScript
import { reaction, when } from 'mobx';

// reaction - 监听特定数据变化并执行副作用
// 第一个函数:跟踪的数据(只有返回值变化时才触发)
// 第二个函数:变化后执行的副作用
const dispose = reaction(
  () => todoStore.todos.length,  // 跟踪表达式
  (length) => {
    console.log(`Todos count changed to ${length}`);
    // 可以在这里保存到localStorage
    localStorage.setItem('todos', JSON.stringify(todoStore.todos));
  }
);

// when - 等待条件满足后执行一次(自动销毁,适合一次性逻辑)
when(
  () => todoStore.todos.length > 0,  // 条件表达式
  () => {
    console.log('First todo added!');  // 条件满足时执行
  }
);

// dispose()可手动取消reaction的监听
// dispose();

🎪 Zustand轻量级状态管理

1. Zustand基础

1.1 基本使用

JavaScript
import { create } from 'zustand';

// 创建store
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// 在组件中使用
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  const decrement = useStore((state) => state.decrement);
  const reset = useStore((state) => state.reset);

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

// 选择多个状态
function Counter() {
  const { count, increment, decrement, reset } = useStore();

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

1.2 复杂状态管理

JavaScript
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

// 使用中间件
const useStore = create(
  devtools(
    persist(
      (set, get) => ({
        todos: [],
        filter: 'all',

        addTodo: (text) =>
          set((state) => ({
            todos: [
              ...state.todos,
              { id: Date.now(), text, completed: false },
            ],
          })),

        toggleTodo: (id) =>
          set((state) => ({
            todos: state.todos.map((todo) =>
              todo.id === id ? { ...todo, completed: !todo.completed } : todo
            ),
          })),

        deleteTodo: (id) =>
          set((state) => ({
            todos: state.todos.filter((todo) => todo.id !== id),
          })),

        setFilter: (filter) => set({ filter }),

        // ⚠️ 注意:getter 每次访问都会返回新的数组引用,在组件中直接使用
        // useStore(state => state.filteredTodos) 会导致无限重渲染。
        // 建议:在组件中使用 useShallow 或 useMemo 来避免不必要的重渲染:
        //   const filteredTodos = useMemo(() => state.filteredTodos, [state.todos, state.filter]);
        get filteredTodos() {
          const { todos, filter } = get();
          switch (filter) {
            case 'active':
              return todos.filter((todo) => !todo.completed);
            case 'completed':
              return todos.filter((todo) => todo.completed);
            default:
              return todos;
          }
        },
      }),
      { name: 'todo-storage' }
    )
  )
);

// 在组件中使用
function TodoList() {
  const todos = useStore((state) => state.todos);
  const filter = useStore((state) => state.filter);
  const filteredTodos = useStore((state) => state.filteredTodos);
  const addTodo = useStore((state) => state.addTodo);
  const toggleTodo = useStore((state) => state.toggleTodo);
  const deleteTodo = useStore((state) => state.deleteTodo);
  const setFilter = useStore((state) => state.setFilter);

  return (
    <div>
      <ul>
        {filteredTodos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
      <button onClick={() => addTodo('New Todo')}>Add Todo</button>
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>
    </div>
  );
}

2. Zustand高级特性

2.1 异步操作

JavaScript
import { create } from 'zustand';

const useStore = create((set, get) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null });

    try {  // try/catch捕获异常
      const response = await fetch('/api/users');
      const users = await response.json();
      set({ users, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },

  addUser: async (user) => {
    set({ loading: true, error: null });

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(user),
      });
      const newUser = await response.json();
      set((state) => ({ users: [...state.users, newUser], loading: false }));
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

// 在组件中使用
function UserList() {
  const { users, loading, error, fetchUsers } = useStore();

  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

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

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2.2 Slice模式

JavaScript
import { create } from 'zustand';

// 创建slice
const createTodoSlice = (set, get) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: Date.now(), text, completed: false }],  // ...展开运算符:展开数组/对象
    })),
  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ),
    })),
  deleteTodo: (id) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    })),
});

const createFilterSlice = (set) => ({
  filter: 'all',
  setFilter: (filter) => set({ filter }),
});

// 合并slices
const useStore = create((set, get) => ({
  ...createTodoSlice(set, get),
  ...createFilterSlice(set),
  get filteredTodos() {
    const { todos, filter } = get();
    switch (filter) {
      case 'active':
        return todos.filter((todo) => !todo.completed);
      case 'completed':
        return todos.filter((todo) => todo.completed);
      default:
        return todos;
    }
  },
}));

⚛️ Recoil原子状态管理

1. Recoil基础

1.1 Atom和Selector

JavaScript
import { atom, selector, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

// Atom - 原子状态
const countState = atom({
  key: 'countState',
  default: 0,
});

const textState = atom({
  key: 'textState',
  default: '',
});

// Selector - 派生状态
const doubledCountState = selector({
  key: 'doubledCountState',
  get: ({ get }) => {
    const count = get(countState);
    return count * 2;
  },
});

const filteredTextState = selector({
  key: 'filteredTextState',
  get: ({ get }) => {
    const text = get(textState);
    return text.toUpperCase();
  },
});

// 在组件中使用
function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const doubledCount = useRecoilValue(doubledCountState);

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

function TextInput() {
  const [text, setText] = useRecoilState(textState);
  const filteredText = useRecoilValue(filteredTextState);

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Filtered: {filteredText}</p>
    </div>
  );
}

1.2 异步Selector

JavaScript
import { atom, selector, useRecoilValue } from 'recoil';

// 异步selector
const currentUserQuery = selector({
  key: 'currentUserQuery',
  get: async () => {
    const response = await fetch('/api/user');
    return response.json();
  },
});

// 带参数的异步selector
const userQuery = selectorFamily({
  key: 'userQuery',
  get: (userId) => async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
});

// 在组件中使用
function CurrentUser() {
  const user = useRecoilValue(currentUserQuery);

  return <div>{user?.name}</div>;  // ?.可选链对象为null/undefined时安全返回undefined
}

function UserProfile({ userId }) {
  const user = useRecoilValue(userQuery(userId));

  return <div>{user?.name}</div>;
}

2. Recoil高级特性

2.1 Atom Effects

JavaScript
import { atom } from 'recoil';

// Atom Effects:唟桶Atom的副作用钩子,可用于同步localStorage、订阅WebSocket等
const localStorageEffect = (key) => ({ setSelf, onSet }) => {
  // 初始化时从 localStorage 恢复值
  const savedValue = localStorage.getItem(key);
  if (savedValue != null) {
    setSelf(JSON.parse(savedValue));  // 设置Atom初始值
  }

  // 每次Atom值变化时同步到localStorage
  onSet((newValue, _, isReset) => {
    isReset
      ? localStorage.removeItem(key)   // 重置时清除存储
      : localStorage.setItem(key, JSON.stringify(newValue));  // 更新存储
  });
};

const counterState = atom({
  key: 'counterState',
  default: 0,
  effects: [localStorageEffect('counter')],  // 挂载副作用
});

// 在组件中使用
function Counter() {
  const [count, setCount] = useRecoilState(counterState);

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

2.2 异步状态管理

JavaScript
import { atom, selector, useRecoilState, useRecoilValueLoadable } from 'recoil';

// 异步数据atom
const usersState = atom({
  key: 'usersState',
  default: selector({
    key: 'usersState/default',
    get: async () => {
      const response = await fetch('/api/users');
      return response.json();
    },
  }),
});

// 在组件中使用
function UserList() {
  const usersLoadable = useRecoilValueLoadable(usersState);

  switch (usersLoadable.state) {
    case 'hasValue':
      return (
        <ul>
          {usersLoadable.contents.map((user) => (  // map转换每个元素;filter筛选;reduce累积
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      return <div>Error: {usersLoadable.contents.message}</div>;
  }
}

🤖 XState状态机

1. XState基础

1.1 简单状态机

JavaScript
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

// 创建状态机
const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' },
    },
    active: {
      on: { TOGGLE: 'inactive' },
    },
  },
});

// 在组件中使用
function ToggleButton() {
  const [state, send] = useMachine(toggleMachine);

  return (
    <button onClick={() => send('TOGGLE')}>
      {state.matches('active') ? 'ON' : 'OFF'}
    </button>
  );
}

1.2 带上下文的状态机

JavaScript
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

// 创建带上下文的状态机
const counterMachine = createMachine({
  id: 'counter',
  initial: 'idle',
  context: {
    count: 0,
  },
  states: {
    idle: {
      on: {
        INCREMENT: {
          target: 'idle',
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
        DECREMENT: {
          target: 'idle',
          actions: assign({
            count: (context) => context.count - 1,
          }),
        },
        RESET: {
          target: 'idle',
          actions: assign({
            count: () => 0,
          }),
        },
      },
    },
  },
});

// 在组件中使用
function Counter() {
  const [state, send] = useMachine(counterMachine);

  return (
    <div>
      <p>Count: {state.context.count}</p>
      <button onClick={() => send('INCREMENT')}>+</button>
      <button onClick={() => send('DECREMENT')}>-</button>
      <button onClick={() => send('RESET')}>Reset</button>
    </div>
  );
}

2. XState高级特性

2.1 异步状态机

JavaScript
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

// 创建异步状态机:用invoke处理异步操作
const fetchMachine = createMachine({
  id: 'fetch',
  initial: 'idle',
  context: {
    data: null,
    error: null,
  },
  states: {
    idle: {
      on: { FETCH: 'loading' },  // 收到FETCH事件时转入loading状态
    },
    loading: {
      // invoke:进入该状态时自动执行异步任务
      invoke: {
        src: async () => {  // async定义异步函数;await等待Promise完成
          const response = await fetch('/api/data');  // await等待异步操作完成
          return response.json();
        },
        onDone: {   // 异步成功:转入success状态并存储数据
          target: 'success',
          actions: assign({
            data: (_, event) => event.data,
          }),
        },
        onError: {  // 异步失败:转入failure状态并记录错误
          target: 'failure',
          actions: assign({
            error: (_, event) => event.data,
          }),
        },
      },
    },
    success: {
      on: { FETCH: 'loading' },  // 可以重新加载
    },
    failure: {
      on: { FETCH: 'loading' },  // 可以重试
    },
  },
});

// 在组件中使用
function DataFetcher() {
  const [state, send] = useMachine(fetchMachine);  // 解构赋值:从对象/数组提取值

  return (
    <div>
      <button onClick={() => send('FETCH')}>Fetch Data</button>
      {state.matches('loading') && <div>Loading...</div>}
      {state.matches('success') && <div>Data: {JSON.stringify(state.context.data)}</div>}
      {state.matches('failure') && <div>Error: {state.context.error}</div>}
    </div>
  );
}

2.2 并行状态

JavaScript
import { createMachine, assign } from 'xstate';

// 创建并行状态机:多个状态区域可以同时独立运行
const parallelMachine = createMachine({  // const不可重新赋值;let块级作用域变量
  id: 'parallel',
  initial: 'active',
  states: {
    active: {
      type: 'parallel',  // 并行状态:子状态同时活跃、独立转换
      states: {
        // 并行区域1:计数器
        counter: {
          initial: 'idle',
          states: {
            idle: {
              on: {
                INCREMENT: {
                  target: 'idle',
                  actions: assign({
                    count: (context) => (context.count || 0) + 1,
                  }),
                },
              },
            },
          },
        },
        // 并行区域2:开关(与计数器完全独立)
        toggle: {
          initial: 'off',
          states: {
            off: {
              on: { TOGGLE: 'on' },
            },
            on: {
              on: { TOGGLE: 'off' },
            },
          },
        },
      },
    },
  },
});

📊 状态管理方案对比

1. 选型决策树

Text Only
项目需求
├── 状态复杂度
│   ├── 简单状态 → Context API / Zustand
│   ├── 中等复杂度 → Zustand / Recoil
│   ├── 大型应用 → Redux Toolkit / MobX
│   └── 复杂业务流程 → XState
├── 团队经验
│   ├── Redux经验 → Redux Toolkit
│   ├── 响应式经验 → MobX
│   ├── 新手友好 → Zustand
│   └── 实验性 → Recoil
└── 性能要求
    ├── 高性能 → Zustand / MobX
    ├── 可预测性 → Redux / XState
    └── 灵活性 → MobX / Recoil

2. 方案对比表

特性 Redux MobX Zustand Recoil XState
学习曲线 陡峭 中等 平缓 中等 陡峭
代码量 中等 中等
性能
调试工具 优秀 良好 良好 良好 优秀
时间旅行 支持 支持 支持 支持 支持
异步处理 Thunk/Saga 内置 内置 内置 内置
社区生态 优秀 良好 良好 良好 良好

📝 练习题

1. 基础题

题目1:使用Zustand实现一个计数器

JavaScript
import { create } from 'zustand';

// 创建计数器store
const useCounterStore = create((set) => ({  // 箭头函数:简洁的函数语法
  // 实现计数器逻辑
}));

// 在组件中使用
function Counter() {
  // 使用store
  return (
    <div>
      <button>+</button>
      <span>0</span>
      <button>-</button>
    </div>
  );
}

2. 进阶题

题目2:使用MobX实现一个待办事项列表

JavaScript
import { makeAutoObservable } from 'mobx';

// 创建TodoStore
class TodoStore {
  // 实现待办事项逻辑
}

// 在组件中使用
function TodoList() {
  // 使用TodoStore
  return (
    <div>
      {/* 渲染待办事项列表 */}
    </div>
  );
}

3. 面试题

题目3:比较Redux和MobX的区别

JavaScript
// 答案要点:
// 1. Redux是单向数据流,MobX是响应式数据
// 2. Redux使用纯函数reducer,MobX使用可变状态
// 3. Redux需要大量样板代码,MobX代码更简洁
// 4. Redux更易于调试,MobX更易于理解
// 5. Redux适合大型应用,MobX适合中小型应用

🎯 本章总结

本章节全面介绍了前端状态管理的各种方案,包括Redux、MobX、Zustand、Recoil、XState等。关键要点:

  1. 状态管理概念:理解状态管理的必要性和适用场景
  2. Redux:掌握Redux Toolkit和最佳实践
  3. MobX:掌握响应式状态管理
  4. Zustand:掌握轻量级状态管理
  5. Recoil:掌握原子状态管理
  6. XState:掌握状态机在状态管理中的应用
  7. 方案选型:根据项目需求选择合适的状态管理方案

下一步将深入学习前端性能优化技术。