test/1114/app/store/weatherStore.ts

306 lines
8.6 KiB
TypeScript
Raw Normal View History

2025-11-17 11:13:06 +08:00
/**
* 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);
}
},
}
)
);