為什麼要寫 rn#
一個是我自己還沒正兒八經地寫過 rn,想試試它的體驗怎麼樣。加上最近對 Follow 這個 RSS 閱讀器很感興趣,但是它暫時還沒移動端,可以作為我邊學習邊實踐的對象。再有就是最近開始上班了,自己老是沒什麼動力在下班後寫點想寫的代碼,有個目標更容易讓自己專注。
同時也立個 flag,把學習和開發的過程寫成每週更新的小博客,歡迎大家關注。
準備工作#
hello world#
好了,廢話不多說,讓我們跑起來第一個 app 吧。不過正所謂 “工欲善其事,必先利其器”,我們先準備好環境。
一般來說你只需要安裝好 Xcode 就行了,不過如果你像我一樣,最近升級了 macOS beta 的話,就會麻煩一些:
- App Store 裡的 Xcode 是不能打開的,和系統版本不匹配。
- 下載完的 Xcode beta 沒辦法直接打開,提示
the plug-in or one of its prerequisite plug-ins may be missing or damaged and may need to be reinstalled.
,需要手動安裝Xcode.app/Contents/Resources/Packages/
下的安裝包。參見 https://forums.developer.apple.com/forums/thread/660860 - 命令行中需要 select 到你在用的 beta 版 Xcode,
xcode-select -s /Applications/Xcode-beta.app
。
然後就是需要一個 nice 的腳手架,我不太熟悉 rn 這邊的技術棧,看完 State of React Native 就選擇了之前在 Twitter 上看到的 Create Expo Stack。
它除了作為一個 expo 項目的腳手架之外,還給你提供了很多主流技術棧的組合選項,這對於我想盡快開始寫 app 非常友好。最終我選擇的組合是:
npx create-expo-stack@latest follow-app --expo-router --tabs --tamagui --pnpm --eas
處理深色模式#
腳手架默認為只支持淺色模式,強迫症表示不能接受,所以首先處理一下先。參考這個 issue,我需要修改 expo 的設置為:
{
"expo": {
"userInterfaceStyle": "automatic",
"ios": {
"userInterfaceStyle": "automatic"
},
"android": {
"userInterfaceStyle": "automatic"
}
}
}
然後你的 useColorScheme
就能正常獲得用戶當前選擇的主題模式。不過需要注意的是,修改完這個配置,你需要再執行一次 expo prebuild
,確保 Info.plist 文件裡 key 為 UIUserInterfaceStyle
的值為 Automatic
。
正戲開始#
好了,現在我們來寫 Follow app 吧!
登錄賬號#
雖然 expo 文檔有很詳細的 Authentication 接入文檔,但我們不需要使用它。 Follow 的網頁端已經處理好了,我們只需要調用網頁端的登錄,為 app 註冊處理網頁登錄後會跳轉的 scheme 鏈接就好。
首先設置好 app 的 scheme,在 app config 裡面設置 scheme: 'follow'
,然後運行一下 expo prebuild
。
用 expo-web-browser
打開 Follow 登錄頁面:
await WebBrowser.openBrowserAsync('https://dev.follow.is/login')
然後用 expo-linking
註冊 url 的監聽事件,在接收到登錄網頁調起的 url 信息後,解析裡面的 token。
Linking.addEventListener('url', ({ url }) => {
const { hostname, queryParams } = Linking.parse(url)
if (hostname === 'auth' && queryParams !== null && typeof queryParams.token === 'string') {
WebBrowser.dismissBrowser()
if (Platform.OS !== 'web') {
SecureStore.setItemAsync(SECURE_AUTH_TOKEN_KEY, queryParams.token)
}
}
})
這裡還遇到的一個問題是 iPhone 上 Safari 的異步函數裡的 window.open
會無效,需要加上 target="_top"
的參數。參考 https://stackoverflow.com/q/20696041/15548365
因為 url 會跳到 auth 這個頁面,我們可以加個讓它跳到主頁的路由 app/auth.tsx
。
import { router } from 'expo-router'
export default function Auth() {
router.navigate('/')
return null
}
OK,這樣我們就已經能夠獲取到用戶的認證憑據了。來試試調個接口看看。
獲取用戶信息#
在 rn 中發起網絡請求看起來和 web 沒有區別,我們仍然可以使用自己喜歡的庫。
function useSession() {
return useSWR(URL_TO_FOLLOW_SERVER, async (url) => {
const authToken = await SecureStore.getItemAsync(SECURE_AUTH_TOKEN_KEY)
const response = await fetch(url, {
headers: {
cookie: `authjs.session-token=${authToken}`,
},
credentials: 'omit',
})
const data = (await response.json()) as Session
return data
})
}
這裡我暫時做了一點反常的設置,是因為 rn 中基於 cookie 的身份驗證存在一些 已知的問題,如果不設置 credentials: 'omit'
的話,就會在第二次請求時設置不正確的 cookie,導致請求失敗。這裡是參考 https://github.com/facebook/react-native/issues/23185#issuecomment-1148130842 的做法。
有了數據我們就可以渲染頁面,這裡先簡單寫寫:
export default function UserInfo() {
const { data: session, mutate } = useSession()
return (
<YStack flex={1} padding={20}>
{session ? (
<YStack>
<XStack gap={24} alignItems="center">
<Image
source={{
uri: session.user.image,
height: 100,
width: 100,
}}
borderRadius={50}
/>
<YStack gap={8}>
<Text color="$color12" fontSize="$8" fontWeight="600">
{session.user.name}
</Text>
<Text color="$color12" fontSize="$5">
{session.user.email}
</Text>
</YStack>
</XStack>
</YStack>
) : (
<Button onPress={handlePressButtonAsync}>Login</Button>
)}
</YStack>
)
}
好了,來看看現在的效果。
啊哦,看起來 Follow 的網頁端還需要做點移動端適配,我又可以水 PR 了。
總結#
總算是有個能跑的軟件了,下週要做什麼呢?或許是給 app 設置數據庫?寫 app 的話還是希望我們可以在弱網甚至無網環境中正常瀏覽我們的訂閱。歡迎大家聊聊自己的想法。