在 Next.js 中配置
NOTE
我们很快会根据 https://github.com/pmndrs/zustand/discussions/2740 中的讨论更新本指南。
Next.js 是 React 生态中流行的服务端渲染框架,它在正确使用 Zustand 时会
带来一些独特的挑战。
请记住,Zustand store 是一个全局
变量(又称模块状态,module state),因此 Context 是可选的。
这些挑战包括:
- 每个请求独立的 store: Next.js 服务端可以同时处理多个请求。这意味着 store 应当按请求创建,不应在请求之间共享。
- 对 SSR 友好: Next.js 应用会被渲染两次,先在服务端渲染 一次,再在客户端渲染一次。客户端与服务端的输出不一致会导致 “水合错误(hydration error)”。为避免这种情况,store 必须在服务端初始化, 然后在客户端用相同的数据重新初始化。更多内容请阅读 SSR 与水合 指南。
- 对 SPA 路由友好: Next.js 支持客户端路由的混合模式,这意味着
要重置某个 store,我们需要在组件层面使用
Context来初始化它。 - 对服务端缓存友好: 较新版本的 Next.js(特别是使用 App Router 架构的应用)支持激进的服务端缓存。由于我们的 store 是模块状态, 它与这种缓存完全兼容。
对于如何正确使用 Zustand,我们有下面这些通用建议:
- 不要使用全局 store —— 因为 store 不应在请求之间共享,所以不应把它定义 为全局变量。相反,store 应当按请求创建。
- React Server Components 不应读取或写入 store —— RSC 不能使用 Hook 或上下文。它们 本来就不该是有状态的。让 RSC 读取全局 store 或向其写入值,违反了 Next.js 的架构。
按请求创建 store
下面我们来编写 store 工厂函数,它会为每个 请求创建一个新的 store。
注意: 不要忘记删除
tsconfig.json文件中的所有注释。
初始化 store
提供 store
我们在组件中使用 createCounterStore,并通过上下文 Provider 共享它。
注意: 在这个示例中,我们通过检查引用的值来确保该组件在重新渲染时是安全的, 从而让 store 只被创建一次。这个组件在服务端每个请求 只会渲染一次,但在客户端可能会被多次重新渲染,比如 当组件树中该组件之上存在有状态的客户端组件,或者该组件 本身还包含其他会导致重新渲染的可变状态时。
在不同架构中使用 store
Next.js 应用有两种架构:Pages Router 和 App Router。 在两种架构中使用 Zustand 的方式应当相同,只是与各自架构相关的细节略有差异。
Pages Router
注意: 按路由创建 store 需要在页面(路由)组件层面创建并共享 store。如果你不需要为每个路由创建 store, 请尽量不要这样做。
App Router
注意: 按路由创建 store 需要在页面(路由)组件层面创建并共享 store。如果你不需要为每个路由创建 store, 请尽量不要这样做。