前阵子帮朋友搭一个新的跨端项目,对方开口就是“用expo创建react-native”,我愣了一下——这个说法虽然有点绕,但确实点出了当下 React Native 开发最主流的一条路:直接用 Expo 这套工具链来初始化、开发、调试和打包 RN 应用。
我用 Expo 创建 React Native 项目已经踩过不少坑,从一开始的“这东西是不是玩具”,到后来发现它在中小型项目里是真的省事。这篇文章就把我从零到一把项目跑起来的完整过程、关键决策、常见坑位一次讲清楚,内容偏向实操,适合刚接触 RN 生态的开发者,也适合那些在裸 RN 和 Expo 之间反复纠结的人。
1. 整体设计与思路拆解
1.1 为什么选择 Expo 而不是裸 React Native
先说一个很多人都有的误区:以为 Expo 是 React Native 之外的另一套框架。其实不是,Expo 是构建在 React Native 之上的一整套工具和服务,它的核心目标是让你不用碰 Android Studio 和 Xcode,也能完成 RN 应用的开发、调试和上架。
我自己最早用 RN 是纯手动搭建环境,那体验怎么说呢,光是处理 Gradle 版本冲突和 CocoaPods 依赖就能耗掉一整个下午。后来切到 Expo,最直观的感受是:“原来 React Native 也可以这么好上手”。你不需要先装 JDK、SDK、模拟器,只要装了 Node,用 npx create-expo-app 创建项目,再用手机装一个 Expo Go,扫码就能在真机上看到界面。
那是不是所有项目都该用 Expo?也不是。如果你要做的是对原生代码高度定制的事,比如需要集成某个第三方 SDK 且 Expo 没有对应模块,或者你们团队本身就在维护一套原生代码库,那这时候裸 RN 可能更合适。但如果你的目标是快速验证产品、开发业务逻辑密集的跨端应用、或者团队里没有专职原生开发人员,Expo 绝对是目前性价比最高的选择。
给个我常用的判断标准,可以直接抄作业:
| 判断项 | 选 Expo | 选裸 RN |
|---|---|---|
| 团队原生开发能力 | 没有或很弱 | 有专职原生开发 |
| 需要自定义原生代码 | 很少或不需要 | 经常需要 |
| 项目交付周期 | 短,要快速出结果 | 长,不急于上线 |
| 是否需要 Expo 云服务 | 需要,或多设备联调频繁 | 不需要 |
| 已有原生项目需要集成 | 否 | 是 |
1.2 创建 Expo 项目背后的核心流程拆解
其实整个流程拆开来看,就几个核心环节:环境准备、初始化项目、选择模板、启动开发服务、真机联调、UI 组件选型、构建发布。每个环节单独看都不复杂,但串起来的时候如果对底层机制不清楚,很容易在某个点卡住。
比如有个朋友在 Windows 上跑 Expo,每次启动开发服务器都报错,后来发现是防火墙把 8081 端口给拦了。还有人在创建项目时死活用不了模板参数,最后才发现是 npx 缓存了旧版 create-expo-app。这些问题都不是什么高深技术,但因为很少写进官方文档,你只能一个个踩过去。
所以我这篇文章不只是教你怎么敲命令,更想帮你在脑子里建立一张完整的“Expo 项目创建全景图”。你知道了每一层在干什么,之后遇到报错才会知道去哪里排查,而不是搜到一条命令就盲目复制。
2. 核心细节解析与实操要点
2.1 环境准备:Node、npm 和 Git 的基本要求
先说环境,这是整个流程里最不应该出错却最多人出错的地方。Expo 官方要求 Node LTS 版本,我建议直接用 Node 18 或 20 的 LTS 版本。版本太老会导致 CLI 命令报语法错误,版本太新又可能出现某些原生模块编译不兼容的问题。
npm 一般会随着 Node 一起装上,这没问题。Git 也建议装好,因为 create-expo-app 初始化的项目默认就带 Git 仓库,如果你机器上没装 Git,项目创建后会有警告,后面的版本管理也会受影响。
我实际测试下来,最容易忽略的是 Watchman。macOS 用户建议装上它,用来监听文件变化。如果不装,Metro Bundler 的文件监听机制可能因为系统文件描述符限制而失灵,典型症状是:你保存了代码,但模拟器里界面半天不刷新,或者刷新后直接红屏报错。
Windows 用户则是另一个常见痛点,cmd 和 PowerShell 执行 npx 时如果有权限问题,可以先试试以管理员身份打开终端。实测下来 Windows 下用 Git Bash 跑 Expo 命令比 PowerShell 稳。
再来检查一下环境:
node -v npm -v git --version三条命令正常输出版本号,就说明基础环境没问题。
2.2 创建项目的三种方式和模板选择
创建 Expo 项目现在统一用 create-expo-app 这个脚手架,命令很简洁:
npx create-expo-app@latest默认会在当前目录下创建一个叫 app 的新目录,名称可以这样指定:
npx create-expo-app my-expo-app如果想直接用 TypeScript 模板,可以用:
npx create-expo-app my-expo-app --template blank-typescript用 npx 的好处在于每次都拉取最新版本,避免本机缓存了旧版导致创建出来的结构过时。我第一次用的时候没加 @latest,结果拿到的是很老的模板,里面连 expo-router 都没有,排查了挺久才明白是缓存问题。
Expo 官方模板其实分好几种,我列几个常用的:
- blank:最干净的 JavaScript 模板,适合想自己搭结构的老手
- blank-typescript:同上,但内置 TypeScript 配置,现在新项目我基本都用这个
- tabs:自带底部导航和 expo-router 文件路由的模板,适合起步就要多页面的项目
- bare-minimum:这个要留意,它会生成一个裸 RN 工程,而不是纯 Expo 托管工作流
新手我建议直接用 blank-typescript,别选 tabs。底下的导航结构虽然方便,但如果你对 expo-router 的路由机制还不熟悉,改起来反而绕。blank-typescript 给你的是一块干净的地基,后续加什么都很顺手。
提示:创建项目时如果网络不稳定,容易卡在 “Downloading and extracting project files.” 这一步。建议在终端里配置好 npm 的淘宝镜像再跑,速度会快很多,命令是
npm config set registry https://registry.npmmirror.com。
2.3 启动项目与 Expo Go 联调
项目创建完成后,进入目录启动开发服务:
cd my-expo-app npx expo start运行之后终端会显示一个二维码,同时提供几个快捷键,a 是打开 Android 模拟器,i 是打开 iOS 模拟器,w 是在浏览器里打开。如果没有配置模拟器也没关系,直接用手机上的 Expo Go 扫码就能跑。
这里插一句 Expo Go 的问题。很多新手下载 Expo Go 时会困惑,为什么应用商店里搜出来一堆第三方工具?因为 Expo Go 是 Expo 官方发布的预览客户端,iOS 在 App Store 里搜 “Expo Go”,Android 在应用市场或官网下载 APK。它本身不是一个完整开发环境,而是用来连接你电脑上 Metro Bundler 的“预览窗口”。
用 Expo Go 联调时,手机和电脑必须在同一个局域网内。有两个细节值得注意:
第一,如果手机扫码后一直显示 “Connection refused” 或者长时间卡在加载界面,大概率是电脑防火墙拦了 8081 端口。Windows 用户记得在防火墙设置里放行 Node.js,macOS 用户检查一下网络是否开启了“允许入站连接”的拦截。
第二,如果你在公司网络或某些公共 WiFi 下,局域网隔离会把设备之间的通信掐掉。遇到这种情况,可以试试用 Expo 的 tunnel 模式启动:
npx expo start --tunnel这个命令会通过 Expo 的云端服务建立一条隧道,手机和电脑不在同一网络也能连。不过隧道模式依赖网络质量,稍有延迟,但应急时很管用。
3. 实操过程与核心环节实现
3.1 目录结构的逐层解读
项目创建好之后,结构比裸 RN 要简洁很多,这是 Expo 托管工作流的一大优点。我先把核心文件逐一拆开讲,不然新手很容易对着目录发懵。
my-expo-app/ ├── .expo/ ├── assets/ ├── node_modules/ ├── app/ │ └── index.tsx ├── app.json ├── package.json ├── tsconfig.json └── ...app.json是 Expo 项目最重要的配置文件,它替代了裸 RN 里很多原生配置。应用名称、图标、启动屏、权限声明等,都在这里管理。我实际改过的一个示例如下:
{ "expo": { "name": "My Expo App", "slug": "my-expo-app", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "ios": { "supportsTablet": true, "bundleIdentifier": "com.example.myapp" }, "android": { "package": "com.example.myapp", "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" } }, "web": { "favicon": "./assets/favicon.png" } } }新版本的 Expo 默认使用目录即路由,也就是说app/目录下的文件结构就是应用的页面路由结构。app/index.tsx对应首页,你再加一个app/about.tsx,就自动有了/about这个路由页面。这是 expo-router 的规则,理解这一点,后面做多页面会在心智上轻松很多。
assets/目录放静态资源,图标、启动屏、字体文件都放这里。注意这里的图标引用路径写在 app.json 里,所以资源的文件名不能随意改,改了就要同步更新配置。
3.2 配色、基础组件与导航的快速实现
项目创建好之后,第一件事就是把默认页面改掉。这里我建议先把基础设计规范定下来,即颜色、间距、字体大小,我一般直接在入口文件里建立一个主题对象。
打开app/index.tsx,可以看到默认模板的核心代码逻辑。我自己习惯先改成这样一套布局:
import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View } from 'react-native'; export default function App() { return ( <View style={styles.container}> <Text style={styles.title}>欢迎来到 Expo 开发</Text> <Text style={styles.subtitle}>这是一个由 Expo 创建的项目</Text> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', alignItems: 'center', justifyContent: 'center', }, title: { fontSize: 24, fontWeight: '600', color: '#333', }, subtitle: { fontSize: 16, color: '#666', marginTop: 8, }, });保存文件,Metro 会自动热更新,模拟器里的界面立刻变化。这套开发体验就是 Expo 最大的价值所在,改代码像写网页一样即时反馈,不用重新编译原生工程。
需要说明的是,StyleSheet.create 在 Expo 里和裸 RN 完全一致,都是 React Native 的样式系统。flex 布局是默认布局方式,flex: 1表示占满父容器剩余空间,alignItems 和 justifyContent 控制水平垂直居中。如果对 flex 不熟,建议先去补一下基本概念,因为后面所有页面布局都离不开它。
3.3 网络请求与数据展示的完整案例
开发一个实际应用,不可能只停留在静态页面。我找一个最常见的业务场景——从远程接口拿数据并渲染列表,来演示 Expo 项目下的完整数据流。
先安装一个轻量的 HTTP 客户端,Expo 支持 axios:
npm install axios然后封装一个简单请求:
// services/api.ts import axios from 'axios'; const api = axios.create({ baseURL: 'https://api.example.com', timeout: 10000, }); export async function fetchPosts() { const response = await api.get('/posts'); return response.data; }在页面组件中使用 useEffect 拉取数据,同时用 useState 管理加载状态和错误状态:
import { useEffect, useState } from 'react'; import { FlatList, Text, View, ActivityIndicator } from 'react-native'; interface Post { id: number; title: string; body: string; } export default function HomeScreen() { const [posts, setPosts] = useState<Post[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { fetchPosts() .then((data) => setPosts(data)) .catch((err) => setError(err.message)) .finally(() => setLoading(false)); }, []); if (loading) { return <ActivityIndicator size="large" color="#333" />; } if (error) { return <Text>加载失败:{error}</Text>; } return ( <FlatList data={posts} keyExtractor={(item) => item.id.toString()} renderItem={({ item }) => ( <View style={{ padding: 16, borderBottomWidth: 1 }}> <Text style={{ fontSize: 18, fontWeight: '600' }}>{item.title}</Text> <Text style={{ fontSize: 14, color: '#666', marginTop: 4 }}>{item.body}</Text> </View> )} /> ); }这就是一个标准的 RN 应用页面骨架。FlatList 是长列表渲染的核心组件,它的存在解决了上万条数据渲染卡顿的问题,内部做了窗口化,只渲染可见区域附近的项。这是 RN 开发里一个非常重要的概念,写列表页时别用 ScrollView 加 map,性能差距会非常明显。
3.4 配置自定义字体、图标和基础 UI 库
当项目从“跑起来”进入“像样”的阶段,就该考虑 UI 了。这里正好回应一下热搜词里的 “expo ui库”。很多新手以为 Expo 内置了一堆现成的 UI 组件,其实它没有。Expo 提供的更多是原生能力模块,比如相机、定位、文件系统,而不是像 Bootstrap 那样的现成组件库。
所以你还需要在 npm 生态里选一个 UI 组件库。我按自己用过的经验做一个对比:
| 组件库 | 风格 | 适合场景 | 备注 |
|---|---|---|---|
| React Native Paper | Material Design | 快速做后台管理类、表单类 | 上手快,社区活跃 |
| NativeBase | 通用型 | 中小型业务项目 | 3.x 版本改版后配置略重 |
| Tamagui | 高性能跨平台 | 想要统一样式系统的团队 | 适合重定制场景,学习成本高 |
| UI Kitten | Eva Design System | 追求视觉一致性的应用 | 内置主题系统,适合中后台风格 |
我自己在 Expo 项目中最常用的是 React Native Paper,因为它安装简单、对 Expo 支持好,而且 Material 风格的组件在 iOS 和 Android 上都有不错的表现。一般的业务系统,包括登录、列表、表单、数据统计页面,这个库都能覆盖。
安装命令如下:
npm install react-native-paper然后在入口文件用 Provider 包裹:
import { PaperProvider } from 'react-native-paper'; export default function RootLayout() { return ( <PaperProvider> <HomeScreen /> </PaperProvider> ); }之后就能在页面里直接使用 Paper 的组件,例如Button、TextInput、Card,它们已经内置了规范化的颜色和间距设计,省去大量重复写样式的时间。
除了 UI 库,图标也值得说一句。Expo 官方自带@expo/vector-icons,里面封装了超过 20 个图标集,包括 Ionicons、MaterialIcons、FontAwesome 等,几万个图标随心用,不需要手动配置。使用方式也很简单:
import { Ionicons } from '@expo/vector-icons'; <Ionicons name="home" size={24} color="black" />这个组件库是 Expo 项目默认集成的,你几乎不需要额外配置,对图标名不确定的,可以查看官网的图标列表页面,或者直接在代码里试。
4. 常见问题与排查技巧实录
4.1 视频读取错误 0xc00d36b4 的真实排查过程
最近热搜词里有一条看起来很奇怪,它是一条 Windows 系统报错:windowsvideomedia error 0xc00d36b4 while reading d:\dx\dxkxq_dataoutput\expo。很多人把它和 Expo 开发环境联想到一起,我一开始也被误导了,实际查了一圈才发现,这个错误码 0xc00d36b4 是 Windows Media Foundation 的视频读取错误,常见原因是文件损坏或视频编码格式不被当前播放器支持。
具体到这条报错,报错里出现 expo 是因为某类自动化脚本把视频文件输出到了与 expo 相关的目录。那是业务流程的问题,不是 Expo 框架的问题。但这件事也给了我们一个提示:在 Windows 上做 Expo 开发,副作用可能来自各种你想不到的地方。
我的排查思路是分三步的:
第一步,确认报错来源。凡是看到 0xc00d36b4 这种 Windows 多媒体错误码,先判断是否与自家项目代码有关系。建议直接在项目目录下搜索报错中的文件路径,如果 d:\dx... 这个目录不是你的 Expo 项目目录,那基本可以断定是其他软件在读取视频文件,而不是 Expo 本身出错。
第二步,检查视频文件本身。用系统自带的“电影和电视”打开确认是否能正常播放。如果打不开,用格式工厂或 FFmpeg 重新转码为 H.264 + AAC 的标准 MP4 格式,这个组合兼容性最好。Expo 官方文档对视频模块要求的编码格式,也推荐使用 H.264 视频编码和 AAC 音频编码。
第三步,清理系统环境。如果你确认报错发生在开发过程中且文件路径指向项目目录,可以尝试删除 node_modules 和 .expo 目录后重新安装:
rm -rf node_modules .expo npm install npx expo start -c-c参数会清空 Metro 缓存,很多莫名其妙的资源加载问题就能解决。我之前遇到过几次启动项目后白屏,就是 Metro 缓存文件损坏导致的,清缓存永远是第一优先级的排障动作。
4.2 Expo Go 无法连接与扫码失效问题
Expo Go 连接不上开发服务器算是出现频次最高的一个问题,我把它单拎出来讲,因为排障思路很有代表性。
如果你点击二维码没反应、扫码又说找不到服务器,按从简到繁的顺序排查:
先确认手机和电脑在同一个 WiFi 网络。这一步看着简单,实际中很常见,手机自动连了 5G 频段,电脑连的是 2.4G 频段,或者两台设备在不同 VLAN 下,都会导致相互发现不了。
然后确认终端里的地址。运行npx expo start后,终端会显示类似exp://192.168.1.5:8081的地址,你的手机必须能够访问到这个 IP,把地址复制到手机浏览器里测试一下,如果能访问说明网络通,不能访问就查防火墙。
Windows 下最常见的是防火墙拦截 Node.js。解决办法是控制面板进入“允许应用通过防火墙”,把 Node.js 的专用和公用都勾上,或者干脆在首次出现防火墙提示时点“允许访问”。
如果以上都没问题,就用隧道模式兜底:
npx expo start --tunnel它会通过远程服务器建立连接,绕开局域网限制,代价是传输速度略慢,但在咖啡厅、酒店这种网络环境复杂的场景下,隧道模式就是我最后的保命手段。
4.3 其它高频问题备忘
按我自己的经验,把常见问题做成一个速查表,遇到直接对号入座:
| 现象 | 可能的根因 | 解决方案 |
|---|---|---|
npx create-expo-app卡住不动 | 网络问题或 npx 缓存了旧版本 | 设置镜像源后重试,或用npx create-expo-app@latest |
| 启动后白屏,控制台无报错 | Metro 缓存损坏 | npx expo start -c |
| 修改代码后页面不刷新 | Watchman 未安装或文件监听失效 | macOS 安装 Watchman,Windows 改用 Git Bash |
npm install时权限报错 | 全局依赖权限问题 | 用 npx 替代全局安装,避免 sudo |
| 应用启动时找不到 expo 模块 | node_modules 损坏 | 删除 node_modules 后重新 npm install |
| 图片资源 setTimeout 后不显示 | 地址引用错误或缓存 | 清理 .expo 目录并重启 |
| TypeScript 报找不到模块 | 路径别名未配置 | 在 tsconfig.json 里配置 paths |
4.4 几个值得记住的调试技巧
有些技巧是官方文档里不起眼的角落,但对开发效率提升非常显著。
第一个是 Expo 开发菜单。在 Expo Go 里摇一摇手机,或者按m键(模拟器),可以打开开发者菜单,里面有强制刷新、打开 React DevTools、查看组件布局等入口。这个菜单是日常调试的核心入口,很多新手不知道,遇到问题只能干瞪眼。
第二个是 React DevTools。它可以在开发者菜单里直接启动,弹出的独立窗口能查看组件树、props、state 的变化。这比单纯看日志定位问题要快得多,尤其是处理列表组件的重复渲染问题时。
第三个是日志输出。Expo 支持在终端里直接看到应用内的 console.log 输出,还可以按层级过滤。开发时在该加日志的地方不要吝啬,线上问题难查大多是因为当时没留足排查线索,本地联调时多打几行日志不会错。
注意:如果你在 Windows 上使用 Expo,需要特别注意项目路径中不要包含中文、空格或特殊字符。比如
D:\开发项目\my-expo-app这种路径会在 Metro 打包时出现各种奇怪报错。统一用全英文、无空格的路径,可以省去一多半的路径相关问题。
5. 从创建到构建发布的完整工作流
5.1 什么时候该用 EAS Build
用 Expo 开发到一定程度,你需要把应用打包成正式的安装包。这里必须讲清楚一个概念:Expo Go 只是一个预览工具,它不能用于最终发布。想要生成独立的 APK 或 IPA,需要走构建流程。
当前 Expo 推荐的构建服务是 EAS Build,全称 Expo Application Services。它是云端的构建服务,你本地提交代码配置,它在远程服务器上完成原生编译,最后返回安装包。好处是不用自己配原生环境,坏处是免费额度有限,而且云端构建需要排队等待。
安装 EAS CLI 并登录:
npm install -g eas-cli eas login在项目目录下初始化配置:
eas build:configure这个命令会生成eas.json配置文件,里面定义了 development、preview、production 三种构建档位。你需要根据平台执行构建命令:
eas build -p android --profile preview eas build -p ios --profile previewiOS 需要 Apple Developer 账号和对应的证书,Android 则相对简单,生成的 AAB 文件可以直接上传到应用商店。这个流程第一次跑会有点懵,但跑通一次后,后续和 CI/CD 接起来就顺了。
5.2 和现有团队项目的迁移方案
最后聊一个很多人实际会遇到的场景:团队里已经有一套老的 RN 项目,代码写得乱糟糟,想借 Expo 重新梳理一遍。这时候不建议直接迁移所有组件,风险太大。我自己的做法是“渐进式迁移”:
第一步,先新建一个 Expo 空白项目,把全局配置、路由结构、设计规范搭好。第二步,把老项目里最核心的页面迁移过来,优先处理业务逻辑,暂不管样式细节。第三步,写一个简单的兼容层,将老项目的自定义组件转换成 Expo 生态下对应的 UI 组件。第四步,确认核心页面表现稳定后,再逐个迁移剩余页面。
这个过程不需要一步到位,exp + 老项目共存的过渡期也可以用 Expo 的web支持来快速验证页面效果。等到后续迭代稳定了,再考虑彻底弃用老工程。
说实话,我很少见到有团队能一次把老 RN 项目完整迁移到 Expo,大多数情况都是拿 Expo 起新项目、新模块,或者用在新业务的快速验证上。如果你是维护老项目的,先别急着推倒重来,把 Expo 引入到新功能模块上,跑通之后大家自然就有信心了。
6. 最后的几点实操体会
写到这里,整条“Expo 创建 React Native 项目”的链路已经完整走了一遍。结合我自己的实践,最后再分享几个真实体会。
模板一定要用 TypeScript。我第一次用 Expo 时选的默认 JavaScript 模板,后来项目规模上去了,类型问题开始成为隐性负担,在 IDE 里面跳转定义、重构变量都需要额外的心智成本。换成 TypeScript 模板之后,代码的可维护性提升不是一点点,而且 Expo 对 TypeScript 的支持已经很成熟,不需要你额外做太多配置。
UI 库和设计系统越早定越好。项目前期为了省时间,东用一个组件、西用一个原生组件,等界面多了,风格会越看越不统一。后面再想统一,就要付出额外成倍的修改成本。建议项目第一天就选定 UI 库,定好主色、辅色、间距规范,后续所有页面都遵守这套规则。
调试能力的优先级被很多人低估。很多新手拿到项目就开始写业务,遇到问题只会用 console.log 硬调。实际上 Expo 的调试体系已经很完善,开发菜单里那些工具用熟了,定位问题的速度会快很多。花半天时间把这些工具摸透,后期至少能省下几十个小时的埋头痛查时间。
最后,保持项目目录的干净。这个建议听着琐碎,但确实是我踩过坑之后的教训。之前有个项目因为路径里带了中文目录名,Metro 打包时反复报错,排查了将近两个小时才找到原因。之后所有项目我都统一用英文小写加短横线命名,这比任何技巧都可靠。
如果你正打算用 Expo 创建自己的 React Native 项目,照着这篇文章的流程走一遍,应该能很顺利地把第一个应用跑起来。后面的路——新页面、新组件、新业务模块——就是在扎实的基础上不断叠加的过程了。