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;
核心逻辑:
- 通过
useSearchParams读取URL中的城市参数,页面首次加载时使用默认值lahore - Server Actions提交后,通过
redirect跳转到带新城市参数的URL,触发Server Component重新渲染 - 重新渲染时根据新的城市参数获取对应天气数据,自动更新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;
核心逻辑:
- 将需要交互的部分拆分为Client Component,用
useState管理天气数据和当前城市 - 表单提交后调用Server Action获取新数据,直接更新客户端状态实现无刷新UI
- Server Component仅作为容器,不处理交互逻辑
内容的提问来源于stack exchange,提问作者Umair Jameel
相关产品推荐
相关产品推荐

