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

Next.js 13.4中如何通过Server Actions更新JSX内的天气数据

解决Next.js 13.4 Server Component中Server Actions更新UI的问题

Server Component本身是在服务器端渲染的,没有客户端状态管理能力,无法直接修改本地变量刷新UI。这里提供两种适配Next.js App Router模型的解决方案:


方案1:使用URL搜索参数(纯Server Component方案)

通过URL传递城市参数,让页面重新渲染时自动获取对应城市的天气数据,完全符合Server Component的渲染逻辑:

import styles from "./page.module.css";
import { notFound, useSearchParams, redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

async function getWeatherData(address) {
  const weatherData = await fetch(
    `http://localhost:3000/api/weather?address=${address}`,
    { cache: "no-cache", tags: ["weather"] }
  );
  if (!weatherData.ok) return notFound();
  return weatherData.json();
}

const Home = async () => {
  // 读取URL中的城市参数,默认值为lahore
  const searchParams = useSearchParams();
  const city = searchParams.get("city") || "lahore";
  // 根据当前城市参数获取天气数据
  const weatherData = (await getWeatherData(city)).data;

  async function changeCityAction(data) {
    "use server";
    const newCity = data.get("cityName")?.toString().trim();
    if (!newCity) return;
    // 重定向到带新城市参数的URL,触发页面重新渲染
    redirect(`/?city=${encodeURIComponent(newCity)}`);
    // 强制刷新缓存,确保获取最新数据
    revalidatePath("/");
  }

  return (
    <main className={styles.main}>
      <article className={styles.widget}>
        <form className={styles.weatherLocation} action={changeCityAction}>
          <input
            className={styles.input_field}
            placeholder="Enter city name"
            type="text"
            id="cityName"
            name="cityName"
            defaultValue={city} // 回显当前选中的城市
          />
          <button className={styles.search_button} type="submit">
            Search
          </button>
        </form>
        {/* 原UI展示逻辑不变 */}
        <div className={styles.icon_and_weatherInfo}>
          <div className={styles.weatherIcon}>
            {weatherData?.weather[0]?.description === "rain" ||
            weatherData?.weather[0]?.description === "fog" ? (
              <i
                className={`wi wi-day-${weatherData?.weather[0]?.description}`}
              ></i>
            ) : (
              <i className="wi wi-day-cloudy"></i>
            )}
          </div>
          <div className={styles.weatherInfo}>
            <div className={styles.temperature}>
              <span>
                {(weatherData?.main?.temp - 273.5).toFixed(2) +
                  String.fromCharCode(176)}
              </span>
            </div>
            <div className={styles.weatherCondition}>
              {weatherData?.weather[0]?.description?.toUpperCase()}
            </div>
          </div>
        </div>
        <div className={styles.place}>{weatherData?.name}</div>
      </article>
    </main>
  );
};

export default Home;

核心逻辑:

  1. 通过useSearchParams读取URL中的城市参数,页面首次加载时使用默认值lahore
  2. Server Actions提交后,通过redirect跳转到带新城市参数的URL,触发Server Component重新渲染
  3. 重新渲染时根据新的城市参数获取对应天气数据,自动更新UI

方案2:拆分Client Component实现无刷新更新

如果需要无刷新更新UI,可将交互部分拆分为Client Component,用客户端状态管理天气数据:

步骤1:创建Client Component WeatherWidget.jsx

"use client";

import styles from "./page.module.css";
import { useState, useEffect } from "react";
import { changeCityAction } from "./actions";

// 客户端请求天气数据
async function fetchWeatherData(city) {
  const res = await fetch(`/api/weather?address=${city}`, { cache: "no-cache" });
  if (!res.ok) throw new Error("Failed to fetch weather");
  const data = await res.json();
  return data.data;
}

export default function WeatherWidget() {
  const [weatherData, setWeatherData] = useState(null);
  const [city, setCity] = useState("lahore");

  // 初始加载默认城市数据
  useEffect(() => {
    const loadInitialData = async () => {
      const data = await fetchWeatherData(city);
      setWeatherData(data);
    };
    loadInitialData();
  }, []);

  // 表单提交处理
  const handleSubmit = async (formData) => {
    const newCity = formData.get("cityName")?.toString().trim();
    if (!newCity) return;
    // 调用Server Action获取新城市数据
    const newWeather = await changeCityAction(formData);
    setWeatherData(newWeather);
    setCity(newCity);
  };

  if (!weatherData) return <div>Loading...</div>;

  return (
    <article className={styles.widget}>
      <form className={styles.weatherLocation} action={handleSubmit}>
        <input
          className={styles.input_field}
          placeholder="Enter city name"
          type="text"
          id="cityName"
          name="cityName"
          defaultValue={city}
          onChange={(e) => setCity(e.target.value)}
        />
        <button className={styles.search_button} type="submit">
          Search
        </button>
      </form>
      {/* 原UI展示逻辑不变 */}
      <div className={styles.icon_and_weatherInfo}>
        <div className={styles.weatherIcon}>
          {weatherData?.weather[0]?.description === "rain" ||
          weatherData?.weather[0]?.description === "fog" ? (
            <i
              className={`wi wi-day-${weatherData?.weather[0]?.description}`}
            ></i>
          ) : (
            <i className="wi wi-day-cloudy"></i>
          )}
        </div>
        <div className={styles.weatherInfo}>
          <div className={styles.temperature}>
            <span>
              {(weatherData?.main?.temp - 273.5).toFixed(2) +
                String.fromCharCode(176)}
            </span>
          </div>
          <div className={styles.weatherCondition}>
            {weatherData?.weather[0]?.description?.toUpperCase()}
          </div>
        </div>
      </div>
      <div className={styles.place}>{weatherData?.name}</div>
    </article>
  );
}

步骤2:创建独立的Server Actions文件 actions.js

"use server";

import { notFound } from "next/navigation";

async function getWeatherData(address) {
  const weatherData = await fetch(
    `http://localhost:3000/api/weather?address=${address}`,
    { cache: "no-cache", tags: ["weather"] }
  );
  if (!weatherData.ok) return notFound();
  return weatherData.json();
}

export async function changeCityAction(data) {
  const city = data.get("cityName")?.toString().trim();
  if (!city) return null;
  const weatherData = (await getWeatherData(city)).data;
  return weatherData;
}

步骤3:修改原Home组件(Server Component)

import styles from "./page.module.css";
import WeatherWidget from "./WeatherWidget";

const Home = () => {
  return (
    <main className={styles.main}>
      <WeatherWidget />
    </main>
  );
};

export default Home;

核心逻辑:

  1. 将需要交互的部分拆分为Client Component,用useState管理天气数据和当前城市
  2. 表单提交后调用Server Action获取新数据,直接更新客户端状态实现无刷新UI
  3. Server Component仅作为容器,不处理交互逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:38:15