Skip to content

前端状态管理指南

状态管理概览

状态管理是现代前端应用的核心挑战之一。随着应用规模的增长,组件间的状态共享、同步和更新变得越来越复杂。选择合适的状态管理方案对于应用的可维护性、性能和开发效率至关重要。

状态管理分类

1. 本地状态 (Local State)

  • 定义: 单个组件内部的状态
  • 使用场景: 表单输入、UI状态、临时数据
  • 实现: useState、useReducer、组件state

2. 共享状态 (Shared State)

  • 定义: 多个组件需要访问的状态
  • 使用场景: 用户信息、主题设置、全局配置
  • 实现: Context API、状态管理库

3. 服务器状态 (Server State)

  • 定义: 从服务器获取的数据
  • 使用场景: API数据、缓存、同步
  • 实现: React Query、SWR、Apollo Client

4. 表单状态 (Form State)

  • 定义: 表单相关的复杂状态
  • 使用场景: 表单验证、字段数组、异步验证
  • 实现: React Hook Form、Formik、Final Form

React状态管理方案

useState和useReducer

useState基础用法

javascript
import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: '', email: '' });

  const increment = () => setCount(prev => prev + 1);
  const updateUser = (field, value) => 
    setUser(prev => ({ ...prev, [field]: value }));

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      
      <input 
        value={user.name}
        onChange={(e) => updateUser('name', e.target.value)}
        placeholder="Name"
      />
    </div>
  );
}

useReducer复杂状态

javascript
import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  loading: false,
  error: null,
};

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    case 'SET_ERROR':
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      {state.loading && <p>Loading...</p>}
      {state.error && <p>Error: {state.error}</p>}
    </div>
  );
}

Context API

基础Context

javascript
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// 使用
function App() {
  return (
    <ThemeProvider>
      <Header />
    </ThemeProvider>
  );
}

function Header() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <header style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'} theme
      </button>
    </header>
  );
}

优化Context性能

javascript
// 分离Context避免不必要的重渲染
const ThemeStateContext = createContext();
const ThemeDispatchContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const dispatch = useMemo(() => ({
    setTheme,
    toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light'),
  }), []);

  return (
    <ThemeStateContext.Provider value={theme}>
      <ThemeDispatchContext.Provider value={dispatch}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeStateContext.Provider>
  );
}

Redux Toolkit

Redux Toolkit是Redux官方推荐的现代Redux开发方式,简化了Redux的使用。

安装配置

bash
npm install @reduxjs/toolkit react-redux

创建Store

javascript
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import postsReducer from './slices/postsSlice';

export const store = configureStore({
  reducer: {
    user: userReducer,
    posts: postsReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST'],
      },
    }),
  devTools: process.env.NODE_ENV !== 'production',
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

创建Slice

javascript
// store/slices/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { userApi } from '../../api/user';

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId) => {
    const response = await userApi.getUser(userId);
    return response.data;
  }
);

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

export const { setUser, clearUser } = userSlice.actions;
export default userSlice.reducer;

使用Redux

javascript
// components/UserProfile.js
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, setUser } from '../store/slices/userSlice';

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

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

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!currentUser) return <div>No user found</div>;

  return (
    <div>
      <h1>{currentUser.name}</h1>
      <p>{currentUser.email}</p>
      <button onClick={() => dispatch(setUser(null))}>
        Clear User
      </button>
    </div>
  );
}

Zustand

Zustand是一个轻量级的状态管理库,提供了简单直观的API。

安装配置

bash
npm install zustand

创建Store

javascript
// store/useStore.js
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

const useStore = create(
  devtools(
    persist(
      (set, get) => ({
        count: 0,
        user: null,
        
        increment: () => set((state) => ({ count: state.count + 1 })),
        decrement: () => set((state) => ({ count: state.count - 1 })),
        reset: () => set({ count: 0 }),
        
        setUser: (user) => set({ user }),
        clearUser: () => set({ user: null }),
        
        // 异步操作
        fetchUser: async (userId) => {
          const response = await fetch(`/api/users/${userId}`);
          const user = await response.json();
          set({ user });
        },
      }),
      {
        name: 'app-storage',
        partialize: (state) => ({ user: state.user }),
      }
    )
  )
);

export default useStore;

使用Zustand

javascript
// components/Counter.js
import React from 'react';
import useStore from '../store/useStore';

function Counter() {
  const { count, increment, decrement, reset } = useStore(
    (state) => ({
      count: state.count,
      increment: state.increment,
      decrement: state.decrement,
      reset: state.reset,
    })
  );

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

Recoil

Recoil是Facebook开发的实验性状态管理库,专为React设计。

安装配置

bash
npm install recoil

基本用法

javascript
// atoms.js
import { atom, selector } from 'recoil';

export const todoListState = atom({
  key: 'todoListState',
  default: [],
});

export const todoListFilterState = atom({
  key: 'todoListFilterState',
  default: 'Show All',
});

export const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({ get }) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

使用Recoil

javascript
// components/TodoList.js
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { todoListState, filteredTodoListState } from '../atoms';

function TodoList() {
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const filteredList = useRecoilValue(filteredTodoListState);

  const addTodo = (text) => {
    setTodoList((oldList) => [
      ...oldList,
      {
        id: Date.now(),
        text,
        isComplete: false,
      },
    ]);
  };

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

Vue状态管理

Vuex

Vuex是Vue.js的官方状态管理库。

安装配置

bash
npm install vuex@next

创建Store

javascript
// store/index.js
import { createStore } from 'vuex';

export default createStore({
  state: {
    count: 0,
    user: null,
    posts: [],
  },
  getters: {
    doubleCount: (state) => state.count * 2,
    getPostById: (state) => (id) => 
      state.posts.find(post => post.id === id),
  },
  mutations: {
    INCREMENT(state) {
      state.count++;
    },
    SET_USER(state, user) {
      state.user = user;
    },
    SET_POSTS(state, posts) {
      state.posts = posts;
    },
  },
  actions: {
    async fetchPosts({ commit }) {
      try {
        const response = await fetch('/api/posts');
        const posts = await response.json();
        commit('SET_POSTS', posts);
      } catch (error) {
        console.error('Error fetching posts:', error);
      }
    },
    async login({ commit }, credentials) {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      });
      const user = await response.json();
      commit('SET_USER', user);
    },
  },
  modules: {
    auth: {
      namespaced: true,
      state: { token: null },
      mutations: {
        SET_TOKEN(state, token) {
          state.token = token;
        },
      },
    },
  },
});

使用Vuex

javascript
// components/Counter.vue
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="fetchPosts">Fetch Posts</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';

export default {
  computed: {
    ...mapState(['count']),
    ...mapGetters(['doubleCount']),
  },
  methods: {
    ...mapMutations(['INCREMENT']),
    ...mapActions(['fetchPosts']),
    increment() {
      this.INCREMENT();
    },
  },
};
</script>

Pinia

Pinia是Vue的新一代状态管理库,提供了更好的TypeScript支持和组合式API。

安装配置

bash
npm install pinia

创建Store

javascript
// stores/counter.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);
  const doubleCount = computed(() => count.value * 2);
  
  function increment() {
    count.value++;
  }
  
  function decrement() {
    count.value--;
  }
  
  return { count, doubleCount, increment, decrement };
});

// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    preferences: {
      theme: 'light',
      language: 'en',
    },
  }),
  getters: {
    isLoggedIn: (state) => !!state.user,
    fullName: (state) => state.user ? `${state.user.firstName} ${state.user.lastName}` : '',
  },
  actions: {
    async login(credentials) {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      });
      this.user = await response.json();
    },
    logout() {
      this.user = null;
    },
    updatePreferences(preferences) {
      this.preferences = { ...this.preferences, ...preferences };
    },
  },
});

服务器状态管理

React Query (TanStack Query)

React Query专门用于处理服务器状态,提供了缓存、同步、后台更新等功能。

安装配置

bash
npm install @tanstack/react-query

基本用法

javascript
// App.js
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5分钟
      cacheTime: 1000 * 60 * 10, // 10分钟
      retry: 3,
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

使用Query

javascript
// hooks/usePosts.js
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { postsApi } from '../api/posts';

export function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: postsApi.getAll,
    select: (data) => data.sort((a, b) => b.createdAt - a.createdAt),
  });
}

export function usePost(id) {
  return useQuery({
    queryKey: ['posts', id],
    queryFn: () => postsApi.getById(id),
    enabled: !!id,
  });
}

export function useCreatePost() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: postsApi.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
    onError: (error) => {
      console.error('Error creating post:', error);
    },
  });
}

export function useUpdatePost() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: ({ id, data }) => postsApi.update(id, data),
    onSuccess: (data) => {
      queryClient.setQueryData(['posts', data.id], data);
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });
}

使用Hook

javascript
// components/PostsList.js
import React from 'react';
import { usePosts, useCreatePost } from '../hooks/usePosts';

function PostsList() {
  const { data: posts, isLoading, error } = usePosts();
  const createPost = useCreatePost();

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

  return (
    <div>
      <button 
        onClick={() => createPost.mutate({ title: 'New Post', content: 'Content' })}
        disabled={createPost.isPending}
      >
        Create Post
      </button>
      
      {posts?.map(post => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.content}</p>
        </div>
      ))}
    </div>
  );
}

表单状态管理

React Hook Form

React Hook Form提供了高性能的表单状态管理,减少了重新渲染。

安装配置

bash
npm install react-hook-form

基本用法

javascript
import React from 'react';
import { useForm } from 'react-hook-form';

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch,
    reset,
  } = useForm({
    defaultValues: {
      email: '',
      password: '',
      rememberMe: false,
    },
  });

  const onSubmit = async (data) => {
    try {
      await loginUser(data);
      reset();
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Email:</label>
        <input
          {...register('email', {
            required: 'Email is required',
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
              message: 'Invalid email address',
            },
          })}
        />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <label>Password:</label>
        <input
          type="password"
          {...register('password', {
            required: 'Password is required',
            minLength: {
              value: 6,
              message: 'Password must be at least 6 characters',
            },
          })}
        />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <div>
        <label>
          <input type="checkbox" {...register('rememberMe')} />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

复杂表单示例

javascript
// 动态字段数组
import { useFieldArray } from 'react-hook-form';

function DynamicForm() {
  const { control, register, handleSubmit } = useForm({
    defaultValues: {
      name: '',
      emails: [{ value: '' }],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'emails',
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      
      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`emails.${index}.value`)}
            placeholder="Email"
          />
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}
      
      <button type="button" onClick={() => append({ value: '' })}>
        Add Email
      </button>
      
      <button type="submit">Submit</button>
    </form>
  );
}

状态管理最佳实践

1. 状态分类策略

javascript
// 状态分类示例
const stateCategories = {
  // 本地状态
  local: {
    formInputs: useState,
    uiState: useState,
  },
  
  // 共享状态
  shared: {
    user: useContext,
    theme: useContext,
  },
  
  // 服务器状态
  server: {
    posts: useQuery,
    userProfile: useQuery,
  },
  
  // 表单状态
  form: {
    validation: useForm,
    fieldArrays: useFieldArray,
  },
};

2. 性能优化

javascript
// 避免不必要的重渲染
const MemoizedComponent = React.memo(({ data }) => {
  const processedData = useMemo(() => 
    expensiveOperation(data), [data]
  );
  
  return <div>{processedData}</div>;
});

// 分离Context避免连锁重渲染
const StateContext = createContext();
const DispatchContext = createContext();

3. 类型安全

typescript
// TypeScript状态定义
interface UserState {
  user: User | null;
  loading: boolean;
  error: string | null;
}

interface AppState {
  user: UserState;
  posts: Post[];
  ui: {
    theme: 'light' | 'dark';
    sidebarOpen: boolean;
  };
}

4. 测试策略

javascript
// 状态管理测试示例
import { renderHook, act } from '@testing-library/react';
import { useCounterStore } from '../store/useCounterStore';

describe('useCounterStore', () => {
  it('should increment count', () => {
    const { result } = renderHook(() => useCounterStore());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  it('should handle async actions', async () => {
    const { result } = renderHook(() => useUserStore());
    
    await act(async () => {
      await result.current.fetchUser(1);
    });
    
    expect(result.current.user).toBeDefined();
  });
});

5. 调试工具

Redux DevTools

javascript
// Redux DevTools配置
import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(thunk)
  )
);

React DevTools Profiler

javascript
// 性能分析
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Duration:', actualDuration);
}

<Profiler id="App" onRender={onRenderCallback}>
  <App />
</Profiler>

选择指南

状态管理选择矩阵

场景推荐方案理由
小型应用useState + Context简单直接
中型应用Zustand轻量高效
大型应用Redux Toolkit生态完善
复杂表单React Hook Form专业表单管理
服务器状态React Query缓存优化
Vue应用Pinia官方推荐

迁移策略

从Redux迁移到Zustand

javascript
// 逐步迁移
const migrationStrategy = {
  phase1: '保持Redux,新增功能使用Zustand',
  phase2: '将简单状态迁移到Zustand',
  phase3: '复杂状态保持Redux,简单状态用Zustand',
  phase4: '完全迁移(可选)',
};

总结

选择合适的状态管理方案需要考虑:

  • 应用规模: 小型应用使用简单方案,大型应用使用完整方案
  • 团队经验: 选择团队熟悉的技术栈
  • 性能需求: 考虑重渲染和内存使用
  • 开发效率: 平衡学习成本和开发效率
  • 生态支持: 考虑社区和工具支持

最佳实践是:从简单开始,按需演进,避免过度设计。通过合理的状态分类和分层管理,可以构建出高性能、易维护的前端应用。

别急,先让缓存热一下。