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

Next.js 13/14 App Router服务端组件向客户端组件传值方法

Next.js 13/14 App Router 服务端到客户端组件的状态传递方案

在App Router中,服务端组件无法直接使用Redux这类客户端状态库,但有几个更贴合Next.js设计的方案,既能传递初始服务端数据,又能支持客户端修改:

方案1:Props传递初始值 + React Context管理客户端状态

这是最基础的原生方案,适合需要在多个客户端组件间共享修改后状态的场景。

步骤:

  1. 服务端组件将获取到的city作为props传给客户端组件
  2. 创建客户端Context,统一管理city的状态和修改方法
  3. 客户端组件通过Context获取、展示并修改city

代码修改示例:

1. 创建Context文件(app/contexts/LocationContext.tsx)

"use client";

import { createContext, useContext, useState, ReactNode } from "react";

type LocationContextType = {
  city: string;
  setCity: (newCity: string) => void;
};

const LocationContext = createContext<LocationContextType | undefined>(undefined);

export function LocationProvider({ 
  children, 
  initialCity 
}: { 
  children: ReactNode; 
  initialCity: string 
}) {
  const [city, setCity] = useState(initialCity);

  return (
    <LocationContext.Provider value={{ city, setCity }}>
      {children}
    </LocationContext.Provider>
  );
}

export function useLocation() {
  const context = useContext(LocationContext);
  if (!context) {
    throw new Error("useLocation must be used within a LocationProvider");
  }
  return context;
}

2. 修改服务端组件page.tsx,包裹Provider并传递初始值

import { LocationProvider } from '@/app/contexts/LocationContext';
import Navbar from '@/app/components/Navbar'; // 假设Navbar包含Location组件

const Page = async () => {
  const city = await getCity();
  const hotels = await getHotelsByCity({ city });

  return (
    <LocationProvider initialCity={city}>
      <Navbar />
      <div className="pt-24 md:pt-28 flex md:flex-row md:flex-wrap flex-col">
        {hotels?.map((hotel, index) => (
          <div key={index}>{hotel}</div>
        ))}
      </div>
    </LocationProvider>
  );
};

export default Page;

3. 修改客户端组件Location.tsx,使用Context获取和修改city

"use client";

import useSelectLocationModal from "@/app/hooks/useSelectLocationModal";
import { useLocation } from "@/app/contexts/LocationContext";

export default function Location() {
  const selectLocationModal = useSelectLocationModal();
  const { city, setCity } = useLocation();

  // 假设模态框选择新城市后调用此方法更新状态
  const handleCityChange = (newCity: string) => {
    setCity(newCity);
    // 若需重新获取酒店数据,可结合后续方案的Server Actions实现
  };

  return (
    <div
      className="flex items-center cursor-pointer"
      onClick={() => selectLocationModal.onOpen(handleCityChange)}
    >
      <p>{city}</p>
    </div>
  );
}

方案2:Props传递初始值 + Server Actions(Next.js 13.4+ / 14推荐)

如果修改城市后需要重新调用服务端API(比如刷新对应城市的酒店列表),Server Actions是官方推荐的方案,无需额外状态库,直接在客户端触发服务端逻辑。

步骤:

  1. 服务端组件传递初始city给客户端组件
  2. 创建Server Action用于更新城市并重新获取酒店数据
  3. 客户端组件使用useTransition处理状态更新,避免页面跳转

代码修改示例:

1. 创建Server Action(app/actions/hotelActions.ts)

"use server";

import { revalidatePath } from "next/cache";

export async function updateCityAndFetchHotels(newCity: string) {
  // 重新获取新城市的酒店数据
  const hotels = await getHotelsByCity({ city: newCity });
  
  // 重新验证首页路径,刷新服务端缓存数据
  revalidatePath("/");
  
  return hotels;
}

2. 修改page.tsx传递初始city

import Navbar from '@/app/components/Navbar';

const Page = async () => {
  const city = await getCity();
  const hotels = await getHotelsByCity({ city });

  return (
    <>
      <Navbar initialCity={city} />
      <div className="pt-24 md:pt-28 flex md:flex-row md:flex-wrap flex-col">
        {hotels?.map((hotel, index) => (
          <div key={index}>{hotel}</div>
        ))}
      </div>
    </>
  );
};

export default Page;

3. 修改客户端组件Location.tsx,使用Server Action

"use client";

import useSelectLocationModal from "@/app/hooks/useSelectLocationModal";
import { updateCityAndFetchHotels } from "@/app/actions/hotelActions";
import { useState, useTransition } from "react";

export default function Location({ initialCity }: { initialCity: string }) {
  const selectLocationModal = useSelectLocationModal();
  const [city, setCity] = useState(initialCity);
  const [isPending, startTransition] = useTransition();

  const handleCityChange = (newCity: string) => {
    startTransition(async () => {
      await updateCityAndFetchHotels(newCity);
      setCity(newCity);
    });
  };

  return (
    <div
      className="flex items-center cursor-pointer"
      onClick={() => selectLocationModal.onOpen(handleCityChange)}
    >
      <p>{isPending ? "更新中..." : city}</p>
    </div>
  );
}

方案3:TanStack Query(React Query)管理远程状态

如果应用有大量数据获取、缓存、同步需求,TanStack Query比Redux更轻量,专门处理服务端数据状态,同时支持客户端修改后同步。

步骤:

  1. 配置TanStack Query客户端
  2. 服务端组件预获取初始city,传递给客户端组件作为初始查询参数
  3. 客户端组件使用useQuery获取酒店数据,useMutation修改城市并刷新数据

代码修改示例:

1. 配置TanStack Query(app/providers.tsx)

"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

2. 修改page.tsx,包裹Providers并传递初始city

import Providers from '@/app/providers';
import Navbar from '@/app/components/Navbar';
import HotelList from '@/app/components/HotelList';

const Page = async () => {
  const initialCity = await getCity();

  return (
    <Providers>
      <Navbar initialCity={initialCity} />
      <HotelList initialCity={initialCity} />
    </Providers>
  );
};

export default Page;

3. 修改客户端组件Location.tsx

"use client";

import useSelectLocationModal from "@/app/hooks/useSelectLocationModal";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import getHotelsByCity from "@/app/services/hotelService";

export default function Location({ initialCity }: { initialCity: string }) {
  const selectLocationModal = useSelectLocationModal();
  const queryClient = useQueryClient();

  const updateCityMutation = useMutation({
    mutationFn: (newCity: string) => getHotelsByCity({ city: newCity }),
    onSuccess: (_, newCity) => {
      // 刷新酒店数据缓存
      queryClient.invalidateQueries({ queryKey: ["hotels", newCity] });
    },
  });

  const handleCityChange = (newCity: string) => {
    updateCityMutation.mutate(newCity);
  };

  return (
    <div
      className="flex items-center cursor-pointer"
      onClick={() => selectLocationModal.onOpen(handleCityChange)}
    >
      <p>{updateCityMutation.isPending ? "更新中..." : initialCity}</p>
    </div>
  );
}

Next.js 13 vs 14的差异

Next.js 14主要是对App Router的稳定性优化,Server Actions从实验性转为稳定特性,方案2在14中可直接使用,无需额外开启实验配置;此外14的缓存机制更智能,revalidatePath和revalidateTag的表现更一致,其他方案在13和14中完全通用。

为什么不推荐Redux?

Redux这类全局状态库在App Router中需要额外配置(如在客户端Provider中包裹),且服务端初始数据的同步需要手动处理,不如上述方案贴合Next.js的服务端渲染特性,增加了不必要的复杂度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:47:10