xChar
·4 months ago

为什么要写 rn

一个是我自己还没正儿八经地写过 rn,想试试它的体验怎么样。加上最近对 Follow 这个 RSS 阅读器很感兴趣,但是它暂时还没移动端,可以作为我边学习边实践的对象。再有就是最近开始上班了,自己老是没什么动力在下班后写点想写的代码,有个目标更容易让自己专注。

同时也立个 flag,把学习和开发的过程写成每周更新的小博客,欢迎大家关注。

准备工作

hello world

好了,废话不多说,让我们跑起来第一个 app 吧。不过正所谓“工欲善其事,必先利其器”,我们先准备好环境。

一般来说你只需要安装好 Xcode 就行了,不过如果你像我一样,最近升级了 macOS beta 的话,就会麻烦一些:

  1. App Store 里的 Xcode 是不能打开的,和系统版本不匹配。
  2. 下载完的 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
  3. 命令行中需要 select 到你在用的 beta 版 Xcode,xcode-select -s /Applications/Xcode-beta.app

然后就是需要一个 nice 的脚手架,我不太熟悉 rn 这边的技术栈,看完 State of React Native 就选择了之前在 Twitter 上看到的 Create Expo Stack

https://x.com/DanStepanov/status/1800306385797980320

它除了作为一个 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>
  )
}

好了,来看看现在的效果。

:::div{style="max-width: 400px"}

:::

啊哦,看起来 Follow 的网页端还需要做点移动端适配,我又可以水 PR 了。

总结

总算是有个能跑的软件了,下周要做什么呢?或许是给 app 设置数据库?写 app 的话还是希望我们可以在弱网甚至无网环境中正常浏览我们的订阅。欢迎大家聊聊自己的想法。

Loading comments...