架构
了解运行时架构、Provider、启动门禁和导航。
Expo 模板把架构放在少数关键文件中,而不是藏在额外框架层里。
app/_layout.tsx 是 runtime composition root,Expo Router 负责导航,
TanStack Query 负责 server state,provider hooks 是 dependency-injection surface。
路由
Expo Router 把文件映射为 routes,app.config.ts 开启了
experiments.typedRoutes。
| 模式 | 文件或示例 | 说明 |
|---|---|---|
| Route groups | app/(auth)、app/(tabs) | 分组文件,不改变 URL path |
| Tabs | app/(tabs)/_layout.tsx | 四个可见 tab,加上 href: null 的隐藏 tab-context screens |
| Pushed screens | app/todos.tsx、app/profile.tsx、app/permissions.tsx | 从 drawer、cards 或 deep links 打开 |
| Dynamic routes | app/component/[slug].tsx | 用 useLocalSearchParams() 读取 slug |
| Modals | Stack.Screen name="paywall" 和 name="redeem-code" | Root stack 设置 presentation: 'modal' |
| Fallback | app/+not-found.tsx | 处理未知 path |
| Web shell | app/+html.tsx | 供 pnpm web 与 Metro static output 使用 |
可通过 deep link 打开的 path 在 lib/deep-links.ts 中显式 allow-list。
新增 route 不会自动变成适合 push notification、referral link 或外部 URL 打开的页面。
Provider stack
RootLayout 由 Sentry.wrap(...) 包裹,内部顺序如下:
| 顺序 | Provider/component | 作用 |
|---|---|---|
| 1 | RootErrorBoundary | 品牌化 runtime fallback 与 Sentry capture |
| 2 | GestureHandlerRootView、KeyboardProvider、SafeAreaProvider | gestures、keyboard、insets 所需 native infrastructure |
| 3 | QueryProvider | TanStack Query cache、persistence、focus/online managers |
| 4 | AuthProvider | Supabase session 与 auth actions |
| 5 | AnalyticsProvider | PostHog capture、identify/reset、consent-aware queueing |
| 6 | ConsentProvider、OnboardingProvider | 持久化 legal 与 onboarding preferences |
| 7 | EntitlementProvider、PurchasesProvider | Supabase entitlement read-model 与 RevenueCat purchase mechanism |
| 8 | I18nProvider | t、locale、setLocale |
| 9 | ThemePreferenceProvider、React Navigation ThemeProvider | NativeWind preference 与 navigation colors |
| 10 | NotificationsProvider、BiometricProvider、ToastProvider、BottomSheetModalProvider | App services 与全局 UI surfaces |
这些 gates 不是包住 navigator 的 wrapper。它们是 provider tree 内的 sibling:
OnboardingGate、AuthGate、VersionGate 在 root Stack 前渲染;
ConsentGate 和 BiometricGate 在后面作为 overlay-style gates 渲染。
依赖注入通过 hooks 完成:useAuth()、useI18n()、useEntitlement()、
usePurchases()、useToast()、useNotifications()、useThemePreference(),
以及各 service helper。
State conventions
| State 类型 | 负责人 |
|---|---|
| Server state | lib/query.tsx 与 screen hooks 中的 TanStack Query |
| Supabase session | AuthProvider;native 上用 SecureStore chunking |
| Entitlement | EntitlementProvider;读取 subscriptions table |
| Purchases | PurchasesProvider;RevenueCat SDK state 与 purchase actions |
| App preferences | 小型 providers,底层使用 AsyncStorage/SecureStore/localStorage |
| UI notifications | ToastProvider、NotificationsProvider、bottom sheets/dialogs |
模板没有 Redux 或 Zustand。只有当未来功能真的需要全局 client state,且它不属于 server cache、auth/session state 或简单持久化偏好时,才考虑引入。
Styling
Styling 分三层:
| 层 | 文件 | 作用 |
|---|---|---|
| NativeWind classes | Screens 与 components | 用 className 表达 layout、type、spacing、tokens |
| CSS variables | global.css | light/dark shadcn-style tokens 与 iOS grouped color tokens |
| JS tokens | lib/theme.ts | THEME、IOS_COLORS、getIOSColor()、NAV_THEME |
可复用 variants 使用 CVA,例如 buttonVariants、buttonTextVariants、
textVariants。Platform fallback 使用常规 React Native 文件模式:
store-review.web.ts 与 tracking-permissions.web.ts 在 web 上 no-op。
Code style 与 checks
模板使用 strict TypeScript、带 Tailwind class sorting 的 Prettier、Expo ESLint flat config、Husky 与 lint-staged。
pnpm typecheck
pnpm lint
pnpm format:check当前模板没有 Jest/Vitest 配置。随附的自动检查是 TypeScript、Expo lint、formatting, 以及 RevenueCat webhook mapper 的 Deno test:
deno test supabase/functions/_shared/revenuecat.test.ts