clerk-expo-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Expo Patterns

Expo 最佳实践

SDK:
@clerk/expo
v3+. Requires Expo 53+, React Native 0.73+.
SDK:
@clerk/expo
v3+。要求Expo 53+、React Native 0.73+版本。

What Do You Need?

你需要实现什么功能?

TaskReference
Persist tokens with SecureStorereferences/token-storage.md
OAuth (Google, Apple, GitHub)references/oauth-deep-linking.md
Protected screens with Expo Routerreferences/protected-routes.md
Push notifications with user datareferences/push-notifications.md
任务参考文档
使用SecureStore持久化存储令牌references/token-storage.md
OAuth(谷歌、苹果、GitHub登录)references/oauth-deep-linking.md
基于Expo Router的受保护页面references/protected-routes.md
携带用户数据的推送通知references/push-notifications.md

Mental Model

核心设计思路

Clerk stores the session token in memory by default. In native apps:
  • SecureStore — encrypt token in device keychain (recommended for production)
  • tokenCache
    — prop on
    <ClerkProvider>
    that provides custom storage
  • useAuth
    — same API as web, works in any component
  • OAuth — requires
    useSSO
    + deep link scheme configured in
    app.json
Clerk默认将会话令牌存储在内存中。在原生应用中:
  • SecureStore — 在设备钥匙串中加密存储令牌(生产环境推荐方案)
  • tokenCache
    <ClerkProvider>
    上的属性,可提供自定义存储实现
  • useAuth
    — 和Web端一致的API,可在任意组件中使用
  • OAuth — 需要使用
    useSSO
    并在
    app.json
    中配置深度链接协议

Minimal Setup

最小化配置

app/_layout.tsx

app/_layout.tsx

tsx
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { Stack } from 'expo-router'

const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!

export default function RootLayout() {
  return (
    <ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}>
      <Stack />
    </ClerkProvider>
  )
}
CRITICAL: Use
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
— not
NEXT_PUBLIC_
. Env vars inside
node_modules
are not inlined in production builds. Always pass
publishableKey
explicitly.
tsx
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { Stack } from 'expo-router'

const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!

export default function RootLayout() {
  return (
    <ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}>
      <Stack />
    </ClerkProvider>
  )
}
重要提示:请使用
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
,不要使用
NEXT_PUBLIC_
前缀的环境变量。生产构建不会内联
node_modules
内部的环境变量,请始终显式传入
publishableKey

Built-in Token Cache

内置令牌缓存

tsx
import { tokenCache } from '@clerk/expo/token-cache'
This uses
expo-secure-store
with
keychainAccessible: AFTER_FIRST_UNLOCK
. Install the peer dep:
bash
npx expo install expo-secure-store
tsx
import { tokenCache } from '@clerk/expo/token-cache'
该实现基于
expo-secure-store
,使用
keychainAccessible: AFTER_FIRST_UNLOCK
配置。请安装对等依赖:
bash
npx expo install expo-secure-store

Auth Hooks

认证Hooks

tsx
import { useAuth, useUser, useSignIn, useSignUp, useClerk } from '@clerk/expo'

export function ProfileScreen() {
  const { isSignedIn, userId, signOut } = useAuth()
  const { user } = useUser()

  if (!isSignedIn) return <Redirect href="/sign-in" />
  return (
    <View>
      <Text>{user?.fullName}</Text>
      <Button title="Sign Out" onPress={() => signOut()} />
    </View>
  )
}
tsx
import { useAuth, useUser, useSignIn, useSignUp, useClerk } from '@clerk/expo'

export function ProfileScreen() {
  const { isSignedIn, userId, signOut } = useAuth()
  const { user } = useUser()

  if (!isSignedIn) return <Redirect href="/sign-in" />
  return (
    <View>
      <Text>{user?.fullName}</Text>
      <Button title="Sign Out" onPress={() => signOut()} />
    </View>
  )
}

OAuth Flow (Google)

OAuth流程(谷歌登录)

tsx
import { useSSO } from '@clerk/expo'
import * as WebBrowser from 'expo-web-browser'

WebBrowser.maybeCompleteAuthSession()

export function GoogleSignIn() {
  const { startSSOFlow } = useSSO()

  const handlePress = async () => {
    try {
      const { createdSessionId, setActive } = await startSSOFlow({
        strategy: 'oauth_google',
        redirectUrl: 'myapp://oauth-callback',
      })
      if (createdSessionId) await setActive!({ session: createdSessionId })
    } catch (err) {
      console.error(err)
    }
  }

  return <Button title="Continue with Google" onPress={handlePress} />
}
tsx
import { useSSO } from '@clerk/expo'
import * as WebBrowser from 'expo-web-browser'

WebBrowser.maybeCompleteAuthSession()

export function GoogleSignIn() {
  const { startSSOFlow } = useSSO()

  const handlePress = async () => {
    try {
      const { createdSessionId, setActive } = await startSSOFlow({
        strategy: 'oauth_google',
        redirectUrl: 'myapp://oauth-callback',
      })
      if (createdSessionId) await setActive!({ session: createdSessionId })
    } catch (err) {
      console.error(err)
    }
  }

  return <Button title="Continue with Google" onPress={handlePress} />
}

Org Switching

组织切换

tsx
import { useOrganization, useOrganizationList } from '@clerk/expo'

export function OrgSwitcher() {
  const { organization } = useOrganization()
  const { setActive, userMemberships } = useOrganizationList()

  return (
    <View>
      <Text>Current: {organization?.name ?? 'Personal'}</Text>
      {userMemberships.data?.map(mem => (
        <Button
          key={mem.organization.id}
          title={mem.organization.name}
          onPress={() => setActive({ organization: mem.organization.id })}
        />
      ))}
    </View>
  )
}
tsx
import { useOrganization, useOrganizationList } from '@clerk/expo'

export function OrgSwitcher() {
  const { organization } = useOrganization()
  const { setActive, userMemberships } = useOrganizationList()

  return (
    <View>
      <Text>Current: {organization?.name ?? 'Personal'}</Text>
      {userMemberships.data?.map(mem => (
        <Button
          key={mem.organization.id}
          title={mem.organization.name}
          onPress={() => setActive({ organization: mem.organization.id })}
        />
      ))}
    </View>
  )
}

Common Pitfalls

常见问题

SymptomCauseFix
publishableKey
undefined in prod
Using env var without
EXPO_PUBLIC_
Rename to
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
Token lost on app restartNo
tokenCache
Pass
tokenCache
from
@clerk/expo/token-cache
OAuth redirect not workingMissing scheme in
app.json
Add
"scheme": "myapp"
to
app.json
WebBrowser.maybeCompleteAuthSession
Not calledCall it at the top level of the OAuth callback screen
useSSO
not found
Old
@clerk/expo
version
useSSO
replaced
useOAuth
in v3+
现象原因解决方案
生产环境中
publishableKey
为undefined
使用了不带
EXPO_PUBLIC_
前缀的环境变量
重命名为
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
应用重启后令牌丢失未配置
tokenCache
传入
@clerk/expo/token-cache
导出的
tokenCache
OAuth跳转不生效
app.json
中未配置协议
app.json
中添加
"scheme": "myapp"
配置
鉴权流程卡住未调用
WebBrowser.maybeCompleteAuthSession
在OAuth回调页面的顶层调用该方法
找不到
useSSO
@clerk/expo
版本过低
v3+版本中
useSSO
替换了旧的
useOAuth

Import Map

导入映射

WhatImport From
ClerkProvider
@clerk/expo
tokenCache
@clerk/expo/token-cache
useAuth
,
useUser
,
useSignIn
@clerk/expo
useSSO
@clerk/expo
useOrganization
,
useOrganizationList
@clerk/expo
内容导入来源
ClerkProvider
@clerk/expo
tokenCache
@clerk/expo/token-cache
useAuth
,
useUser
,
useSignIn
@clerk/expo
useSSO
@clerk/expo
useOrganization
,
useOrganizationList
@clerk/expo

See Also

另请参阅

  • clerk-setup
    - Initial Clerk install
  • clerk-custom-ui
    - Custom flows & appearance
  • clerk-orgs
    - B2B organizations
  • clerk-setup
    - Clerk初始安装
  • clerk-custom-ui
    - 自定义流程与外观
  • clerk-orgs
    - B2B组织功能

Docs

文档