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

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能力。

  1. 客户端筛选器组件:更新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>
  );
}
  1. 服务器列表组件:读取参数并请求数据
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状态管理更新组件。

  1. 定义服务器动作
'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;
}
  1. 客户端整合筛选器与列表
'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和客户端筛选效率。

  1. 服务器组件提供初始数据
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} />;
}
  1. 客户端组件用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:27:24