test/1114/app/store/weatherStore.ts

306 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* weatherStore.ts - Zustand 全局状态管理
*
* 学习要点:
* 1. Zustand 的基本使用方法
* 2. 如何创建和使用 store
* 3. 状态更新的不可变性immutability
* 4. TypeScript 与 Zustand 的结合
* 5. 本地存储localStorage的集成
*/
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { WeatherAPI } from '@/services/weatherApi';
/**
* 缓存过期时间(毫秒)
* 10分钟后缓存过期需要重新获取
*/
const CACHE_EXPIRY = 10 * 60 * 1000;
/**
* WeatherCache - 天气数据缓存项
*/
export interface Location {
name: string; // 城市名称
country: string; // 国家代码
region: string; // 地区/省份
lat: string; // 纬度
lon: string; // 经度
timezone_id: string; // 时区ID
localtime: string; // 本地时间YYYY-MM-DD HH:mm
localtime_epoch: number; // 本地时间Unix时间戳
utc_offset: string; // UTC偏移量
}
/**
* Current - 当前天气数据接口
* WeatherStack 返回的当前天气信息
*/
export interface Current {
observation_time: string; // 观测时间
temperature: number; // 温度(摄氏度)
weather_code: number; // 天气代码
weather_icons: string[]; // 天气图标URL数组
weather_descriptions: string[]; // 天气描述数组
wind_speed: number; // 风速km/h
wind_degree: number; // 风向角度
wind_dir: string; // 风向(如 "N", "NE"
pressure: number; // 气压mb
precip: number; // 降水量mm
humidity: number; // 湿度(%
cloudcover: number; // 云量(%
feelslike: number; // 体感温度
uv_index: number; // 紫外线指数
visibility: number; // 能见度km
is_day: string; // 是否白天("yes" / "no"
}
/**
* WeatherData - 完整的天气数据接口
* 这是 WeatherStack API 返回的完整数据结构
*/
export interface WeatherData {
request?: {
type: string;
query: string;
language: string;
unit: string;
};
location: Location;
current: Current;
}
export interface WeatherCache {
data: WeatherData;
timestamp: number;
}
/**
* WeatherStore - Zustand store 的状态接口
* 定义了全局状态管理的完整类型
*/
export interface WeatherStore {
// 状态
currentWeather: WeatherData | null;
weatherCache: Map<string, WeatherCache>; // 城市名 -> 缓存数据
isLoading: boolean;
error: string | null;
// Actions
setCurrentWeather: (weather: WeatherData | null) => void;
getWeatherFromCache: (city: string) => WeatherData | null;
cacheWeather: (city: string, weather: WeatherData) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
reset: () => void;
// API Actions
searchWeather: (city: string) => Promise<void>;//查找缓存 无:查找
refreshWeather: () => Promise<void>;//刷新 缓存
}
/**
* useWeatherStore - 天气应用的全局状态管理
*
* Zustand 的核心概念:
* 1. create() 函数创建 store
* 2. set() 函数更新状态
* 3. get() 函数读取当前状态
* 4. persist 中间件实现数据持久化
*
* 缓存策略:
* - 使用 Map 存储城市天气数据
* - 每条数据包含时间戳10分钟后过期
* - 优先从缓存读取,减少 API 调用
*/
export const useWeatherStore = create<WeatherStore>()(
persist(
(set, get) => ({
// ========== 状态定义 ==========
/**
* currentWeather - 当前天气数据
*/
currentWeather: null,
/**
* weatherCache - 天气数据缓存
* Key: 城市名(小写)
* Value: { data, timestamp }
*/
weatherCache: new Map(),
/**
* isLoading - 加载状态
*/
isLoading: false,
/**
* error - 错误信息
*/
error: null,
// ========== Action 方法定义 ==========
/**
* setCurrentWeather - 设置当前天气
*
* @param weather - 新的天气数据
*/
setCurrentWeather: (weather) =>
set({ currentWeather: weather }),
/**
* getWeatherFromCache - 从缓存获取天气数据
*
* @param city - 城市名
* @returns 缓存的天气数据,如果过期或不存在则返回 null
*
* 学习要点:
* - 使用 get() 读取当前状态
* - 缓存过期检查
* - Zustand 的纯函数特性
*/
getWeatherFromCache: (city) => {
const cache = get().weatherCache.get(city.toLowerCase());
if (!cache) return null;
// 检查缓存是否过期
const isExpired = Date.now() - cache.timestamp > CACHE_EXPIRY;
return isExpired ? null : cache.data;
},
/**
* cacheWeather - 缓存天气数据
*
* @param city - 城市名
* @param weather - 天气数据
*
* 学习要点:
* - Map 的不可变更新
* - 使用 new Map() 创建新实例确保响应式更新
*/
cacheWeather: (city, weather) =>
set((state) => {
const newCache = new Map(state.weatherCache);
newCache.set(city.toLowerCase(), {
data: weather,
timestamp: Date.now(),
});
return { weatherCache: newCache };
}),
/**
* setLoading - 设置加载状态
*/
setLoading: (loading) =>
set({ isLoading: loading }),
/**
* setError - 设置错误信息
*/
setError: (error) =>
set({ error, isLoading: false }),
/**
* reset - 重置所有状态
*/
reset: () =>
set({
currentWeather: null,
isLoading: false,
error: null,
// 保留缓存数据
}),
/**
* searchWeather - 搜索天气(使用缓存策略)
*
* @param city - 城市名
*
* 学习要点:
* - 在 store 中封装 API 调用逻辑
* - 优先使用缓存,减少 API 调用
* - 统一管理 loading 和 error 状态
*/
searchWeather: async (city: string) => {
try {
set({ isLoading: true, error: null });
// 1. 先从缓存获取
const cachedWeather = get().getWeatherFromCache(city);
if (cachedWeather) {
set({ currentWeather: cachedWeather, isLoading: false });
return;
}
// 2. 缓存未命中,调用 API
const weatherData = await WeatherAPI.getCurrentWeather(city);
// 3. 更新当前天气并缓存
set({ currentWeather: weatherData, isLoading: false });
get().cacheWeather(city, weatherData);
} catch (err) {
set({
error: err instanceof Error ? err.message : '获取天气数据失败',
isLoading: false,
});
}
},
/**
* refreshWeather - 刷新当前天气(强制刷新,忽略缓存)
*
* 学习要点:
* - 强制刷新,不使用缓存
* - 更新缓存中的数据
*/
refreshWeather: async () => {
const currentWeather = get().currentWeather;
if (!currentWeather) return;
try {
set({ isLoading: true, error: null });
const city = currentWeather.location.name;
const weatherData = await WeatherAPI.getCurrentWeather(city);
// 更新当前天气和缓存
set({ currentWeather: weatherData, isLoading: false });
get().cacheWeather(city, weatherData);
} catch (err) {
set({
error: err instanceof Error ? err.message : '刷新失败',
isLoading: false,
});
}
},
}),
{
/**
* persist 中间件配置
*
* 学习要点:
* - 持久化缓存数据到 localStorage
* - Map 需要序列化为数组存储
*/
name: 'weather-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
weatherCache: Array.from(state.weatherCache.entries()),
}),
// 反序列化:将数组转回 Map
onRehydrateStorage: () => (state) => {
if (state && Array.isArray(state.weatherCache)) {
state.weatherCache = new Map(state.weatherCache as any);
}
},
}
)
);