iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

目前已經透過 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 中的狀態與方法,這邊定義了 locationerrorMsgcurrentWeather 這三個狀態,並且定義了 setLocationsetErrorMsgsetCurrentWeatherfetchWeatherData 這四個方法。

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

接下來就可以在 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

拿到目前天氣資料之後,可以直接在 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 來管理狀態。


上一篇
Day 20 - 使用 OpenWeatherMap API - 目前天氣資料
下一篇
Day 22 - 使用 Geocoding 顯示目前地區
系列文
掌握 React Native 與 Expo:30天雙打,新手也能精通跨平台開發!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言