目前已經透過 OpenWeatherMap API 拿到現在的天氣資料,但是要怎麼將資料傳遞給 component 使用呢?
有以下幾種方式可以傳遞資料給 component:
傳遞方式 | 優點 | 缺點 |
---|---|---|
props | 最簡單使用 | 需要層層傳遞,難以維護 |
Context | 避免層層傳遞的問題 | 重新渲染效能可能受影響,狀態龐大也不好維護 |
Redux | 狀態可預測,強大的開發工具及龐大的生態系 | 對於簡單的專案可能過於複雜 |
Zustand | 簡潔靈活的 API 與無需使用 reducer | 相比 Redux 使用人數較少 |
方法有很多種,不過綜合考量後,這篇文章將會使用 Zustand 來管理狀態。(因為我想用新玩具來玩,不然平常是用 Redux Toolkit)
那麼本篇文章將會示範如何使用 Zustand 這個狀態管理套件,來幫助我們解決這個問題。
首先先安裝 Zustand:
$ npm install zustand
接下來在 src
資料夾下新增一個 store
資料夾,並且新增一個 index.ts
檔案:
import { create } from 'zustand'
import * as Location from 'expo-location'
interface WeatherDetail {
description: string
icon: string
id: number
main: string
}
interface CurrentWeatherInfo {
feels_like: number
temp: number
weather: WeatherDetail[]
}
interface Store {
location: Location.LocationObject | null
errorMsg: string | null
currentWeather: CurrentWeatherInfo | null
setLocation: (location: Location.LocationObject | null) => void
setErrorMsg: (msg: string | null) => void
setCurrentWeather: (weather: CurrentWeatherInfo | null) => void
fetchWeatherData: () => Promise<void>
}
使用 Zustand 的起手式是使用 create
來建立一個 store,並且定義 store 中的狀態與方法,這邊定義了 location
、errorMsg
、currentWeather
這三個狀態,並且定義了 setLocation
、setErrorMsg
、setCurrentWeather
、fetchWeatherData
這四個方法。
Zustand 跟 Redux 的差別在於,Zustand 的狀態跟方法都是在同一個物件中,而且不需要使用 reducer
來更新狀態,只需要直接使用 setXXX
這種方式就可以更新狀態。
接下來使用 create
來建立 store:
const useStore = create<Store>((set) => ({
location: null,
errorMsg: null,
currentWeather: null,
setLocation: (location) => set({ location }),
setErrorMsg: (msg) => set({ errorMsg: msg }),
setCurrentWeather: (weather) => set({ currentWeather: weather }),
fetchWeatherData: async () => {
const { location, errorMsg } = useStore.getState()
if (location && location.coords) {
const { latitude, longitude } = location.coords
const apiUrl = process.env.EXPO_PUBLIC_WEATHER_API_URL
const apiKey = process.env.EXPO_PUBLIC_WEATHER_API_KEY
try {
const response = await fetch(
`${apiUrl}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&units=metric&appid=${apiKey}`
)
const json = await response.json()
set({ currentWeather: json.current })
} catch (err) {
console.log(err)
}
}
}
}))
export default useStore
create
的第一個參數是一個 callback,callback 中的 set
參數是一個函式,可以用來更新狀態,而 create
的回傳值就是一個 hook,可以直接使用。
這裡如同上面所提到的,會使用 set
來更新狀態,而 set
的參數是一個物件,物件中的 key 就是狀態的名稱,value 則是狀態的值。最後再把 useStore
export 出去,就可以在其他地方使用了。
接下來就可以在 App.tsx
中使用 useStore
了,並且可以把 useState
全部刪掉,用不到了:
import { useEffect } from 'react'
import * as Location from 'expo-location'
import useStore from './src/store'
export default function App() {
const { location, setLocation, setErrorMsg, fetchWeatherData } = useStore()
useEffect(() => {
;(async () => {
let { status } = await Location.requestForegroundPermissionsAsync()
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied')
return
}
let location = await Location.getCurrentPositionAsync({})
setLocation(location)
})()
}, [])
useEffect(() => {
if (location) {
fetchWeatherData()
}
}, [location])
return (
<SafeAreaView className='flex-1 bg-blue-500'>
// 省略
</SafeAreaView>
)
}
這裡可以看到,我們可以直接使用 useStore
來取得狀態與方法,並且可以直接使用剛剛定義的 setLocation
來更新 GPS 定位狀態,並且使用 fetchWeatherData
來取得天氣資料。
拿到目前天氣資料之後,可以直接在 CurrentWeather.tsx
中使用 useStore
來顯示天氣資料:
import { View, Text } from 'react-native'
import { AntDesign } from '@expo/vector-icons'
import useStore from '../store'
const CurrentWeather = () => {
const { currentWeather } = useStore()
if (
!currentWeather ||
!currentWeather?.weather ||
currentWeather?.weather?.length === 0
) {
return null
}
const { temp } = currentWeather
const { description } = currentWeather?.weather[0]
return (
<View className='items-center space-y-2'>
<AntDesign name='cloudo' size={60} color='#ded8d8' />
<Text className='text-6xl font-bold text-white'>{temp}</Text>
<Text className='text-xl text-gray-300'>{description}</Text>
</View>
)
}
在 CurrentWeather
中,也一樣直接使用 useStore
來取得剛剛取得的天氣資料 currentWeather
,並且使用顯示。
透過使用 Zustand,可以讓我們更簡單地管理狀態,其簡單直覺的 API 設計,讓我們可以更快速地開發應用程式,如果之後有開發的專案有狀態管理的需求,或許可以考慮使用 Zustand 來管理狀態。