這篇文章將會著重於建立 App 的 UI 架構,會使用一開始建立專案設定的 nativewind 和 tailwindcss 來快速地設計和實現 UI。
在開始寫 code 之前,先來簡單地設計一下 App 的 UI,預想中會有以下幾個部分:
最後的畫面預計會長這樣:
這個畫面又可以分成 4 個部分:
接下來就可以按照這個規劃去分出幾個 component 來實作了。
首先建立一個 src/components
的資料夾,裡面會放我們的 component。接著新增 4 個檔案:
$ touch src/components/HeaderInfo.tsx src/components/CurrentWeather.tsx src/components/HourlyForecast.tsx src/components/DailyForecast.tsx
HeaderInfo 會顯示地點和日期,這裡使用 View
和 Text
,並且使用 tailwindcss 的 class 來設定樣式,就可以不用另外寫 css 了。
import { View, Text } from 'react-native'
const HeaderInfo = () => (
<View className='items-center p-6 space-y-4 w-500'>
<Text className='text-2xl font-semibold text-white'>Taipei</Text>
<Text className='text-sm text-gray-200'>Monday, 12 September</Text>
</View>
)
export default HeaderInfo
CurrentWeather 主要顯示現在的溫度、天氣狀況,以及天氣圖示。這裡使用了前面幾天介紹的 expo/vector-icons
。
import { View, Text } from 'react-native'
import { AntDesign } from '@expo/vector-icons'
const CurrentWeather = () => (
<View className='items-center space-y-2'>
<AntDesign name='cloudo' size={60} color='#ded8d8' />
<Text className='text-6xl font-bold text-white'>28°</Text>
<Text className='text-xl text-gray-300'>Partly Cloudy</Text>
</View>
)
export default CurrentWeather
HourlyForecast 會顯示接下來幾個小時的天氣預報,這裡我們先放上 4 個小時的預報,每個預報會顯示時間、天氣圖示、溫度。
import React, { FC } from 'react'
import { View, Text } from 'react-native'
import { AntDesign, Feather } from '@expo/vector-icons'
type AntDesignNames = keyof typeof AntDesign.glyphMap
type FeatherNames = keyof typeof Feather.glyphMap
interface WeatherIconConfig {
component: ComponentType<any>
name: AntDesignNames | FeatherNames
}
interface HourlyForecastProps {
time: string
icon: keyof typeof weatherIcons
temp: string
}
const weatherIcons: Record<string, WeatherIconConfig> = {
sunny: { component: Feather, name: 'sun' },
cloudy: { component: AntDesign, name: 'cloudo' },
rainy: { component: Feather, name: 'cloud-rain' }
}
const HourlyForecast: FC<HourlyForecastProps> = ({ time, icon, temp }) => {
const IconComponent = weatherIcons[icon]?.component || AntDesign || Feather
const iconName = weatherIcons[icon]?.name || 'question'
const iconColor = weatherIcons[icon]?.color || '#ffffff'
return (
<View className='items-center space-y-2'>
<Text className='text-sm font-medium text-white'>{time}</Text>
<IconComponent name={iconName} size={30} color={iconColor} />
<Text className='text-lg font-semibold text-white'>{temp}°</Text>
</View>
)
}
export default HourlyForecast
這裡需要說明一下,我們使用了 Record<string, WeatherIconConfig>
來定義 weatherIcons
的型別,這樣就可以在 weatherIcons
裡面放入不同的 icon 設定,並且在 HourlyForecast
裡面使用 weatherIcons[icon]?.component
來取得 icon 的 component,這樣就可以不用寫很多的 if
判斷式。
DailyForecast 會顯示本週的天氣預報,這裡我們先放上 7 天的預報,每個預報會顯示星期幾、天氣圖示、溫度。
import React, { FC, ComponentType } from 'react'
import { View, Text } from 'react-native'
import { AntDesign, Feather } from '@expo/vector-icons'
type AntDesignNames = keyof typeof AntDesign.glyphMap
type FeatherNames = keyof typeof Feather.glyphMap
interface WeatherIconConfig {
component: ComponentType<any>
name: AntDesignNames | FeatherNames
}
interface DailyForecastProps {
day: string
icon: keyof typeof weatherIcons
tempHigh: string
tempLow: string
}
const weatherIcons: Record<string, WeatherIconConfig> = {
sunny: { component: Feather, name: 'sun' },
cloudy: { component: AntDesign, name: 'cloudo' },
rainy: { component: Feather, name: 'cloud-rain' }
}
const DailyForecast: FC<DailyForecastProps> = ({
day,
icon,
tempHigh,
tempLow
}) => {
const IconComponent = weatherIcons[icon]?.component || AntDesign
const iconName = weatherIcons[icon]?.name || 'question'
return (
<View className='flex-row justify-around items-center mb-8 mr-4'>
<Text className='text-lg font-medium text-white w-28'>{day}</Text>
<IconComponent name={iconName} size={30} color='#ded8d8' />
<Text className='text-lg font-semibold text-white w-24'>
{tempHigh}° / {tempLow}°
</Text>
</View>
)
}
export default DailyForecast
DailyForecast 和 HourlyForecast 有點類似,也是使用 Record<string, WeatherIconConfig>
來定義 weatherIcons
的型別,並且在 DailyForecast
裡面使用 weatherIcons[icon]?.component
來取得 icon 的 component。
這個重複的東西我們之後再來處理。
接下來就可以把剛剛寫好的 component 組裝起來了,這裡我們使用 ScrollView
來讓 DailyForecast
的區塊可以滑動,並且使用 SafeAreaView
來讓畫面在 iPhone X 以上的機型上可以顯示正常。
import { View, SafeAreaView, ScrollView } from 'react-native'
import { StatusBar } from 'expo-status-bar'
import {
CurrentWeather,
DailyForecast,
HourlyForecast,
HeaderInfo
} from './src/components'
export default function App() {
return (
<SafeAreaView className='flex-1 bg-blue-500'>
<StatusBar style='auto' />
<HeaderInfo />
<CurrentWeather />
<View className='flex-row justify-around p-6'>
<HourlyForecast time='4 PM' icon='sunny' temp='28' />
<HourlyForecast time='5 PM' icon='cloudy' temp='26' />
<HourlyForecast time='6 PM' icon='rainy' temp='26' />
<HourlyForecast time='7 PM' icon='rainy' temp='25' />
</View>
<ScrollView>
<View className='p-6'>
<DailyForecast
day='Tuesday'
icon='rainy'
tempHigh='73'
tempLow='60'
/>
<DailyForecast
day='Wednesday'
icon='cloudy'
tempHigh='75'
tempLow='62'
/>
<DailyForecast
day='Thursday'
icon='sunny'
tempHigh='77'
tempLow='64'
/>
<DailyForecast day='Friday' icon='sunny' tempHigh='79' tempLow='65' />
<DailyForecast
day='Saturday'
icon='sunny'
tempHigh='81'
tempLow='66'
/>
<DailyForecast day='Sunday' icon='sunny' tempHigh='82' tempLow='67' />
</View>
</ScrollView>
</SafeAreaView>
)
}
這樣我們的主要 UI 就完成了,不過剛剛有提到,HourlyForecast
和 DailyForecast
有點類似,我們可以把它們抽出來,變成一個共用的 config,這樣就不用重複寫一樣的東西了。但這篇文章先到這裡,明天再來介紹如何抽出共用的 config。
明天見👋!