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

NextJS基于客户端输入实现服务端Fetch的方案咨询

解决方案

方案1:搜索参数+客户端状态双向同步

你之前用查询字符串时输入状态丢失,问题出在没把搜索参数和输入框的本地状态做双向同步。结合useSearchParams和useState就能解决:用户输入时同时更新本地状态和URL搜索参数,路由刷新后从搜索参数恢复输入状态,服务端组件直接读取搜索参数过滤数据。

客户端组件(过滤输入)

'use client';

import { useSearchParams, useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';

export default function DateFilter() {
  const searchParams = useSearchParams();
  const router = useRouter();
  // 从URL搜索参数初始化输入状态
  const [dateRange, setDateRange] = useState({
    start: searchParams.get('startDate') || '',
    end: searchParams.get('endDate') || '',
  });

  // 监听搜索参数变化,同步到本地状态(比如用户手动修改URL)
  useEffect(() => {
    setDateRange({
      start: searchParams.get('startDate') || '',
      end: searchParams.get('endDate') || '',
    });
  }, [searchParams]);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    const newDateRange = { ...dateRange, [name]: value };
    setDateRange(newDateRange);

    // 更新URL搜索参数,保留现有其他参数
    const params = new URLSearchParams(searchParams);
    params.set(name, value);
    router.push(`/?${params.toString()}`, { scroll: false });
  };

  return (
    <div>
      <input
        type="date"
        name="startDate"
        value={dateRange.start}
        onChange={handleInputChange}
      />
      <input
        type="date"
        name="endDate"
        value={dateRange.end}
        onChange={handleInputChange}
      />
    </div>
  );
}

服务端组件(数据获取)

import { headers } from 'next/headers';

export default async function DataPage() {
  // 从请求头中解析URL参数
  const requestUrl = headers().get('next-url');
  const searchParams = requestUrl ? new URLSearchParams(requestUrl.split('?')[1]) : new URLSearchParams();
  const startDate = searchParams.get('startDate');
  const endDate = searchParams.get('endDate');

  // 带过滤参数请求数据
  const res = await fetch(`https://api.example.com/data?start=${startDate}&end=${endDate}`);
  const data = await res.json();

  return <div>{/* 渲染过滤后的数据 */}</div>;
}

优缺点:支持页面刷新、书签分享,符合REST语义;但参数会暴露在URL中,不适合敏感数据。


方案2:客户端用SWR/React Query直接请求API路由

把数据获取逻辑放到客户端组件,用SWR或React Query管理数据状态,用户输入时触发重新请求,无需刷新路由,服务端通过API路由接收过滤参数处理数据。

客户端组件(带数据请求)

'use client';

import useSWR from 'swr';
import { useState } from 'react';

// 封装请求函数
const fetcher = async (url: string) => {
  const res = await fetch(url);
  if (!res.ok) throw new Error('请求失败');
  return res.json();
};

export default function DateFilterPage() {
  const [dateRange, setDateRange] = useState({ start: '', end: '' });
  
  // 根据输入状态动态生成请求URL,输入不全时不发起请求
  const { data, isLoading, error } = useSWR(
    dateRange.start && dateRange.end 
      ? `/api/data?start=${dateRange.start}&end=${dateRange.end}` 
      : null,
    fetcher
  );

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setDateRange(prev => ({ ...prev, [name]: value }));
  };

  return (
    <div>
      <input
        type="date"
        name="start"
        value={dateRange.start}
        onChange={handleInputChange}
      />
      <input
        type="date"
        name="end"
        value={dateRange.end}
        onChange={handleInputChange}
      />
      {isLoading && <p>加载中...</p>}
      {error && <p>加载失败,请重试</p>}
      {data && <div>{/* 渲染数据 */}</div>}
    </div>
  );
}

API路由(服务端处理过滤)

// app/api/data/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const startDate = searchParams.get('start');
  const endDate = searchParams.get('end');

  // 这里替换成你的数据过滤逻辑
  const filteredData = await fetchFromDatabase(startDate, endDate);

  return Response.json(filteredData);
}

优缺点:无需刷新路由,用户体验流畅;自带缓存、重试、自动重新请求等特性;但数据获取在客户端,无法利用服务端组件的静态生成优势。


方案3:Cookie传递参数(适合非公开场景)

如果不想把过滤参数暴露在URL中,可以将用户输入的日期范围存在Cookie里,服务端组件读取Cookie后过滤数据。

客户端组件(设置Cookie+触发刷新)

'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';

export default function DateFilter() {
  const router = useRouter();
  // 从Cookie初始化输入状态
  const [dateRange, setDateRange] = useState(() => {
    const cookieStr = document.cookie.split('; ').find(row => row.startsWith('dateRange='));
    return cookieStr ? JSON.parse(cookieStr.split('=')[1]) : { start: '', end: '' };
  });

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    const newDateRange = { ...dateRange, [name]: value };
    setDateRange(newDateRange);

    // 设置会话级Cookie,关闭浏览器自动失效
    document.cookie = `dateRange=${JSON.stringify(newDateRange)}; path=/; sameSite=strict; max-age=86400`;
    
    // 触发路由刷新,让服务端组件读取新Cookie
    router.push(window.location.pathname, { scroll: false });
  };

  return (
    <div>
      <input
        type="date"
        name="start"
        value={dateRange.start}
        onChange={handleInputChange}
      />
      <input
        type="date"
        name="end"
        value={dateRange.end}
        onChange={handleInputChange}
      />
    </div>
  );
}

服务端组件(读取Cookie)

import { cookies } from 'next/headers';

export default async function DataPage() {
  const cookieStore = cookies();
  const dateRangeStr = cookieStore.get('dateRange')?.value;
  const dateRange = dateRangeStr ? JSON.parse(dateRangeStr) : { start: '', end: '' };

  const res = await fetch(`https://api.example.com/data?start=${dateRange.start}&end=${dateRange.end}`);
  const data = await res.json();

  return <div>{/* 渲染数据 */}</div>;
}

优缺点:参数不暴露在URL中,适合隐私场景;但Cookie有大小限制(约4KB),且需要手动处理过期和清理逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 17:32:22