Next.js 13/14 App Router服务端组件向客户端组件传值方法
Next.js 13/14 App Router 服务端到客户端组件的状态传递方案
在App Router中,服务端组件无法直接使用Redux这类客户端状态库,但有几个更贴合Next.js设计的方案,既能传递初始服务端数据,又能支持客户端修改:
方案1:Props传递初始值 + React Context管理客户端状态
这是最基础的原生方案,适合需要在多个客户端组件间共享修改后状态的场景。
步骤:
- 服务端组件将获取到的
city作为props传给客户端组件 - 创建客户端Context,统一管理city的状态和修改方法
- 客户端组件通过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是官方推荐的方案,无需额外状态库,直接在客户端触发服务端逻辑。
步骤:
- 服务端组件传递初始
city给客户端组件 - 创建Server Action用于更新城市并重新获取酒店数据
- 客户端组件使用
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更轻量,专门处理服务端数据状态,同时支持客户端修改后同步。
步骤:
- 配置TanStack Query客户端
- 服务端组件预获取初始city,传递给客户端组件作为初始查询参数
- 客户端组件使用
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
相关产品推荐
相关产品推荐

