Next.js 13中基于客户端组件变量调用API的方案咨询
问题描述
我在服务器组件中通过以下函数获取产品列表:
async function getData() { const res = await fetch(`${apiPath}`); const data = (await res.json()) as PackProps[]; return data }
现在网站用户需要对列表应用筛选器,我已获取带对应参数的新API格式,但不知如何调用。问题在于筛选器逻辑写在客户端组件中,与展示列表的组件相互独立。若尝试嵌套组件传递参数,会出现报错:
... cannot be used as a JSX component. Its return type 'Promise<Element>' is not a valid JSX element.
显然客户端组件内无法标记函数组件为async并调用API,想请教如何在不丢失Server Side Rendering(SSR)优势的前提下调用API或强制API重渲染?
解决方案
方案一:通过URL搜索参数传递筛选条件
将筛选参数同步到URL查询字符串中,服务器组件通过searchParams读取参数并发起带筛选条件的请求,全程保留SSR能力。
- 客户端筛选器组件:更新URL参数
'use client'; import { useRouter, useSearchParams } from 'next/navigation'; export default function FilterComponent() { const router = useRouter(); const searchParams = useSearchParams(); const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const newParams = new URLSearchParams(searchParams); e.target.value ? newParams.set('category', e.target.value) : newParams.delete('category'); router.push(`/?${newParams.toString()}`); }; return ( <select onChange={handleFilterChange} defaultValue={searchParams.get('category') || ''}> <option value="">全部分类</option> <option value="electronics">电子产品</option> <option value="clothing">服饰</option> </select> ); }
- 服务器列表组件:读取参数并请求数据
import FilterComponent from './FilterComponent'; async function getData(searchParams: { category?: string }) { const params = new URLSearchParams(); if (searchParams.category) params.set('category', searchParams.category); const res = await fetch(`${apiPath}?${params.toString()}`, { next: { revalidate: 60 } }); const data = (await res.json()) as PackProps[]; return data; } export default async function ProductList({ searchParams }: { searchParams: { category?: string } }) { const products = await getData(searchParams); return ( <div> <FilterComponent /> <ul> {products.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); }
URL变化时,服务器组件会重新渲染并发起带筛选参数的请求,完全保留SSR优势。
方案二:用服务器动作(Server Actions)触发数据更新
若不想暴露筛选参数到URL,可通过Server Actions在客户端触发服务器端请求,配合React状态管理更新组件。
- 定义服务器动作
'use server'; export async function fetchFilteredProducts(filter: { category?: string }) { const params = new URLSearchParams(); if (filter.category) params.set('category', filter.category); const res = await fetch(`${apiPath}?${params.toString()}`, { next: { revalidate: 60 } }); const data = (await res.json()) as PackProps[]; return data; }
- 客户端整合筛选器与列表
'use client'; import { useState, useTransition, useEffect } from 'react'; import { fetchFilteredProducts } from './actions'; export default function ProductListClient() { const [products, setProducts] = useState<PackProps[]>([]); const [isPending, startTransition] = useTransition(); // 初始加载数据(可保留SSR初始渲染) useEffect(() => { startTransition(async () => { const initialData = await fetchFilteredProducts({}); setProducts(initialData); }); }, []); const handleFilterChange = async (e: React.ChangeEvent<HTMLSelectElement>) => { startTransition(async () => { const filteredData = await fetchFilteredProducts({ category: e.target.value || undefined }); setProducts(filteredData); }); }; return ( <div> <select onChange={handleFilterChange}> <option value="">全部分类</option> <option value="electronics">电子产品</option> <option value="clothing">服饰</option> </select> {isPending && <p>加载中...</p>} <ul> {products.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); }
若需要初始SSR,可先在服务器组件渲染初始列表,再由客户端触发筛选更新。
方案三:React Query/SWR 结合服务器组件初始数据
用数据缓存库管理客户端请求,同时由服务器组件提供初始数据,兼顾SSR和客户端筛选效率。
- 服务器组件提供初始数据
import ProductListClient from './ProductListClient'; async function getData() { const res = await fetch(`${apiPath}`, { next: { revalidate: 60 } }); const data = (await res.json()) as PackProps[]; return data; } export default async function ProductList() { const initialProducts = await getData(); return <ProductListClient initialProducts={initialProducts} />; }
- 客户端组件用React Query处理筛选
'use client'; import { useQuery } from '@tanstack/react-query'; import { useState } from 'react'; async function fetchProducts(filter: { category?: string }) { const params = new URLSearchParams(); if (filter.category) params.set('category', filter.category); const res = await fetch(`${apiPath}?${params.toString()}`); return res.json() as Promise<PackProps[]>; } export default function ProductListClient({ initialProducts }: { initialProducts: PackProps[] }) { const [filter, setFilter] = useState<{ category?: string }>({}); const { data: products } = useQuery({ queryKey: ['products', filter], queryFn: () => fetchProducts(filter), initialData: initialProducts, }); const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => { setFilter({ category: e.target.value || undefined }); }; return ( <div> <select onChange={handleFilterChange} defaultValue={filter.category || ''}> <option value="">全部分类</option> <option value="electronics">电子产品</option> <option value="clothing">服饰</option> </select> <ul> {products?.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); }
既保留初始数据的SSR,又能在客户端高效处理筛选请求,同时具备缓存和自动重验证能力。
内容的提问来源于stack exchange,提问作者Roberto Filippini
相关产品推荐
相关产品推荐

