Next.js 教程系列(十六)Next.js 中的状态管理方案

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

第十六章:Next.js 中的状态管理方案

理论讲解

1. Next.js 应用中的状态管理需求

  • Next.js 支持 SSR、SSG、CSR、ISR 等多种渲染模式,状态管理需兼容服务端与客户端。
  • 企业级项目常见状态类型:全局状态(如用户信息、主题、权限)、局部状态(如表单、弹窗)、异步数据(如接口数据、WebSocket 实时数据)、持久化状态(如本地存储、Cookie)。
  • 状态管理的挑战:服务端与客户端同步、性能优化、可维护性、团队协作、类型安全、持久化、与后端协作。

2. 主流状态管理方案对比

  • Context API:React 内置,适合简单全局状态,性能有限。
  • Redux Toolkit:企业级标准,强大生态,支持中间件、持久化、DevTools,适合复杂业务。
  • Zustand:轻量、极简、TypeScript 友好,天然支持 SSR/Next.js,适合中小型项目和局部全局状态。
  • Recoil:原子化状态,适合复杂组件树,易于组合。
  • Jotai/MobX:响应式、极简,适合特定场景。
  • SWR/React Query:专注异步数据获取与缓存,适合接口数据管理。
  • 推荐组合:Zustand/Redux 管理全局业务状态,SWR/React Query 管理异步数据。

3. 状态持久化与服务端协作

  • 状态持久化:localStorage、sessionStorage、IndexedDB、Cookie,常用于主题、Token、草稿等。
  • SSR/SSG/ISR 场景下,需注意服务端与客户端状态同步。
  • 状态与 Server Actions、API Routes 协作:如全局状态变更后自动刷新页面、推送通知等。

4. 企业级状态管理最佳实践

  • 状态拆分:全局/局部/异步/持久化分层管理,避免"大一统"Store。
  • 类型安全:TypeScript 全面约束,减少运行时错误。
  • 性能优化:避免不必要的重渲染,按需订阅、懒加载、分片更新。
  • 团队协作:统一目录结构、命名规范、代码审查、自动化测试。
  • 状态监控与调试:集成 Redux DevTools、LogRocket、Sentry 等。

详细代码示例

1. 使用 Zustand 管理全局主题模式

ts 复制代码
// stores/theme.ts
import create from 'zustand';

type ThemeState = {
  mode: 'light' | 'dark';
  toggle: () => void;
};

export const useThemeStore = create<ThemeState>((set) => ({
  mode: 'light',
  toggle: () => set((state) => ({ mode: state.mode === 'light' ? 'dark' : 'light' })),
}));
tsx 复制代码
// components/ThemeToggle.tsx
import { useThemeStore } from '../stores/theme';

export default function ThemeToggle() {
  const mode = useThemeStore((s) => s.mode);
  const toggle = useThemeStore((s) => s.toggle);
  return (
    <button onClick={toggle} aria-label="切换主题" className="p-2">
      当前模式:{mode === 'light' ? '亮色' : '暗色'}
    </button>
  );
}

2. Redux Toolkit 管理复杂业务状态

ts 复制代码
// stores/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface UserState {
  name: string;
  role: string;
  token: string;
}

const initialState: UserState = { name: '', role: '', token: '' };

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser(state, action: PayloadAction<UserState>) {
      return { ...state, ...action.payload };
    },
    clearUser() {
      return initialState;
    },
  },
});

export const { setUser, clearUser } = userSlice.actions;
export default userSlice.reducer;
ts 复制代码
// stores/index.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

export const store = configureStore({
  reducer: { user: userReducer },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
tsx 复制代码
// pages/_app.tsx
import { Provider } from 'react-redux';
import { store } from '../stores';

export default function App({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

3. 状态持久化与 SSR 协作

ts 复制代码
// stores/theme.ts (持久化)
import create from 'zustand';
import { persist } from 'zustand/middleware';

export const useThemeStore = create(persist(
  (set) => ({
    mode: 'light',
    toggle: () => set((state) => ({ mode: state.mode === 'light' ? 'dark' : 'light' })),
  }),
  { name: 'theme-mode' }
));
tsx 复制代码
// pages/_document.tsx (SSR 同步主题)
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
  return (
    <Html>
      <Head />
      <body className="bg-white dark:bg-black">
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

4. Recoil 原子化状态管理

ts 复制代码
// stores/atoms.ts
import { atom } from 'recoil';

export const counterAtom = atom<number>({
  key: 'counter',
  default: 0,
});
tsx 复制代码
// components/Counter.tsx
import { useRecoilState } from 'recoil';
import { counterAtom } from '../stores/atoms';

export default function Counter() {
  const [count, setCount] = useRecoilState(counterAtom);
  return (
    <div>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

5. SWR/React Query 管理异步数据

tsx 复制代码
// components/UserProfile.tsx
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function UserProfile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);
  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>加载失败</div>;
  return <div>用户名:{data.name}</div>;
}

实战项目:实时通知中心

1. 需求分析

  • 全局管理消息数据,支持未读计数、消息标记为已读、WebSocket 实时推送。
  • 支持移动端适配、a11y、国际化、性能优化、错误处理。
  • 与后端/Server Actions 协作,消息状态持久化。

2. 目录结构

复制代码
stores/
  notification.ts
components/
  NotificationList.tsx
  NotificationBell.tsx
pages/
  dashboard.tsx
api/
  notifications.ts

3. 关键代码片段

  • 用 Zustand 管理全局消息状态,SWR 拉取历史消息,WebSocket 推送新消息。
  • 消息已读状态同步到后端,未读计数全局展示。
  • 组件响应式布局,支持移动端和无障碍。
ts 复制代码
// stores/notification.ts
import create from 'zustand';

type Notification = { id: string; content: string; read: boolean };
type NotificationState = {
  list: Notification[];
  unread: number;
  add: (n: Notification) => void;
  markRead: (id: string) => void;
  setList: (list: Notification[]) => void;
};

export const useNotificationStore = create<NotificationState>((set) => ({
  list: [],
  unread: 0,
  add: (n) => set((s) => ({ list: [n, ...s.list], unread: s.unread + 1 })),
  markRead: (id) => set((s) => {
    const list = s.list.map((n) => n.id === id ? { ...n, read: true } : n);
    return { list, unread: list.filter((n) => !n.read).length };
  }),
  setList: (list) => set({ list, unread: list.filter((n) => !n.read).length }),
}));
tsx 复制代码
// components/NotificationBell.tsx
import { useNotificationStore } from '../stores/notification';

export default function NotificationBell() {
  const unread = useNotificationStore((s) => s.unread);
  return (
    <button aria-label="通知" className="relative">
      <span>🔔</span>
      {unread > 0 && <span className="badge">{unread}</span>}
    </button>
  );
}
tsx 复制代码
// components/NotificationList.tsx
import { useNotificationStore } from '../stores/notification';

export default function NotificationList() {
  const list = useNotificationStore((s) => s.list);
  const markRead = useNotificationStore((s) => s.markRead);
  return (
    <ul>
      {list.map((n) => (
        <li key={n.id} className={n.read ? 'read' : 'unread'}>
          {n.content}
          {!n.read && <button onClick={() => markRead(n.id)}>标记为已读</button>}
        </li>
      ))}
    </ul>
  );
}

理论讲解(进阶扩展)

5. 状态提升与下沉、隔离与作用域

  • 状态提升:将局部状态提升到最近的公共父组件,实现兄弟组件通信。
  • 状态下沉:将全局状态拆分为局部状态,减少不必要的全局依赖。
  • 状态隔离:通过 Context、Provider、作用域 Store 实现多实例隔离(如多弹窗、嵌套表单)。
  • 作用域 Store:如 Recoil 的 Scope、Zustand 的 createStore,支持多租户/多实例。

6. 跨页面/多标签同步与一致性

  • 多标签/多窗口同步:用 BroadcastChannel、localStorage 事件、Service Worker 实现。
  • 状态一致性:如用户登出、Token 失效、全局通知等需多标签同步。
  • 示例:用户在A标签页登出,B标签页自动同步登出。

7. 状态安全、权限与国际化

  • 状态安全:Token/敏感信息仅存内存或 HttpOnly Cookie,防止XSS/CSRF。
  • 权限控制:全局状态中存储用户角色/权限,组件/路由按权限渲染。
  • 国际化/多租户:全局状态存储当前语言、租户ID,支持多语言/多租户切换。

8. 状态与异步流、缓存失效、灰度发布

  • WebSocket/SSE/GraphQL订阅:全局状态与实时数据流协作,自动推送/刷新。
  • 缓存失效/手动刷新:如 React Query/SWR 的 mutate、invalidateQueries。
  • 灰度发布/AB测试:全局状态存储实验分组,按分组渲染不同UI/功能。

复杂代码示例与企业级场景

6. 多标签/多窗口状态同步

ts 复制代码
// hooks/useBroadcast.ts
import { useEffect } from 'react';
export function useBroadcast(key: string, onMessage: (data: any) => void) {
  useEffect(() => {
    const bc = new BroadcastChannel(key);
    bc.onmessage = (e) => onMessage(e.data);
    return () => bc.close();
  }, [key, onMessage]);
}

// 在全局状态变更时同步到其他标签页
useBroadcast('auth', (data) => {
  if (data.type === 'logout') {
    // 清理本地状态
  }
});

7. 状态与权限控制

tsx 复制代码
// components/ProtectedButton.tsx
import { useSelector } from 'react-redux';
export default function ProtectedButton({ role, ...props }) {
  const userRole = useSelector((s) => s.user.role);
  if (userRole !== role) return null;
  return <button {...props} />;
}

8. 状态与国际化/多租户

ts 复制代码
// stores/i18n.ts
import create from 'zustand';
export const useI18nStore = create((set) => ({
  lang: 'zh',
  setLang: (lang) => set({ lang }),
}));

// 切换语言
useI18nStore.getState().setLang('en');

9. 状态与WebSocket实时协作

ts 复制代码
// hooks/useWebSocket.ts
import { useEffect } from 'react';
export function useWebSocket(url, onMessage) {
  useEffect(() => {
    const ws = new WebSocket(url);
    ws.onmessage = (e) => onMessage(JSON.parse(e.data));
    return () => ws.close();
  }, [url, onMessage]);
}

// 在通知中心中实时推送新消息
useWebSocket('wss://api/ws', (msg) => useNotificationStore.getState().add(msg));

10. 状态与缓存失效/手动刷新

tsx 复制代码
// components/RefreshButton.tsx
import { mutate } from 'swr';
export default function RefreshButton() {
  return <button onClick={() => mutate('/api/user')}>手动刷新用户数据</button>;
}

11. 状态与灰度发布/AB测试

ts 复制代码
// stores/experiment.ts
import create from 'zustand';
export const useExperimentStore = create(() => ({
  group: Math.random() > 0.5 ? 'A' : 'B',
}));

// 组件内按分组渲染
const group = useExperimentStore((s) => s.group);
return group === 'A' ? <NewUI /> : <OldUI />;

12. 状态自动化测试

js 复制代码
// __tests__/themeStore.test.ts
import { act } from 'react-dom/test-utils';
import { useThemeStore } from '../stores/theme';

test('主题切换', () => {
  act(() => {
    useThemeStore.getState().toggle();
  });
  expect(useThemeStore.getState().mode).toBe('dark');
});

企业级实战案例(扩展)

1. 多标签同步通知中心

  • 用户在任一标签页操作(如登出、消息已读),所有标签页自动同步。
  • BroadcastChannel/Service Worker 实现全局状态同步。

2. 权限分级的全局状态管理

  • 不同角色/租户拥有不同菜单、按钮、数据权限。
  • 全局状态存储权限,组件/路由按权限渲染。

3. 国际化与多租户状态管理

  • 全局状态存储当前语言、租户ID,支持多语言/多租户切换。
  • 状态与后端协作,切换时自动刷新数据。

4. 状态与Serverless/微服务集成

  • 状态变更后自动推送到微服务/Serverless,支持实时通知、全局刷新。

5. 状态与灰度发布/AB测试

  • 全局状态存储实验分组,按分组渲染不同UI/功能,支持动态切换。

最佳实践(扩展)

  • 安全:Token/敏感信息仅存内存或 HttpOnly Cookie,状态变更需鉴权。
  • 性能:多标签同步用 BroadcastChannel,WebSocket/SSE 实时推送,缓存失效及时刷新。
  • 协作:统一状态管理方案、目录结构、命名规范,关键状态流程自动化测试。
  • 监控:状态异常、同步失败、权限越权等日志采集与告警。
  • 合规:多租户/国际化状态隔离,敏感信息加密,符合GDPR/等保。

常见问题与解决方案(扩展)

Q6: 多标签/多窗口状态如何同步?

A: 用 BroadcastChannel、localStorage 事件、Service Worker 实现,关键状态变更时广播消息。

Q7: 如何防止状态泄露与权限越权?

A: Token/敏感信息仅存内存/HttpOnly Cookie,组件/路由按权限渲染,后端二次校验。

Q8: 状态与国际化/多租户如何协作?

A: 全局状态存储当前语言/租户ID,切换时自动刷新数据,后端接口支持多语言/多租户。

Q9: 状态与WebSocket/实时推送如何集成?

A: 全局状态与 WebSocket/SSE/GraphQL 订阅协作,推送新数据自动更新 Store。

Q10: 状态与灰度发布/AB测试如何落地?

A: 全局状态存储实验分组,按分组渲染不同UI/功能,支持动态切换与数据分析。


配图说明(扩展)

graph TD; A[后端/Server Actions] --> B[全局状态 Store] B --> C[组件订阅渲染] B --> D[本地持久化/多标签同步] C --> E[用户操作] E --> B B --> F[权限/国际化/多租户分流] B --> G[WebSocket/实时推送] B --> H[灰度发布/AB测试分组]

Next.js 状态管理多标签同步、权限分级、国际化、灰度发布、实时推送等全流程示意图。
最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax