React跨组件共享天气API数据 无需重复请求渲染天气图标
跨组件复用天气数据实现方案
React函数组件内部通过useState声明的状态属于组件私有闭包变量,无法直接导出给其他组件使用。要实现「仅发起一次API请求、全项目任意组件复用天气数据、单独渲染天气图标」的需求,最通用的无额外依赖方案是使用React Context做状态提升,把数据请求和公共逻辑抽离到全局共享层。
具体实现步骤
1. 抽离全局天气Context
新建天气上下文文件,把API请求、天气数据、图标匹配逻辑全部统一管理,整个应用只会初始化一次、发一次请求:
// src/context/WeatherContext.tsx import { createContext, useContext, useEffect, useState, ReactNode } from 'react'; import axios from 'axios'; import { Weather } from '../types'; import { MdWbSunny } from 'react-icons/md'; import { IoIosPartlySunny } from 'react-icons/io'; import { BsFillCloudSnowFill } from 'react-icons/bs'; // 定义上下文返回值类型 type WeatherContextType = { weather: Weather | null; loading: boolean; getWeatherIcon: (forecast?: string) => ReactNode; }; const WeatherContext = createContext<WeatherContextType | undefined>(undefined); // 全局Provider组件,包裹在应用最外层 export const WeatherProvider = ({ children }: { children: ReactNode }) => { const [weather, setWeather] = useState<Weather | null>(null); const [loading, setLoading] = useState(true); const fetchWeatherData = async () => { setLoading(true); const res = await axios.get('http://mock-api-call/weather/get-weather'); setWeather(res.data.result.weather); setLoading(false); }; // 仅在Provider初始化时请求一次数据 useEffect(() => { fetchWeatherData(); }, []); // 统一抽离图标匹配逻辑,所有组件复用,无需重复写判断 const getWeatherIcon = (forecast?: string) => { switch (forecast) { case 'Sunny': return <MdWbSunny data-testid="sunny" />; case 'Snowing': return <BsFillCloudSnowFill data-testid="snowing" />; default: return <IoIosPartlySunny data-testid="overcast" />; } }; return ( <WeatherContext.Provider value={{ weather, loading, getWeatherIcon }}> {children} </WeatherContext.Provider> ); }; // 封装自定义调用Hook,简化组件使用逻辑 export const useWeather = () => { const context = useContext(WeatherContext); if (!context) throw new Error('useWeather 必须在 WeatherProvider 内部使用'); return context; };
2. 在应用根层挂载Provider
把WeatherProvider包裹在项目根组件的最外层,保证全局只有一份天气数据实例:
// src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { WeatherProvider } from './context/WeatherContext'; ReactDOM.createRoot(document.getElementById('root')!).render( <WeatherProvider> <App /> </WeatherProvider> );
3. 改造原有天气组件
移除原组件内部的状态声明和API请求逻辑,直接通过useWeather消费全局数据:
import { Title, Text, Container } from '@mantine/core'; import { useWeather } from '../context/WeatherContext'; const WeatherComponent = () => { const { weather, loading, getWeatherIcon } = useWeather(); if (loading) return <Container>天气数据加载中...</Container>; return ( <Container> <Title order={2}> {getWeatherIcon(weather?.forcast)} </Title> <Text size="xl" data-testid="forcast"> {weather?.forcast} </Text> <Text size="lg" data-testid="temp"> Temp: {`${weather?.min} to ${weather?.max}`} </Text> <Text size="md" data-testid="description"> {weather?.description} </Text> </Container> ); }; export default WeatherComponent;
4. 任意组件单独渲染天气图标
其他任何需要用到天气数据或者天气图标的组件,直接调用useWeather即可,完全不会重复发起API请求:
// 示例:页面头部组件仅展示天气图标 import { useWeather } from '../context/WeatherContext'; const SiteHeader = () => { const { weather, loading, getWeatherIcon } = useWeather(); return ( <header className="site-header"> <h1>我的站点</h1> {!loading && <div className="header-weather">{getWeatherIcon(weather?.forcast)}</div>} </header> ); }; export default SiteHeader;
其他可选方案
如果项目已经接入了Zustand、Redux、Jotai这类全局状态管理库,直接把天气数据、请求逻辑、图标匹配方法存在全局Store里即可,实现逻辑和Context方案一致,同样可以保证单次请求、全局共享数据。
注意:不要尝试通过props层层传递天气数据,嵌套层级深的时候维护成本极高,也容易触发不必要的重渲染。
内容的提问来源于stack exchange,提问作者goatstash
相关产品推荐
相关产品推荐

