You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14中使用Zustand实现布局数据共享至客户端组件的问题

Next.js 14 服务器组件数据传递到 Zustand 客户端状态解决方案

核心问题分析

Zustand 是客户端状态管理库,默认无法直接读取服务器组件的数据。你遇到的问题根源在于:服务器组件获取的数据没有正确初始化客户端的 Zustand store,导致客户端组件拿到空数组;同时服务器组件重复请求数据可以通过数据透传来避免。


步骤1:创建支持初始值的 Zustand Store

需要封装一个可接收初始数据的 store 工厂函数,并配合 React Context 让客户端组件能访问到 store 实例(因为服务器组件不能直接初始化 Zustand store)。

// app/store/products.ts
import { create } from 'zustand';
import { createContext, useContext } from 'react';

// 定义产品类型
type Product = { id: string; name: string; price: number };

// Store 状态类型
type ProductStore = {
  products: Product[];
  setProducts: (products: Product[]) => void;
};

// 创建 Store 的工厂函数,接收初始数据
const createProductStore = (initialProducts: Product[]) => {
  return create<ProductStore>((set) => ({
    products: initialProducts,
    setProducts: (newProducts) => set({ products: newProducts }),
  }));
};

// 创建 Context 用于传递 Store 实例
const ProductStoreContext = createContext<ReturnType<typeof createProductStore> | null>(null);

// 客户端 Provider 组件(必须标记为 use client)
'use client';
export const ProductStoreProvider = ({ 
  children, 
  initialProducts 
}: { 
  children: React.ReactNode; 
  initialProducts: Product[] 
}) => {
  const store = createProductStore(initialProducts);
  return (
    <ProductStoreContext.Provider value={store}>
      {children}
    </ProductStoreContext.Provider>
  );
};

// 客户端组件使用的 Hook
export const useProductStore = () => {
  const store = useContext(ProductStoreContext);
  if (!store) throw new Error('useProductStore 必须在 ProductStoreProvider 内部使用');
  return store;
};

步骤2:在服务器组件 Layout 中获取数据并注入 Provider

在根布局(服务器组件)中获取数据,然后用客户端的 ProductStoreProvider 包裹整个应用,将初始数据传递进去。

// app/layout.tsx
import { ProductStoreProvider } from './store/products';

// 服务器端数据请求函数
async function fetchProducts() {
  const res = await fetch('https://api.example.com/products', { 
    cache: 'force-cache' // 根据业务需求调整缓存策略,比如 'no-store' 禁用缓存
  });
  if (!res.ok) throw new Error('获取产品数据失败');
  return res.json();
}

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // 服务器端获取初始数据
  const initialProducts = await fetchProducts();

  return (
    <html lang="zh-CN">
      <body>
        {/* 将初始数据传递给客户端 Provider */}
        <ProductStoreProvider initialProducts={initialProducts}>
          {children}
        </ProductStoreProvider>
      </body>
    </html>
  );
}

步骤3:客户端组件读取 Zustand Store

在客户端组件中直接使用 useProductStore 获取数据,此时初始数据已经从服务器传递过来,不会是空数组。

// app/components/ClientOverview.tsx
'use client';
import { useProductStore } from '../store/products';

export default function ClientOverview() {
  const products = useProductStore((state) => state.products);

  return (
    <div className="product-list">
      <h3>产品概览</h3>
      {products.length === 0 ? (
        <p>暂无产品数据</p>
      ) : (
        products.map((product) => (
          <div key={product.id} className="product-item">
            {product.name} - ¥{product.price}
          </div>
        ))
      )}
    </div>
  );
}

步骤4:避免服务器组件重复请求数据

如果你的 page.tsx 也是服务器组件,不需要重复请求数据,可以通过布局透传 props 的方式将数据传递给页面:

修改 layout.tsx 中的 children 部分:

// app/layout.tsx
// ...
return (
  <html lang="zh-CN">
    <body>
      <ProductStoreProvider initialProducts={initialProducts}>
        {/* 将初始数据透传给页面组件 */}
        {React.cloneElement(children as React.ReactElement, { initialProducts })}
      </ProductStoreProvider>
    </body>
  </html>
);

然后在 page.tsx 中直接使用透传的数据:

// app/page.tsx
import ClientOverview from './components/ClientOverview';

type Product = { id: string; name: string; price: number };

export default function HomePage({ initialProducts }: { initialProducts: Product[] }) {
  // 直接使用布局传递的数据,无需重复请求
  return (
    <main>
      <h1>首页</h1>
      <ClientOverview />
    </main>
  );
}

关键注意事项

  1. Zustand Store 必须在客户端初始化:服务器组件不能直接创建 Zustand store,必须通过标记 use client 的 Provider 组件来初始化。
  2. 初始数据从服务器透传:通过服务器组件布局获取数据,传递给客户端 Provider,确保客户端 store 一开始就有初始值。
  3. 服务器请求缓存:Next.js 14 中服务器组件的 fetch 默认会缓存请求结果,即使多个组件调用同一个请求函数,也只会发起一次请求;透传 props 则进一步避免了不必要的函数调用。

内容的提问来源于stack exchange,提问作者StackMyFlow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:19:50