☰
Expo快速搭建移动端Demo:从初始化到真机打包全流程实践
2026/9/26 17:03:08 网站建设 项目流程

1. 为什么Demo首选Expo而不是原生工程

我最近帮一个朋友在48小时内搭了一个产品原型,需求是:一个App端Demo,能登录、能展示列表、能调用相机拍照,最好还能在安卓和iOS两台手机上同时跑起来给投资人看。我第一反应就是Expo。这里先交代一个背景:我平时不是那种"什么都想用跨端框架"的人,正经企业级项目我也写过不少原生代码,但"做Demo给你看"这件事,Expo几乎是当下最优解。

原因很直接。第一,速度快。原生工程哪怕用模板初始化,也要折腾Gradle、CocoaPods、模拟器配置,头一次跑通Hello World少说两小时;Expo配合Expo Go客户端,扫码就能预览,几分钟就能把第一个页面摊在真实手机上。第二,迭代轻。改完代码保存,真机上的App会自动刷新状态,不需要重新编译,这在给非技术同事演示的时候太重要了。第三,省心。新版本SDK把权限、推送、应用更新这些周期长的杂事都抽象成了配置项,Demo阶段根本不需要碰AndroidManifest和Info.plist。

当然也会有人说,Expo的底层能力有限,某些原生模块没法直接接。这句话放在两三年前还算成立,但现在的Expo Dev Client已经可以弹出原生代码,也能用config plugin注入自定义原生逻辑。对于Demo阶段,九成需求都不需要走到这一步。还有一点值得强调:Expo的生态特别适合"快速验证想法"。它不只提供运行时和构建工具,还围绕React Native沉淀了一批组件和工具,也就是大家常说的Expo UI生态。做Demo最怕的不是功能实现不了,而是"功能能跑但界面没法看"。Expo生态里现成的UI组件和模板,能帮你把这一点补齐。这篇文章我就从零开始,把我最近用Expo搭Demo的完整思路、操作细节和踩过的坑,一次性捋清楚。

适合看这篇文章的人有两类。一类是产品经理、独立开发者、学生,想快速验证App想法,不想先把时间耗在原生环境配置上;另一类是已经会写React,但还没怎么碰过React Native的前端工程师,想用最短路径熟悉移动端开发流程。我尽量少讲官方文档里已经写得很清楚的东西,多讲实际操作中才会遇到的判断和问题。

2. 初始化与目录结构:create-expo-app背后的几个隐藏选项

2.1 选对模板,比想象中重要

创建Expo项目的命令很统一,就一条:

npx create-expo-app@latest MyDemo

但很多人忽略了一点:这个命令默认装的模板,和你想的可能不一样。不同版本Expo的默认模板变化挺大,早期是空白的React Native + TypeScript结构,现在默认模板往往自带expo-router和一套示例页面。如果你是纯Demo用户,我的建议是先想清楚一件事:你这个Demo是"单页验证一下某个交互",还是"像模像样带几个页面的完整应用"。

如果是前者,用--template blank-typescript最轻:

npx create-expo-app@latest MyDemo --template blank-typescript

这个模板只有一个App.tsx入口,没有路由、没有多余依赖,非常适合验证单个UI组件或一个API交互。如果是后者,我的建议是直接用默认模板,因为默认模板已经配置好expo-router、TypeScript、基础组件示例,省去自己接路由的麻烦。

这里的逻辑要展开说一下。Demo和正式项目不同,正式项目你追求可维护性,会把路由、状态管理、网络层一个个单独架好;Demo追求的是"少写胶水代码",模板多带一点功能,反而帮你省时间。默认模板里的示例页面虽然花哨,但正好可以作为UI参考,你只需要把里面的假数据换成真实接口就行。

2.2 目录结构到底在表达什么

装完项目以后,你可能会被它的目录结构吓一跳。我来说说哪些是核心,哪些可以先忽略。

MyDemo/ ├── app/ │ ├── _layout.tsx │ ├── index.tsx │ └── ... ├── assets/ ├── components/ ├── constants/ ├── hooks/ ├── app.json ├── package.json └── tsconfig.json

app/这个目录是expo-router的约定,它代表的是"文件即路由"。app/index.tsx对应应用首页,app/about.tsx对应/about路径。这种设计对做Demo极其友好——要加页面,就新建一个tsx文件,不需要去任何配置文件里注册路由。我第一次用的时候还不太习惯,总想去找路由表,后来发现文件系统就是路由表,加页面真的只需要"新建文件、写组件、保存"三步。

app.json是Expo项目的核心配置文件。很多新手在这上面栽跟头,觉得它只是放着App名称和图标配置。实际上它控制着App的横竖屏、权限说明、包名、Scheme、插件启用等关键信息。我之前有个Demo在安卓上启动时崩溃,排查了半天,最后发现是app.json里配了一个userInterfaceStyle和一个不兼容的插件。所以记住:在Expo项目里改任何原生层面的配置,第一站永远是app.json。

2.3 TypeScript要不要一开始就上

我的答案是:直接上,别犹豫。即便你的Demo只写两百行代码,TypeScript也能帮你避免很多低级错误。create-expo-app默认模板已经内置TS支持,新项目不需要额外配置。有些朋友会跟我说:我就想快速看看效果,写JS不是更快吗?但实际操作中我发现,做Demo恰恰最需要类型提示,因为你要频繁调React Navigation、expo-router这些库的API,有类型定义时补全提示会大幅减少查文档的时间。所谓"快速",在工程技术上从来不是不写类型,而是别把时间浪费在因为拼错属性名而反复试错上。

初始化完成之后,先跑起来看一眼基础状态,再决定下一步动哪里:

npm run start

终端会出一个二维码,用手机上的Expo Go扫码就能打开。这一步如果顺利,说明你的环境没问题,可以开始写功能了。如果这一步就报错,八成是Node版本问题或者端口被占用,前者建议用nvm切换Node LTS版本,后者换端口或者关掉占用进程即可。

3. Expo UI生态里如何快速搭出能看的界面

3.1 别急着引第三方UI库,先在生态里找答案

做Demo时最容易犯的错,就是打开npm搜索UI库,看到哪个Star多就装哪个。我踩过这个坑:有次Demo需要一个底部弹窗,我装了一个很流行的库,结果它要求React Native版本严格对应,跟Expo SDK版本对不上,安装成功但运行崩溃,白白浪费了一下午。

在Expo项目里,第一优先要看的永远是Expo生态自己的东西。这个说法可能有点泛,我具体解释一下:Expo官方维护和推荐的那些库,和Expo SDK是同步发版的,升级SDK时它们也会跟着适配,兼容性风险最小。比如你要处理图片选择,用expo-image-picker;要处理权限,用expo-permissions相关的接口;要处理线性渐变、模糊效果,Expo也有对应的模块。这些不仅仅是"能用",它们默认就处理了iOS和Android的差异,这在Demo阶段特别值钱。

UI组件方面,现在Expo生态里也有很多现成方案。有一类是基于React Native核心组件但做了一层统一封装的组件集,提供按钮、列表、表单、反馈提示等基础件;还有一类是完整主题体系,预置了配色、字体、间距规则。我的建议是:如果你确实希望界面"看起来是设计过的",找一个组件集跟着主题走,比自己用StyleSheet凑要快得多。做Demo不是做设计提案,用户关心的是功能通不通、交互顺不顺,视觉统一就够了。

3.2 用StyleSheet搭界面时值得记住的几个细节

我见过不少React开发者转到Expo后,习惯用CSS-in-JS的思维写样式,然后被React Native的样式模型教育了一顿。这里把要点说透:

React Native的样式和Web CSS看起来像,但有几个关键差异。第一,默认布局是flex,但flexDirection默认是column,不是row,所以竖着排列是天然的,横向排列要显式写flexDirection: 'row'。第二,不支持margin合并,Web上两个元素上下margin会折叠,React Native不会。第三,所有尺寸都是无单位的数字,对应dp/pt,不能用px、rem这些单位。第四,没有"类选择器"和"后代选择器",样式直接绑定在组件上,或者通过一个StyleSheet对象共享。

为了帮你更快理解,我给一个简单的卡片样式示例:

import { StyleSheet, View, Text } from 'react-native'; export default function Card({ title, desc }) { return ( <View style={styles.card}> <Text style={styles.title}>{title}</Text> <Text style={styles.desc}>{desc}</Text> </View> ); } const styles = StyleSheet.create({ card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, marginVertical: 8, shadowColor: '#000', shadowOpacity: 0.08, shadowRadius: 8, elevation: 3, }, title: { fontSize: 18, fontWeight: '600', marginBottom: 4, }, desc: { fontSize: 14, color: '#666', }, });

提一个很多人不知道的细节:shadow*系列是iOS的阴影属性,安卓上需要用elevation才能显示阴影效果。如果你只想统一外观,可以两层都写上,上面这个示例就是这种兼容写法。

3.3 用expo-router组织多个页面时,别把UI逻辑全塞在组件里

当Demo从单页变成多页,最容易出现的情况是:每个页面文件里直接写满了fetch请求、loading状态、错误提示和UI结构。短期看没问题,但你要给别人演示功能时,中间态的界面粗糙会明显拉低整体质感。

我自己的习惯是,在Demo阶段先做一个极简的"状态容器":把每个页面的核心逻辑抽到hook里,比如useFruitList()、useUserInfo(),然后把返回数据、loading、error都交给页面组件渲染。这样做的好处是,页面组件只关心"数据到了以后怎么显示",后续更换UI组件集时,不需要动逻辑层。而且,Expo Router的页面文件应该是轻量的,它只需要做路由参数读取和页面拼接,不要承担数据请求逻辑。这样代码结构干净,演示时也更容易临时加页面。

很多Demo项目死掉不是因为功能不够多,而是因为页面间跳转的关系混乱,演示到一半自己都不清楚"现在从哪进入这个页面的"。用expo-router的文件路由后,每个页面有明确的访问路径,调试时还能直接改URL跳转,这在开发期简直是Debug利器。真机上你甚至可以通过deep link直接打开指定页面,这在Demo演示时非常实用——比如你想跳过登录环节,直接展示功能页,只要构造对应的路径打开就行。

3.4 交互反馈:Toast、loading和下拉刷新别自己造

我总结过Demo翻车的高频原因:不是功能报错,而是用户操作后没有反馈。按钮点击后界面没变化,请求发出后没有loading提示,操作成功也没有任何Toast通知,用户会以为App卡死了。

Expo生态里常见的选择是做一套轻量的交互反馈组件:加载中显示spinner或骨架屏,操作结束给一个Toast,下拉刷新用系统自带RefreshControl。如果你用的UI组件集已经包含了这些组件,就直接用;如果还没有,也千万不要自己写Toast动画,直接用生态里的现成实现,因为移动端的交互细节比Web多,自己写很容易出现键盘遮挡、安全区适配问题。

我实际用下来,最稳妥的做法是:页面级的loading用系统ActivityIndicator或组件库的loading组件,操作成功用Toast,操作失败用Alert.alert弹窗,这些都是系统级反馈,用户接受度高,实现成本也很低。有人说Alert太丑,但Demo里"能用、明确、不出错"比"好看"优先级更高,等你确认交互路径没问题,再换成更精致的定制弹窗也不迟。

4. 真机预览、刷新和调试链路里最容易翻车的地方

4.1 不是扫个码就万事大吉:Expo Go、Tunnel和网络环境的三角关系

做Demo,大部分时间你都在用Expo Go在真机上看效果。但Expo Go能不能连上你的开发服务器,受网络环境影响很大。通常Expo启动后默认使用LAN模式,要求手机和电脑在同一个局域网内。如果不在同一网络,比如手机用的是4G/5G网络,或者电脑和手机隔着一个复杂的访客Wi-Fi,就会一直转圈连不上。

这时有两个常用办法。第一个是在终端按字母t切换到Tunnel模式,Expo会建立一个远程通道,手机不走局域网也能访问开发服务器。方便是方便,但速度会慢一些,而且有缓存,改动后刷新偶尔不及时。第二个办法是用--lan再指定某个网卡地址,排查局域网问题。我自己的经验是:能LAN就连LAN,Tunnel真的只是备选方案。有次我在咖啡馆演示,咖啡馆的访客网络强行隔离了设备,LAN彻底失效,Tunnel虽然慢,但撑住了整个演示过程,所以这个备选方案一定要知道。

还有一个常见问题是二维码对应的IP地址是旧地址。电脑重启换了个网段,或者路由器重新分配了IP,但终端上还留着旧地址,扫码后访问不通。遇到这种情况,重新运行npm run start或者按r刷新,基本都能解决。

4.2 刷新机制不是你想象的热更新

Expo开发模式下的刷新,分为"快速刷新"和"完全重新加载"两种。快速刷新保留了组件状态,你改了一个样式、一个文案,界面会平滑更新,非常舒服。但有些改动是快速刷新无法处理的,比如修改了app.json里的配置、新增了依赖、改了路由结构,这时候就要完全重新加载。

新手最容易踩的坑是:改了app.json里的应用名称或图标,保存后看到终端提示更新了,但手机上的Expo Go没变化,于是以为坏了。实际上app.json的改动触发的是全量重新构建,Expo Go里需要手动完全退出这个项目,再重新扫码进入。Android上更彻底一点,建议在Expo Go里把当前项目移除,再重新扫码,否则有时会残留旧配置。

调试方面,Expo官方提供一条调试链:手机摇一摇打开开发菜单,选择Debug,就能在浏览器里打开DevTools调试JS。这个流程在Demo阶段够用了,网络请求、console日志、元素树都能看到。还有一个实用技巧:在代码里可以开启expo-dev-menu的启动参数,让开发菜单始终显示,方便反复调试启动参数和缓存问题。

4.3 依赖装错版本是Demo第一杀手

我几乎每次帮人排查Demo运行不了,最终原因都指向同一个:依赖版本和Expo SDK不匹配。Expo SDK每代都有对应的React Native版本,而第三方库对React Native版本有要求。装了不兼容的库后,启动时大概率报错,甚至直接白屏。

这里给三个实操建议。第一,装库前先查这个库是否支持当前Expo SDK版本,你可以npx expo install来安装Expo官方推荐的库,它会自动匹配当前SDK的兼容版本;普通的npm包才用npm install。第二,不要轻易用npm install latest,latest未必兼容当前Expo版本。第三,一旦出现"library not found"或者"undefined is not an object"这类不明错误,先把最近安装的依赖逐一卸载,定位是不是某个包造成的。

下面这个命令是我每次排错的第一步,它会检查整个项目的依赖兼容性:

npx expo-doctor

它会把有问题的依赖列出来,并提示你哪些版本需要调整。这个命令在新手群里不够知名,但它真的能帮你省掉很多无头绪排查。把expo-doctor跑一遍、把已知问题修掉,再去查业务代码错误,顺序千万别反。

4.4 给演示留好后路:固定IP和本地缓存

如果你要在正式场合演示,不要依赖现场网络环境。我自己的做法是提前做好三手准备:第一,在项目里把默认的接口地址设置成本机局域网IP或一个稳定的内网地址,而不是localhost;第二,准备一个Tunnel通道,万一现场网络连锁路由器都不开放,还能靠它兜底;第三,重要功能要能本地mock数据运行,避免现场后端接口突然挂掉。

有人觉得Demo而已,没必要准备那么多。但我的真实经历是:演示现场最不可控的就是网络。你有十成把握后端没问题,但会议室Wi-Fi偏偏把接口域名解析挂了。所以Demo里把关键页面和数据层解耦,提供一套内置mock数据,这样的健壮性不是过度设计,而是给演示买保险。

5. 给Demo收尾:打包、给同事演示以及后续扩展思路

5.1 要用EAS Build生成一个可安装的包

Demo做到后面,你可能不满足于只在Expo Go里跑,而是希望生成一个独立的APK文件,直接发给同事安装,或者装到演示平板上。这时候要了解EAS Build。它是Expo官方托管构建服务,会在云端帮你构建一个独立的App包。

流程大致是:先装依赖:

npm install -g eas-cli

登录Expo账号并配置项目:

eas login eas init

然后指定构建计划:

eas build --platform android --profile preview

第一次构建要配置android package name,取一个唯一的反向域名,比如com.mycompany.mydemo。构建完成后,控制台会给你一个下载APK的链接。这个过程大概十到二十分钟,比本地自己配Gradle环境快一个量级。

有一点要提醒:EAS Build免费层有构建次数限制,Demo阶段一般够用,但如果大量频繁构建,还是要留意配额。另外,构建时需要联网,而且上传项目代码到Expo服务器时,如果你的项目里有私密配置,注意不要写死在代码里,用环境变量来管理。

5.2 应用图标和启动屏:两个最容易被忽略的细节

Demo如果只是自测,图标无所谓。但如果你要把APK发给别人演示,App装到手机上却显示一个默认图标,体验分会掉很多。Expo提供了配置图标和启动屏的方式:准备一个1024x1024的PNG,放到assets/icon.png,在app.json里配置icon字段即可。启动屏可以用一张图片或配置背景色。

这里有个实操建议:即使你没有设计师资源,也可以用简单的纯色背景加一个居中的文字Logo来当启动屏,看起来也比默认配置专业得多。安卓上图标会有圆角遮罩的问题,如果你的图标不是全幅背景图,最好四周留一些安全边距,否则边缘可能会被裁掉。另外,不同系统对启动屏的适配要求不一样,Expo配置后会自动生成适合各平台尺寸的图片,所以尽量不要自己手动去设置多尺寸图片,交给app.json统一处理即可。

5.3 当Demo要长成MVP:哪些能力可以平滑升级

很多Demo最后都会被追问一句:"这个能不能直接做成正式产品?"这个问题的答案,直接决定你当初在Expo上的选择是否值得。我的看法是,Expo不仅适合做Demo,也适合作为MVP的起点,关键在于你在Demo阶段是否留了升级的空间。

具体来说是三件事。第一,多页面应用从一开始就使用expo-router,因为它是文件路由,后面扩展页面几乎无成本。第二,网络层单独封装,不要在每个组件里散落fetch,这样以后要接GraphQL、加拦截器、做缓存都很方便。第三,状态管理在Demo阶段可以用React自带能力,但一旦涉及多个页面共享用户信息或配置,建议尽早引入轻量级状态库,不要等到互相传参传得头晕才重构。

另外,如果未来要上架应用商店,EAS可以提交构建产物,配合expo-updates可以做热更新。热更新这个能力是Expo做MVP非常有优势的点:你可以先把审核通过的版本发到商店,之后改完JS代码直接推给用户,不用重新走一遍商店审核流程。对新项目来说,这能让你保持很高的迭代速度,同时控制风险。配置expo-updates并不复杂,在app.json里添加插件,然后设置更新服务器的URL即可。第一次配置需要一点耐心,但配完以后的收益非常大。

5.4 我的一个收尾习惯:用"能演示的状态"作为验收标准

最后分享一个我自己的习惯。做Demo时,我会把"能不能在别人手机上顺利跑通主流程"作为阶段性的验收标准,而不是单纯看代码写完没有。所以我每隔一段时间就会让身边的人用Expo Go扫码,从没有看过的视角走一遍主流程。这个习惯帮我发现了很多自己发现不了的问题,比如某个页面依赖了开发环境才有的数据、某个跳转需要上一次进过对应页面才能工作、某个按钮在字体调大后会溢出屏幕。

这些细节单靠自己是没办法完全发现的,而Demo阶段发现这些问题的成本是最低的。不要等打包发出去以后,再收到一张"这里打不开"的截图。Demo的意义不在于技术实现有多完美,而在于它能准确传达你想要验证的那个想法。Expo让我把更多精力花在想法本身,而不是跟工程环境纠缠,这种体验在做快速原型时非常难得。如果你正准备搭自己的第一个移动端Demo,我的建议很简单:先把Expo项目跑起来,把一个页面做到"能在真机上流畅运行",再考虑要不要进入下一个阶段。当你走完这一步,后面的事情会比想象中顺利得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询