关于 expo-web-to-native
expo-web-to-native 是一项 Expo 技能,为现有 React Web 应用提供渐进式原生迁移路径,并在逐步转换屏幕期间保持可发布状态。它涵盖评估、DOM 外壳、屏幕重设计、数据接入和发布。
主要功能
- 01评估并分类屏幕
- 02先搭建 DOM 组件外壳
- 03逐步将屏幕转换为原生界面
- 04通过参考资料指导原生重设计
- 05对照 Web 应用验证运行中的屏幕
- 06接入数据、身份验证和存储
适用场景
- 01规划现有 React 应用的渐进式迁移
- 02在原生屏幕开发前发布 Web 外壳
- 03确定原生重设计屏幕的优先级
- 04对比原生屏幕与 Web 原版的行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ expo/ skills --skill expo-web-to-native安装后在 Claude Code 中输入/expo-web-to-native确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/false-friends.mdnative-patterns.mdrun-as-goal.mdverify-on-device.md详细介绍
从 Web 迁移到原生
Web React 应用不会转换为原生应用——没有转译器。它会逐个屏幕地迁移,就像绞杀榕围绕一棵树生长并逐渐取代它:搭建一个原生外壳,第一天就在里面运行整个 Web UI,然后按照优先级逐个将屏幕绞杀为原生屏幕。本技能是安排工作顺序的主干;每一步都会交由现有的 Expo 技能处理,而不是重新解释一遍。它将 Expo 的 From Web to Native with React 落到实处——阅读该文了解原因。
flowchart TD
A1[1 · Assess: write the worklist] --> A2[2 · Scaffold Expo shell]
A2 --> A3[3 · DOM-component shell<br/>· expo-dom · SHIP DAY ONE]
A3 --> A4[4 · Strangle screens to native<br/>highest-value first · expo-router]
A4 -->|more screens| A4
A4 --> A5[5 · Wire data / auth / storage<br/>· expo-data-fetching]
A5 --> A6[6 · Ship · eas-app-stores]
原则
- 迁移,不要重写。 绝不一次性全面替换;每一步都要确保应用可以交付。
- 第一天就交付。 在任何内容原生化之前,Web UI 就会在 DOM 组件外壳中运行(第 3 步)——这就是里程碑;之后的一切都是优化。
- 按价值逐步替换。 将高频使用的屏幕原生化,其余部分保留在 WebView 中。每个 DOM 屏幕都会携带约 2 MB 的 Web 运行时——这足以说明不该把所有内容都作为 DOM 来交付。
- 原生化意味着重新设计,而不是换皮。 被替换的屏幕应该看起来像 Apple/Google 原生推出的界面,而不是套了原生外观的网页。优先使用
@expo/ui——它会渲染真正的 SwiftUI/Compose,因此感觉与操作系统完全一致;仅在需要自定义布局时才将带样式的 RN 基元作为后备方案。此外,还可以使用平台导航(expo-router:NativeTabs、大标题)、通过@expo/ui实现的 Liquid Glass 和原生组件,以及移动端 UX(底部弹层、滑动、触觉反馈)。Web→原生模式映射见./references/native-patterns.md。如果它看起来仍像网站,那你只是移植了界面,并没有重新设计。 - 通过运行验证,而不是编译。 构建成功证明不了什么(空白 WebView 也能编译通过)。运行每个屏幕——但要对照 Web 原版评估内容和行为,而不是像素(原生化后的屏幕应该更有原生感,而非完全相同)。
- 统筹编排,不要重新发明。 每一步都会转交给现有技能。这里的价值在于顺序和注意事项——逐项习惯用法的映射见
./references/false-friends.md。
按循环方式运行(推荐)
迁移是一个漫长的“重复直到完成”循环,因此第一步是编写目标说明并启动它——而不是手动逐个处理屏幕。在 ./references/run-as-goal.md 中填写此应用的目标说明并呈现出来;它会在每次迭代中重新读取本技能,因此每一轮 /goal 都会重新加载操作指南和工作清单,并推进下一个屏幕(甚至会自行引导 assess 步骤)。然后使用该目标说明运行 /goal——或者,如果运行环境无法循环,就将它写入 migration-goal.md 并让用户启动。下面的步骤就是每次迭代要执行的内容;只有在不使用循环时才手动执行。
迁移流程
没有要迁移的代码仓库——只是作为 Web 开发者从头构建原生应用?你不需要这些步骤:使用
expo-router,并打开./references/false-friends.md作为 Web→原生习惯用法映射参考。以下所有内容都假设已有一个 Web 应用。
1. 评估 → 编写工作清单
阅读代码仓库并生成 migration-progress.md,作为后续迁移检查的持久工作清单。划分两个方面:
- 屏幕与后端。 页面路由(
page.tsx)是要迁移的屏幕;服务端路由(route.ts)、ORM 和身份验证处理程序仍留在服务端。一次性决定后端方案:保持部署状态(原生应用成为 HTTP 客户端),或将其迁移到 EAS Hosting(eas-hosting)。 - 为每个屏幕分类,确定其迁移方式:原样移植(展示型 → 在 DOM WebView 中运行)、立即原生化(高频使用,或需要原生体验——手势、列表、键盘)、稍后原生化,或混合式(原生外壳包裹 Web 子树,例如,用聊天列表包裹 Markdown 渲染器)。
阅读时记下框架相关信息——RSC 还是客户端、Tailwind/shadcn、数据获取位置——因为这些会影响每个屏幕的迁移方式(false-friends 中有对应关系;尤其是异步服务端组件,必须拆分为客户端获取数据组件和展示组件,才能迁移)。也要标记第三方服务/SDK——浏览器 SDK 无法直接沿用(false-friends → 服务与 SDK);支付尤其需要注意,应分叉处理,而非替换(应用内数字商品必须通过 RevenueCat 使用应用商店 IAP,约 30% 抽成——不能用 Stripe),这是现在就要做出的商业模式决策,而不是等到 App Store 审核时再决定。只有在所有路由都完成分类、所有屏幕都完成归类后,这份工作清单才值得信赖。
2. 搭建外壳
使用 create-expo-app,然后在 Expo Router 中映射 Web 路由——Next 的路由树几乎可以一对一映射(注意:[id]/page.tsx → [id].tsx,路由也可能位于 src/app/)。为每个路由创建一个空屏幕。
3. 使用 DOM 组件搭建外壳——第一天的里程碑
将每个屏幕迁移为 DOM 组件(使用 'use dom',参照 expo-dom 技能),并由其原生路由渲染,这样在进行原生化之前,整个应用就能在手机上运行。预计需要逐屏修改——解除服务端组件封装、替换框架导入(next/link)、沿用样式——这些都由 false-friends 涵盖。然后按下文所述运行并验证;完成后即可直接提交 TestFlight。
4. 按价值将屏幕原生化
从上到下依次处理 migration-progress.md。对每个屏幕,使用原生方式重新设计,不要照搬 Web 布局。优先使用 @expo/ui(真正的 SwiftUI/Compose——按钮、列表、底部弹出层、选择器、滑块;./references/native-patterns.md 对照列出了哪些 Web 模式对应哪些原生组件),然后使用平台导航(expo-router——NativeTabs、大标题)和移动端 UX(滑动操作、触觉反馈、惯性/反向滚动);仅在自定义布局时使用 RN 基础组件。每种惯用模式都可参阅 ./references/false-friends.md。@expo/ui 和 DOM 组件都能在 Expo Go(SDK 56+)中运行——只有在使用自定义原生模块时,才需要开发构建(expo-dev-client 技能)。对照运行中的 Web 原版,验证内容和行为(外观应更具原生感),然后勾选完成。每轮只处理一个屏幕,应用始终可发布。这是一个围绕持久工作清单的循环,因此可以无人值守地运行——将其交给目标循环(./references/run-as-goal.md)。
5. 接入数据、身份验证和存储
Web 数据层无法在迁移后继续使用——相对路径请求、Cookie 会话、localStorage 和环境变量都需要调整(替换方案见 false-friends)。使用 expo-data-fetching 处理请求和缓存;如果后端迁移到了 EAS Hosting,则添加 eas-hosting。
6. 发布
使用 eas-app-stores 构建应用商店版本(App Store / Play / TestFlight),之后使用 EAS Update 推送 OTA 更新。
通过运行验证,而不是编译
expo export 成功只能证明屏幕已打包,不能证明它能正确渲染——屏幕可能构建成功,却仍然显示空白或渲染错误。因此,在完成外壳以及每个原生化屏幕后,都要针对同一路由比较两个正在运行的应用:
- 原始 Web 应用 — 使用
agent-browser(vercel-labs CLI)截取:打开路由,执行snapshot --json获取无障碍树,再执行screenshot。 - 原生应用 — 使用
argent驱动模拟器:用describe/debugger-component-tree查看结构,并用flow在每轮中重放检查。
通过标准是内容和行为一致,而非像素一致:原生化屏幕应比 Web 更有原生感,而不是与 Web 完全相同(DOM 外壳阶段除外——该阶段呈现的就是 Web UI,因此应保持一致)。原生体验也包含感受,无法通过截图体现——对于带有转场或手势的屏幕,请录制一段短视频,而不只是截取静态画面(参见 native-patterns.md → Feel)。这个流程对所用工具有明确要求:如果没有安装 agent-browser 或 argent,请先询问用户并安装,然后再继续——不要改用手动截图。完整流程和设置方法见 ./references/verify-on-device.md。
参考资料
./references/false-friends.md— Web 习惯用法 → 原生等效方案,以及每种方案的注意事项。用于第 3–5 步的查询,也适合需要改掉 Web 开发习惯的情况。./references/native-patterns.md— Web UX 模式 → 原生重新设计(优先使用@expo/ui)。用于第 4 步的重新设计指南,让屏幕具有原生 OS 的体验,而非仅仅换肤。./references/verify-on-device.md— 双 Agent 的一致性检查流程:驱动 Web 应用(浏览器 Agent)和原生应用(argent),打开同一路由并进行比较。./references/run-as-goal.md— 现成的、针对迁移任务的目标,用于无人值守地执行第 4 步(每次迭代都会重新读取此技能)。- Expo — 使用 React 从 Web 走向原生 — 本技能付诸实践时所依据的权威指南。
提交反馈
如果你发现此技能存在错误、容易误导或已经过时的信息,请提交反馈,帮助 Expo 改进:
npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-web-to-native" "<actionable feedback>"
只有在有具体且可执行的反馈时才提交。请尽可能提供相关背景信息。
如果 Agent 反复无法完成 Expo 任务,或用户不得不介入,请使用 --category evals 提交简要报告,说明任务、预期结果和实际问题,并将相关 Expo 软件包或命令作为 --subject。