跳转到内容

Next.js

Next.js 集成的关键是:每请求创建状态、只传可序列化数据、客户端重建 store。

  • App Router 下的 Server + Client 组合页面。
  • 需要服务端预取数据并首屏渲染。
  • 需要避免跨请求共享状态导致串数据。
  • 每请求 Store:不要在模块顶层创建全局 store。
  • Hydrate 边界:只传 plain object,不传 store 实例。
  • 传值流向:server fetch -> initialState -> client createStore

不要在模块顶层放全局 store:

// bad: 整个 Node 进程共享
export const store = io({ count: 0 });

改成工厂函数:

lib/store.ts
import { io } from '@iostore/store';
export type AppState = {
count: number;
user: { id: string; name: string } | null;
};
export function createStore(initial: AppState) {
return io(initial);
}
// app/dashboard/page.tsx (Server Component)
import { createStore } from '@/lib/store';
import { DashboardClient } from './dashboard-client';
export default async function Page() {
const user = await fetch('https://example.com/api/me').then((r) => r.json());
const serverStore = createStore({ count: 0, user });
return <DashboardClient initialState={serverStore.snapshot()} />;
}

规则:边界只传 initialState 这种可序列化对象。

app/dashboard/dashboard-client.tsx
'use client';
import { useRef } from 'react';
import { useIO } from '@iostore/react';
import { createStore, type AppState } from '@/lib/store';
export function DashboardClient({ initialState }: { initialState: AppState }) {
const storeRef = useRef<ReturnType<typeof createStore> | null>(null);
if (!storeRef.current) {
storeRef.current = createStore(initialState);
}
const count = useIO(storeRef.current.count);
return <button onClick={() => storeRef.current?.count.set((v) => v + 1)}>{count}</button>;
}
阶段API说明
创建初始状态io(initialState)每请求创建,避免跨请求污染。
生成边界数据scope.snapshot()产出可序列化初始数据。
客户端重建createStore(initialState)Hydration 后恢复运行时能力。
组件订阅useIO(unitOrDerived)在 Client Component 中读取。
客户端写入`unit.set(nextupdater)`
// 错误:模块级单例(SSR 会跨请求共享)
export const store = io({ count: 0 });
// 推荐:工厂函数按请求创建
export const createStore = (initial: AppState) => io(initial);
// 错误:传 store 实例到 Client Component
// <Client store={serverStore} />
// 推荐:只传 serializable initialState
// <Client initialState={serverStore.snapshot()} />
  • io(...) 实例作为 props 传到 Client Component。
  • 模块级单例 store 导致多用户状态串扰。
  • 首屏数据与客户端初始化数据不一致导致 hydration 警告。
  • 用类型约束 initialState,避免边界数据漂移。
  • 复杂页面按路由或业务模块拆分多个 store。
  • 首屏后再接入客户端行为扩展(如 persist)。
  • 结合 ReactuseIOSelector 做细粒度订阅。
  • Persist 管理客户端持久化。