先交代一下背景:我最近在学 React Native,目标很简单——不搞那种“先看两周文档再动手”的思路,而是第一天就开项目、边查边写,争取尽早跑通一个真实 APP 的基础链路。结果第一关就撞上了启动页(Splash Screen)的问题:APP 能启动,但要么白屏一会,要么干脆没有启动图,模拟器和真机表现还不一样。
这篇文章就把“React Native 学习第一天”的完整过程记录下来,重点拆解启动图片(Splash Screen)的配置逻辑、踩坑点和我实际验证过的解决方案。不管你是刚接触 RN 的新手,还是被启动白屏折腾过的老同志,这篇内容应该都能给你省下一点排查时间。
1. 第一天的学习思路与目标拆解
1.1 为什么选择“边学边写”而不是先啃完文档再动手
我知道很多人接触 React Native 的时候,第一反应是去买一本大部头,或者把官方文档从头到尾读一遍。我个人的经验是:文档该看,但不要放在第一步,尤其当你的目标是“做出东西”的时候。
我给自己定的第一天目标非常窄:能创建一个 RN 项目,能在 Android 和 iOS 模拟器上跑起来,能在启动 APP 的时候看到一张自定义的启动图片。三个目标按优先级排序,第三个最难,也是我今天要重点讲的。
这个思路的核心在于“反馈驱动学习”。你每写一段代码、每改一个配置,就能立刻在模拟器上看到结果,这种即时反馈带来的记忆效果,远好于单纯阅读。遇到不懂的术语(比如 MainActivity、LaunchScreen、windowBackground),就边查边记,而不是停下来系统学习。第一天的正确姿势不是“学会”,而是“跑通”。
1.2 环境准备:这些工具一次配到位
我是在 macOS 上操作的,不过下面这套流程对 Windows 同样适用,只是 SDK 路径和一些环境变量的写法略有差异。如果你还没装环境,按这个清单来:
- Node.js 16 以上版本,建议用 nvm 管理,避免以后不同项目版本冲突
- JDK 17(React Native 0.73 之后官方推荐,装 17 别装 11)
- Android Studio,用来跑 Android 模拟器,同时管理 SDK
- Xcode(仅 macOS 需要),跑 iOS 模拟器用的
- 一个趁手的编辑器,我用的是 VS Code,装一个 React Native 扩展插件就行
检查环境的命令是npx react-native doctor,这命令会告诉你哪个环节缺什么,比手动一个个查省事得多。我第一次跑的时候,它提示 Android SDK 的 platform-tools 没装,按提示补上就过了。
1.3 创建第一个项目:命令就三条
环境就绪之后,创建项目的命令非常简单:
npx react-native init SplashDemo cd SplashDemo npx react-native run-android这里有两个坑需要提醒你:
第一个是初始化时不要用react-native init后面直接跟项目名就完事,默认模板其实分成两个分支——一个是旧的社区 CLI 风格,一个是新版的@react-native-community/cli初始化方式。npx react-native init现在默认创建的是新版模板,底层用的就是@react-native-community/cli,这是个正常现象,别看到命令行输出一堆@react-native-community开头的包名就慌。
第二个是run-android第一次会下载 Gradle 依赖,特别慢,这一步不是卡死,是 Gradle 在拉包。国内网络如果特别慢,建议在android/build.gradle里把仓库优先指向阿里云镜像,能省下不少时间。改完记得 sync 一下 Gradle。
2. 启动图片(Splash Screen)的原理拆解
2.1 为什么 APP 需要启动图片,它到底在解决什么问题
启动图片,业界一般叫 Splash Screen 或者 Launch Screen。很多人以为这只是“加个品牌露出图”,好看而已,其实它的核心作用远不止于此。
React Native 的 JavaScript 引擎启动是有延迟的。APP 从用户点击图标到 JS 代码真正执行完首帧渲染,中间要经历:系统拉起进程、初始化原生运行时、加载 bundle 包、执行 JS 逻辑、注册组件、渲染首屏。这个过程哪怕很快,在低端 Android 设备上也可能需要 2-3 秒。
如果这段时间什么都不显示,用户看到的就是一个白屏或黑屏。这个体验非常糟糕,用户会本能的认为 APP 崩了。启动图片的目的,就是在这段“不可见”的时间内,给用户一个视觉反馈,告诉他们“程序正在启动,请稍候”,在体验层面掩盖掉技术上的加载等待。
一句话总结:启动图片是优化启动体验的缓冲层,不是装饰。
2.2 实现启动图片的三条技术路线对比
在 React Native 项目里实现启动图片,常规路线有三条,我第一天就把它们都试了一遍,简单对比如下:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 原生方案(手动改原生工程) | 在 Android 的styles.xml和 iOS 的LaunchScreen.storyboard中配置图片 | 不依赖额外库,性能最好 | 双端分别配置,代码量大,维护成本高 |
| react-native-splash-screen 库 | 在原生层显示一个原生 View,并在 JS 层控制隐藏时机 | 最常用,社区成熟,文档全 | 需要手动关联原生代码,遇到 RN 版本升级可能兼容性出问题 |
| react-native-bootsplash 库 | 自动生成各尺寸图片,配置相对简单 | 支持动态生成资源,适配 Android 12 新特性 | 模板生成逻辑偏定制,新手理解起来有门槛 |
我最终选择的是react-native-splash-screen,原因很简单:它是我搜到最多人用的方案,遇到问题能搜到大量解决方案,对第一天的我来说,可排查性比先进性重要得多。
不过要提醒你一点:这条路线在 React Native 0.76 之后的一个版本里,出现过过一次原生模块自动链接失效的 issue。如果你用的是 0.77 及以上版本,建议优先看react-native-bootsplash或者原生配置方案。
2.3 启动图片的加载顺序:原生先启动,JS 后接管
理解启动图片的实现机制,最关键的是搞清楚先后顺序:
- 用户点击 APP 图标,系统启动原生进程
- 原生层读取启动配置(Android 读取主题中的
windowBackground,iOS 读取 LaunchScreen),立即显示启动图片 - 原生层初始化 React Native 环境,加载 JS Bundle
- JS 引擎执行完首屏渲染逻辑,调用
SplashScreen.hide()方法 - 启动图片隐藏,展示 APP 真实首屏
这个顺序决定了配置的核心思路:你必须在原生层设置启动图,单纯在 JS 层写<Image>是没用的,因为 JS 层渲染出来之前,用户已经看到画面了。
3. 实操记录:从零到一完成启动页配置
3.1 准备启动图资源:尺寸和格式这么选才不踩坑
我先去网上找了一张 1024x1024 的 PNG 图片作为演示资源。但在实际操作中,你最好根据启动图的分辨率和显示区域,准备多张不同尺寸的图,否则会踩到拉伸变形或边缘被裁切的坑。
Android 端启动图建议至少准备以下密度对应的dpi资源:
| 密度 | 分辨率参考 |
|---|---|
mdpi | 320x480 |
hdpi | 480x800 |
xhdpi | 720x1280 |
xxhdpi | 1080x1920 |
xxxhdpi | 1440x2560 |
如果你不想做这么多张,最低限度也要放一张xxhdpi的,然后在 XML 里设置scaleType="center",这样可以避免小图被拉伸变模糊。
格式方面强烈建议用 PNG 而非 JPEG。JPEG 虽然有损压缩,体积更小,但在带透明背景的场景下完全不能用。另外 iOS 的 LaunchScreen 对 PNG 的 alpha 通道支持不太稳定,如果你在真机上看到图片边缘有黑边或白边,大概率是 alpha 透明通道的问题,建议把背景做成不透明的。
3.2 Android 端配置:四个文件改了就能看到效果
第一步:安装依赖
npm install react-native-splash-screen --save第二步:修改 MainActivity
打开android/app/src/main/java/com/splashdemo/MainActivity.java,改成这样:
import android.os.Bundle; import org.devio.rn.splashscreen.SplashScreen; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { @Override protected void onCreate(Bundle savedInstanceState) { SplashScreen.show(this); super.onCreate(savedInstanceState); } @Override protected String getMainComponentName() { return "SplashDemo"; } }注意第二行 import 别写错,这个库包名是org.devio.rn.splashscreen,很多人会顺手写成com.reactnative.splashscreen,然后编译直接报错找不到符号。
第三步:新建启动图布局文件
在android/app/src/main/res/layout/目录下新建launch_screen.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/launch_screen_bg" android:orientation="vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:contentDescription="Splash" android:src="@drawable/launch_screen_image" /> </LinearLayout>第四步:修改 styles.xml
在android/app/src/main/res/values/styles.xml里添加下面这段:
<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowBackground">@drawable/launch_screen</item> </style>注意,这里的windowBackground引用的是一个drawable资源,这也是很多教程没讲清楚的地方。如果你直接引用一张图片,放大到不同屏幕比例时会有问题,正确做法是先准备一个launch_screen.xml放在drawable目录下,作为背景的根布局。
最后在AndroidManifest.xml里把启动 Activity 的主题切换为SplashTheme:
<activity android:name=".MainActivity" android:theme="@style/SplashTheme" android:exported="true"> </activity>做完这四个文件的修改,Android 端就配置好了。注意整个过程里你只是“显示”了启动图,真正“隐藏”的逻辑在 JS 层,我们下一节讲。
3.3 iOS 端配置:LaunchScreen.storyboard 是核心入口
iOS 端的配置相对简单一些,但也更容易被配置细节卡住。
用 Xcode 打开项目的ios目录,找到LaunchScreen.storyboard,这是 iOS 的启动图定义文件。默认模板里只有一个空白 View,你要做的就是拖一个UIImageView上去,然后设置图片。
不过我更推荐直接在Images.xcassets里添加LaunchImage资源,因为 storyboard 方式在部分机型上会存在缓存问题,真机调试时非常烦人——你改了图,但系统还显示旧的,要重启设备才生效。
用 xcassets 方式的操作路径是:
- 打开
Images.xcassets,在空白处右键选择 “New App Icon / Launch Image” - 把不同尺寸的启动图拖入对应的
2x、3x占位框 - 在
Build Settings中确认Launch Screen File为空 - 在
Assets.xcassets的AppIcon设置里选好LaunchScreen资源
iOS 的启动图尺寸很繁琐,一个典型的 iPhone 15 Pro Max 需要 1290x2796 的3x尺寸图。如果你只是学习测试,直接放一张 828x1792 的2x图也能在绝大多数模拟器上跑通。
3.4 JS 层控制:显示之后要在正确时机隐藏
配置完成之后,在 JS 层做两件事。
第一件:在根组件挂载时导入并调用
在index.js或App.js的组件生命周期里:
import SplashScreen from 'react-native-splash-screen'; // 在根组件的 componentDidMount 里 useEffect(() => { SplashScreen.hide(); }, []);第二件:确保时机正确
这里有个非常重要的经验:hide()的调用时机,直接决定用户看到的体验。
如果你在useEffect里立刻调用hide(),启动图片可能就一闪而过,因为首屏内容还没渲染完,隐藏后依然是白屏。正确做法是等首屏数据准备好,或者至少在第一个页面完成基本布局之后再调用。
我当时验证了一个很实用的模式:
const [ready, setReady] = useState(false); useEffect(() => { // 模拟异步加载数据的过程 setTimeout(() => { setReady(true); SplashScreen.hide(); }, 2000); }, []);这样启动图至少展示 2 秒,覆盖掉 JS 加载和首屏渲染时间。生产环境里,你应该根据真实的数据加载耗时来决定何时隐藏,而不是写死一个时间。
4. 踩坑实录:启动白屏、图片适配与双端差异
4.1 启动白屏:最典型的问题,原因只有一个
我在模拟器上第一次跑通项目后,发现 APP 启动时出现 1 到 2 秒的纯白屏,然后才看到启动图片加载出来。这个现象在热词里叫 “react native 启动白屏”,非常常见。
原因其实很简单:启动图片的显示依赖于原生资源的加载,但原生工程初始化时,主题背景会先显示出来。如果你在styles.xml里设置了windowBackground,那个阶段的背景就是它,但如果你没设置,系统默认就是白色。
排查方式很直接:把windowBackground改成一个深色,比如#000000。如果启动瞬间闪过的是黑色,说明你的启动图还没覆盖到最早期阶段;如果依然是白色,那就是你的windowBackground配置根本没有生效,检查styles.xml中 parent 主题是否正确。
我当时的问题出在Theme.AppCompat.Light.NoActionBar这一行上。React Native 默认模板用的是Theme.AppCompat.Light.NoActionBar的变体,我这里改成SplashTheme之后,Activity 的标题栏被隐藏了,但背景色没变,加了一行<item name="android:windowDisablePreview">true</item>反而更糟。最后直接删掉这个 item,才恢复正常。
4.2 Android 12 的 SplashScreen API:系统级行为与旧方案冲突
如果你用的是 Android 12 及以上设备,你会发现启动图的表现变得非常怪异:有时候你能看到自定义的启动图,有时候系统先显示一个默认的应用图标和背景色,然后才跳到你的启动图。
这是因为 Android 12 原生引入了一套新的 SplashScreen API,它控制了系统级的启动动画。当系统级 SplashScreen 存在时,你的应用内启动图(windowBackground / react-native-splash-screen)会在其后执行,叠加出“两层启动”的效果。
解决方案是适配 Android 12 的 SplashScreen API。在values-v31目录下新建styles.xml:
<style name="SplashTheme" parent="android:style/Theme.SplashScreen"> <item name="windowSplashScreenBackground">@color/launch_bg_color</item> <item name="windowSplashScreenAnimatedIcon">@drawable/launch_screen_image</item> <item name="postSplashScreenTheme">@style/AppTheme</item> </style>用这套配置,Android 12+ 设备会走新的系统级 SplashScreen,老版本设备走旧逻辑,双端兼容。我是折腾到这一步才明白,为什么有的教程说“Android 12 上启动图失效了”——不是失效,是系统升级了玩法,你的适配方案也得跟着升级。
4.3 iOS 端启动图为什么改了不生效?缓存在作祟
iOS 是一个让人又爱又恨的平台。爱的是开箱即用的体验,恨的是启动图缓存机制特别顽固。
我在改了一张图之后,发现模拟器上显示的还是旧图。试过清理构建缓存,试过删除 App 重装,都没用。最后搜了一下,说是模拟器端要把 “Content and Privacy” 之类的设置重置,更直接的方案是xcrun simctl clear_cache命令重置模拟器缓存。
实战命令如下:
xcrun simctl shutdown all xcrun simctl erase all这会把整个模拟器恢复到出厂状态,速度很快但会把所有安装的 APP 都清掉。在你尝试任何启动图修改之前,建议先执行一次,避免“改了半天以为是代码问题,结果是缓存”的尴尬。
另一个 iOS 端的坑是:如果你用的是LaunchScreen.storyboard,系统实际上是在首次启动时生成一张快照图。快照一旦生成就不会更新,除非你改变 storyboard 的结构(比如新加一个 View 或改约束)。所以我要么改 storyboard 里控件的尺寸或位置,要么就直接切到 xcassets 方式。
4.4 图片加载不出来或变形:先查资源目录再查 scaleType
启动图最常见的视觉问题有两种:图片不显示,或者显示出来变形拉伸。
图片不显示时,先别急着查代码。Android 的资源匹配机制非常严格,文件放在res/drawable-xhdpi和放在res/drawable有区别,文件名里不能含大写字母——这点尤其坑,SplashScreen.png和splashscreen.png在资源查找时完全不是一回事。尽量用小写字母命名。
图片变形时,检查布局文件的scaleType。用fitXY一定会拉伸,用center可以保持比例但在长屏机型上两侧可能留白。折中方案是fitCenter配合一个和启动图同色的背景,这样留白区域看起来就像是设计的一部分。
5. 边学边写的节奏控制与后续学习建议
5.1 第一天结束时,我理解了哪些核心概念
现在回头看,第一天最大的收获不是“配置出了启动图”,而是把 React Native 双端启动流程跑通了一遍。我梳理了三个第二天还要用到的概念:
第一,原生层和 JS 层是两套独立的环境。很多新手写 RN 代码时会觉得“反正 Java 代码不用管”,但启动图这类功能恰恰必须穿透两层才能完整实现。RN 不是写一份代码跑双端那么简单,双端差异会出现在任何涉及原生的环节。
第二,配置类的改动通常需要重新编译原生工程,不是刷新一下就生效。我第 N 次改完styles.xml之后只重载了 JS,结果启动图纹丝不动,白白浪费时间。记住:改了android/或ios/目录下的文件,必须重新run-android或run-ios,甚至要先clean一次。
第三,模拟器场景和真机场景有差异。Android 12 的新 SplashScreen API 在模拟器上反应慢半拍,iOS 的缓存问题在真机上更顽固。如果只在模拟器上调通就以为万事大吉,拿到真机上大概率还是会被打脸。
5.2 第一天的代码管理习惯:别忽略 Git 提交
学习阶段的代码也别乱放,从第一天就提交 Git 是个好习惯。我当时的划分很简单:
git init git add . git commit -m "feat: 初始化项目并配置双端启动图"第二天开始写业务逻辑后,每完成一个小功能就提交一次,这样万一改出问题,你能精准的回滚到某个版本,而不是靠 Ctrl+Z 倒退。这个习惯在整条 RN 学习路上都会救你很多次。
我还建议第一次配置成功之后,立刻复制一份代码备份,因为后续配置别的组件时,很可能不小心改坏现有的原生配置。备份路径放在项目外,名字写成SplashDemo_working_backup_日期这种。
5.3 后续两天学什么:从启动页走向真实页面
第一天的目标已经达成,第二天我计划做两个事:一是搭一个底部导航框架,让 APP 真正有“多页面”的样子;二是封装一个简单的网络请求模块,把 JS 层和后端联通的链路跑通。第三天再回头把启动图片的交互细节优化一遍,比如根据启动状态显示动态 loading 效果。
这条路线是我个人觉得比教程合理的地方:按照“先能跑,再变复杂”的顺序推进,每一步都处在上一大步搭好的、可运行可验证的代码基础上。你不需要一步到位,先跑通再把功能加厚。
6. 写在最后:第一天的一个小经验
最后分享一个我自己的体会:React Native 学习的第一天,不要贪多,也不要在配置环境上死磕超过两小时。如果某个依赖加载超过 30 分钟还没进展,切镜像也好、求助社区也好,尽快过去,别让自己卡在“看不见成果”的环节。启动图片这件事,本质上是让你快速建立“我改代码,APP 有反应”的信心循环。这个循环一旦建立起来,后面学什么都会顺利很多。
一个小技巧送给你:如果你今天也打算边学边写,建议你从npx react-native init这种工程模板开始,不要用 Expo 的托管工作流。虽然 Expo 几乎零配置,很多学习教程也用 Expo 演示,但它的启动图和原生配置都是高度封装的,你学不到原生层的东西。做学习项目,宁可麻烦一点,也要亲手碰一遍原生配置。
我今天的启动图配置完,看到模拟器上那张自定义图片稳稳当当地展示了两秒,然后平滑过渡到主界面,说实话挺有成就感的。下一步就是接着写业务页面了,希望这篇文章能帮你少踩几个我踩过的坑。