数据层

使用类型化 Supabase 客户端、TanStack Query 和持久化模式。

Expo 模板的数据层相当于原生模板中的 Repository pattern:类型化 Supabase 调用放在小型 feature module 中,TanStack Query 负责 loading、cache、retry、pagination、offline awareness 和 mutation 后的刷新。

技术栈

文件作用
Supabase clientlib/supabase.ts使用 types/database.ts 类型的 createClient<Database>()
Query providerlib/query.tsx全局 QueryClient、offline-first 默认值、focus/online managers
Query keyslib/query.tsxprofile、todos、uploads、subscription、version config 的稳定 key
Feature moduleslib/profile.tslib/sample-uploads.tslib/referrals.ts把 query/mutation 函数从 screen 组件中抽离
Screen queriesapp/todos.tsxapp/upload-sample.tsx把 feature 调用组合成 UI 状态、缓存更新和 toast

schema 修改后运行类型生成:

pnpm gen:types

之后尽量从 types/database.ts 导入 table types,不要手写 row shape。

Query provider

QueryProvider 使用 PersistQueryClientProvider 包住应用,默认值如下:

设置效果
networkModeofflineFirstqueries 和 mutations 能容忍临时离线状态
Query retry最多 2 次 transient failure只重试网络类错误;validation/auth 错误不重试
staleTime1 分钟最近成功数据避免马上 refetch
gcTime24 小时内存 cache 可以跨普通导航保留
持久化AsyncStorage key SOAR_QUERY_CACHE成功 query 最多可跨启动恢复 7 天

NetInfo 驱动 TanStack Query online manager。在 native 上,AppState 驱动 focus manager,让应用回到前台时可以刷新 stale data。

Feature modules

可复用数据调用放在 lib/,screen 只负责呈现状态。

  • lib/profile.ts 读取和 upsert profiles,用 expo-image-manipulator 压缩头像,上传到 avatars bucket,并解析 signed URL。
  • lib/sample-uploads.ts 读取最近的 sample_uploads rows,并通过带进度的 XMLHttpRequest 上传文件到 Supabase Storage。
  • lib/referrals.ts 基于 deep-link helper 生成分享链接。
  • lib/network.ts 把 transient failure 映射为用户友好文案,并提供 retryAsync
  • lib/cache.ts 把小型 JSON payload 存进 AsyncStorage 或 localStorage。

profile.ts 引用了 avatars bucket,但随附迁移只创建 sample-uploads。如果保留头像上传,请为项目添加 avatars bucket 和对应策略。

Todos 参考功能

app/todos.tsx 是最完整的 CRUD 参考:

  • useInfiniteQuery 每次分页读取 20 条 todos
  • 查询按 user_id 过滤,同时 RLS 仍在服务端强制所有权。
  • 新增、切换、编辑、删除、清理已完成 mutation 成功后,用 queryClient.setQueryData(...) 更新 query cache。
  • 重排会向 Supabase 写入 sort_order
  • 如果 refetch 失败但已有缓存 rows,UI 会显示上次保存的 todos,并弹出 reconnect toast。

这里的“乐观”是指 mutation 成功后立即把结果合并到 cache;它不会假装失败的写入已经成功。

离线行为

应用有三层离线行为:

  • TanStack Query 会跨启动持久化成功 query 结果。
  • NetInfo 标记 QueryClient 的 online/offline 状态,并可驱动 OfflineBanner
  • toNetworkFriendlyMessage 把 transient errors 转成统一文案。

components/offline-banner.tsx 已实现,但 root layout 当前没有全局挂载 NetworkStatusProviderOfflineBanner。如果产品需要全局离线横幅,把它们加在 navigator 外层即可。

RLS 预期

客户端会按已登录用户过滤,但真正的信任边界来自 Postgres RLS:

表/资源客户端访问
profiles用户可 select、insert、update 自己的 profile row
todos用户可 select、insert、update、delete 自己的 todos
sample_uploads用户可 select、insert、delete 自己的 upload rows
sample-uploadsstorage.objects用户可读写删除第一个路径段等于自己 user ID 的对象
subscriptions / payments用户只能 select 自己的 rows;service-role Edge Functions 负责写入

添加功能

添加迁移

supabase/migrations/ 中创建表、索引、触发器和按 owner 隔离的 RLS policy。

重新生成类型

pnpm gen:types

合适时使用生成的 Tables<"your_table"> helper。

创建 lib/ module

把 Supabase from(...)storage.from(...) 和数据映射放在小模块里,不要散落在 screen 中。

添加 query keys 和 hooks

queryKeys 中添加稳定 key,然后从 useQueryuseInfiniteQueryuseMutation 调用你的 module。

渲染 screen

SignInGate 处理未登录状态,用 toNetworkFriendlyMessage 映射错误,并在 mutation 后更新或 invalidate cache。

相关页面

On this page