# Expo Documentation
Expo is an open-source React Native framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app such as live updates, instantly sharing your app, and web support. The company behind Expo also offers Expo Application Services (EAS), which are deeply integrated cloud services for Expo and React Native apps.
# 开始使用
## 介绍
开始使用 Expo 创建应用。
Expo 是一个使开发 Android 和 iOS 应用更容易的框架。我们的框架提供基于文件的路由、标准的原生模块库等等。Expo 是开源的,在 [GitHub](https://github.com/expo/expo) 和 [Discord](https://chat.expo.dev) 上有一个活跃的社区。
¥Expo is a framework that makes developing Android and iOS apps easier. Our framework provides file-based routing, a standard library of native modules, and much more. Expo is open source with an active community on [GitHub](https://github.com/expo/expo) and [Discord](https://chat.expo.dev).
我们还制作了 [Expo 应用服务(EAS)](https://expo.dev/eas),这是一组在开发过程的每个步骤中补充 Expo 框架的服务。
¥We also make [Expo Application Services (EAS)](https://expo.dev/eas), a set of services that complement the Expo framework in each step of the development process.
要开始使用,请访问:
¥To get started visit:
## 创建项目
了解如何创建新的 Expo 项目。
系统要求:
¥System requirements:
* [Node.js (LTS)](https://nodejs.cn/)。
* 支持 macOS、Windows(Powershell 和 [WSL 2](https://expo.fyi/wsl))和 Linux。
¥macOS, Windows (Powershell and [WSL 2](https://expo.fyi/wsl)), and Linux are supported.
我们建议从 `create-expo-app` 创建的默认项目开始。默认项目包含示例代码以帮助你入门。
¥We recommend starting with the default project created by `create-expo-app`. The default project includes example code to help you get started.
要创建新项目,请运行以下命令:
¥To create a new project, run the following command:
```sh
$ npx create-expo-app@latest
```
> 你可以通过添加 [`--template` 选项](/more/create-expo/#--template) 来选择不同的模板。
>
> ¥You can choose a different template by adding the [`--template` option](/more/create-expo/#--template).
## 下一步
¥Next step
你有一个项目。现在是时候设置你的开发环境,以便你可以开始开发了。
¥You have a project. Now it's time to set up your development environment so that you can start developing.
## 设置你的环境
了解如何设置开发环境以开始使用 Expo 进行构建。
让我们设置一个本地开发环境以在 Android 和 iOS 上运行你的项目。
¥Let's set up a local development environment for running your project on Android and iOS.
## 你想在哪里开发?
¥Where would you like to develop?
我们建议使用真实设备进行开发,因为你将能够准确地看到用户将看到的内容。
¥We recommend using a real device to develop, since you'll get to see exactly what your users will see.
## 你想如何开发?
¥How would you like to develop?
Expo Go 是一个用于快速试用 Expo 的沙盒。开发版本是你自己的应用的版本,其中包括 Expo 的开发者工具。
¥Expo Go is a sandbox for trying out Expo quickly. A development build is a build of your own app that includes Expo's developer tools.
***
# Android Emulator Setup
## Set up an emulator
Step 1:
On the Android Studio main screen, click **More Actions**, then **Virtual Device Manager** in the dropdown.
Step 2:
Click the **Create device** button.
Step 3:
Under **Select Hardware**, choose the type of hardware you'd like to emulate. We recommend testing against a variety of devices, but if you're unsure where to start, the newest device in the Pixel line could be a good choice.
Step 4:
Select an OS version to load on the emulator (probably one of the system images in the **Recommended** tab), and download the image.
Step 5:
Change any other settings you'd like, and press **Finish** to create the emulator. You can now run this emulator anytime by pressing the Play button in the AVD Manager window.
# Android Studio Environment Setup
## Install Watchman and JDK
#### Prerequisites
Use a package manager such as [Homebrew](https://brew.sh/) to install the following dependency.
#### Install dependencies
Step 1:
[Install Watchman](https://facebook.github.io/watchman/docs/install#macos) using a tool such as Homebrew:
```sh
$ brew install watchman
```
Step 2:
Install OpenJDK distribution called Azul Zulu using Homebrew. This distribution offers JDKs for both Apple Silicon and Intel Macs.
Run the following commands in a terminal:
```sh
$ brew install --cask zulu@17
```
After you install the JDK, add the `JAVA_HOME` environment variable in **~/.bash_profile** (or **~/.zshrc** if you use Zsh):
```bash
export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
```
#### Prerequisites
Use a package manager such as [Chocolatey](https://chocolatey.org/) to install the following dependencies.
#### Install dependencies
Install [Java SE Development Kit (JDK)](https://openjdk.org/):
```sh
$ choco install -y microsoft-openjdk17
```
For Linux:
#### Install dependencies
Step 1:
Follow [instructions from the Watchman documentation](https://facebook.github.io/watchman/docs/install#linux) to compile and install it from the source.
Step 2:
Install [Java SE Development Kit (JDK)](https://openjdk.org/):
You can download and install [OpenJDK@17](http://openjdk.java.net/) from [AdoptOpenJDK](https://adoptopenjdk.net/) or your system packager.
# Android Studio Setup
## Set up Android Studio
For macOS:
Step 1:
Download and install [Android Studio](https://developer.android.com/studio).
Step 2:
Open the **Android Studio** app, click **More Actions** and select **SDK Manager**.
Step 3:
Open Android Studio, go to **Settings** > **Languages & Frameworks** > **Android SDK**. From the **SDK Platforms** tab, select the latest Android version (API level).
Then, click on the **SDK Tools** tab and make sure you have at least one version of the **Android SDK Build-Tools** and **Android Emulator** installed.
Step 4:
Copy or remember the path listed in the box that says **Android SDK Location**.
Step 5:
Click **Apply** and **OK** to install the Android SDK and related build tools.
Step 6:
If you are on macOS or Linux, add an [environment variable](https://developer.android.com/studio/command-line/variables#envar) pointing to the Android SDK location in **~/.bash_profile** (or **~/.zshrc** if you use Zsh). For example: `export ANDROID_HOME=/your/path/here`.
Add the following lines to your **/.zprofile** or **~/.zshrc** (if you are using bash, then **~/.bash_profile** or **~/.bashrc**) config file:
```sh
$ export ANDROID_HOME=$HOME/Library/Android/sdk
$ export PATH=$PATH:$ANDROID_HOME/emulator
$ export PATH=$PATH:$ANDROID_HOME/platform-tools
```
Step 7:
Reload the path environment variables in your current shell:
```sh
$ source $HOME/.zshrc
$ source $HOME/.bashrc
```
Step 8:
Finally, make sure that you can run `adb` from your terminal.
Note: Troubleshooting: Android Studio not recognizing JDK
---
If Android Studio doesn't recognize your homebrew installed JDK, you can create a Gradle configuration file to explicitly set the Java path:
1. Create a Gradle properties file in your home directory:
```sh
$ touch ~/.gradle/gradle.properties
```
2. Add the following line to the **gradle.properties** file, replacing the path with your actual Java installation path:
```bash gradle.properties
java.home=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
```
3. If you have an existing `.gradle` folder in your project directory, delete it and reopen your project in Android Studio:
```sh
$ rm -rf .gradle
```
This should resolve issues with Android Studio not detecting your JDK installation.
---
For Windows:
Step 1:
Download [Android Studio](https://developer.android.com/studio).
Step 2:
Open **Android Studio Setup**. Under **Select components to install**, select Android Studio and Android Virtual Device. Then, click **Next**.
Step 3:
In the Android Studio Setup Wizard, under **Install Type**, select **Standard** and click **Next**.
Step 4:
The Android Studio Setup Wizard will ask you to verify the settings, such as the version of Android SDK, platform-tools, and so on. Click **Next** after you have verified.
Step 5:
In the next window, accept licenses for all available components.
Step 6:
After the tools installation is complete, configure the `ANDROID_HOME` environment variable. Go to **Windows Control Panel** > **User Accounts** > **User Accounts** (again) > **Change my environment variables** and click **New** to create a new `ANDROID_HOME` user variable. The value of this variable will point to the path to your Android SDK:
Note: How to find installed SDK location?
---
By default, the Android SDK is installed at the following location:
```bash
%LOCALAPPDATA%\Android\Sdk
```
To find the location of the SDK in Android Studio manually, go to **Settings** > **Languages & Frameworks** > **Android SDK**. See the location next to **Android SDK Location**.
---
Step 7:
To verify that the new environment variable is loaded, open **PowerShell**, and copy and paste the following command:
```sh
$ Get-ChildItem -Path Env:
```
The command will output all user environment variables. In this list, see if `ANDROID_HOME` has been added.
Step 8:
To add platform-tools to the Path, go to **Windows Control Panel** > **User Accounts** > **User Accounts** (again) > **Change my environment variables** > **Path** > **Edit** > **New** and add the path to the platform-tools to the list as shown below:
Note: How to find installed platform-tools location
---
By default, the platform-tools are installed at the following location:
```bash
%LOCALAPPDATA%\Android\Sdk\platform-tools
```
---
Step 9:
Finally, make sure that you can run `adb` from the PowerShell. For example, run the `adb --version` to see which version of the `adb` your system is running.
# Xcode Setup
Step 1:
### Install Xcode
Open up the Mac App Store, search for [Xcode](https://apps.apple.com/us/app/xcode/id497799835), and click **Install** (or **Update** if you have it already).
Step 2:
### Install Xcode Command Line Tools
Open Xcode, choose **Settings...** from the Xcode menu (or press cmd ⌘ + ,). Go to the **Locations** and install the tools by selecting the most recent version in the **Command Line Tools** dropdown.
Step 3:
### Install an iOS Simulator in Xcode
To install an iOS Simulator, open **Xcode > Settings... > Components**, and under **Platform Support > iOS ...**, click **Get**.
Step 4:
### Install Watchman
[Watchman](https://facebook.github.io/watchman/docs/install#macos) is a tool for watching changes in the filesystem. Installing it will result in better performance. You can install it with:
```sh
$ brew update
$ brew install watchman
```
# Create a development build for a physical Android device with EAS
## Set up an Android device with a development build
Step 1:
### Install EAS CLI
To build your app, you will need to install EAS CLI. You can do this by running the following command in your terminal:
```sh
$ npm install -g eas-cli
```
Step 2:
### Create an Expo account and login
To build your app, you will need to create an Expo account and login to the EAS CLI.
1. [Sign up](https://expo.dev/signup) for an Expo account.
2. Run the following command in your terminal to log in to the EAS CLI:
```sh
$ eas login
```
Step 3:
### Configure your project
Run the following command to create an EAS config in your project:
```sh
$ eas build:configure
```
Step 4:
### Create a build
Run the following command to create a development build:
```sh
$ eas build --platform android --profile development
```
Step 5:
### Install the development build on your device
After the build is complete, scan the QR code in your terminal or open the link on your device. Tap **Install** to download the build on your device, then tap **Open** to install it.
# Create a development build for a physical Android device locally
## Set up an Android device with a development build
## Running your app on an Android device
Step 1:
### Install expo-dev-client
Run the following command in your project's root directory:
```sh
$ npx expo install expo-dev-client
```
Step 2:
### Enable debugging over USB
Most Android devices can only install and run apps downloaded from Google Play, by default. You will need to enable USB Debugging on your device to install your app during development.
To enable USB debugging on your device, you will first need to enable the "Developer options" menu by going to **Settings** > **About phone** > **Software information** and then tapping the `Build number` row at the bottom seven times. You can then go back to **Settings** > **Developer options** to enable "USB debugging".
Step 3:
### Plug in your device via USB
Plug in your Android device via USB to your computer.
Check that your device is properly connecting to ADB, the Android Debug Bridge, by running `adb devices` in your terminal. You should see your device listed with `device` listed next to it. For example:
```sh
$ adb devices
List of devices attached
8AHX0T32K device
```
Step 4:
### Run your app
Run the following from your terminal:
```sh
$ npx expo run:android
```
> This command runs a development server after building your app. You can skip running `npx expo start` on the next page.
# Run on a physical Android device with Expo Go
## Set up an Android device with Expo Go
Scan the QR code to download the app from the Google Play Store, or visit the Expo Go page on the [Google Play Store](https://play.google.com/store/apps/details?id=host.exp.exponent&referrer=docs).
# Create a development build for Android Emulator with EAS
## Set up an Android Emulator with a development build
## Create a development build
Step 1:
### Install EAS CLI
To build your app, you will need to install EAS CLI. You can do this by running the following command in your terminal:
```sh
$ npm install -g eas-cli
```
Step 2:
### Create an Expo account and login
To build your app, you will need to create an Expo account and login to the EAS CLI.
1. [Sign up](https://expo.dev/signup) for an Expo account.
2. Run the following command in your terminal to log in to the EAS CLI:
```sh
$ eas login
```
Step 3:
### Configure your project
Run the following command to create an EAS config in your project:
```sh
$ eas build:configure
```
Step 4:
### Create a build
Run the following command to create a development build:
```sh
$ eas build --platform android --profile development
```
Step 5:
### Install the development build on your emulator
After the build is complete, the CLI will prompt you to automatically download and install it on the Android Emulator. When prompted, press Y to directly install it on the emulator.
If you miss this prompt, you can download the build from the link provided in the terminal and drag and drop it onto the Android Emulator to install it.
# Create a development build for Android Emulator locally
## Set up an Android Emulator with a development build
## Running your app on an Android Emulator
Step 1:
### Install expo-dev-client
Run the following command in your project's root directory:
```sh
$ npx expo install expo-dev-client
```
Step 2:
Run the following from your terminal:
```sh
$ npx expo run:android
```
> This command runs a development server after building your app. You can skip running `npx expo start` on the next page.
# Run on Android Emulator with Expo Go
## Set up an Android Emulator with Expo Go
## Install Expo Go
When you start a development server with `npx expo start` on the [start developing](/get-started/start-developing) page, press a to open the Android Emulator. Expo CLI will install Expo Go automatically.
# Create a development build for a physical iOS device with EAS
## Set up an iOS device with a development build
Step 1:
### Enroll in the Apple Developer Program
To install a development build on your iOS device, you will need an active subscription to the Apple Developer Program. Sign up for the [Apple Developer Program here](https://developer.apple.com/programs/).
Step 2:
### Install EAS CLI
To build your app, you will need to install EAS CLI. You can do this by running the following command in your terminal:
```sh
$ npm install -g eas-cli
```
Step 3:
### Create an Expo account and login
Next, you will need to create an Expo account and login to the EAS CLI.
1. [Sign up](https://expo.dev/signup) for an Expo account.
2. Run the following command in your terminal to log in to the EAS CLI:
```sh
$ eas login
```
Step 4:
### Configure your project
Run the following command to create an EAS config in your project:
```sh
$ eas build:configure
```
Step 5:
### Create an ad hoc provisioning profile
To install a development build on your iOS device, you will need to create an ad hoc provisioning profile. Create one by running the following command in your terminal:
```sh
$ eas device:create
```
Step 6:
### Create a development build
Run the following command to create a development build:
```sh
$ eas build --platform ios --profile development
```
Step 7:
### Install the development build on your device
After the build is complete, scan the QR code in your terminal and tap **Open with iTunes** when it appears inside the Camera app. Alternatively, open the link displayed in the terminal on your device.
After confirming the installation, the app will appear in your device's app library.
Step 8:
### Turn on developer mode
1. Open **Settings** > **Privacy & Security**, scroll down to the **Developer Mode** list item and navigate into it.
2. Tap the switch to enable **Developer Mode**. After you do so, Settings presents an alert to warn you that Developer Mode reduces your device's security. To continue enabling **Developer Mode**, tap the alert's **Restart** button.
3. After the device restarts and you unlock it, the device shows an alert confirming that you want to enable Developer Mode. Tap **Turn On**, and enter your device passcode when prompted.
> Alternatively, if you have Xcode installed on your Mac, you can use it to [enable iOS developer mode](/guides/ios-developer-mode/#connect-an-ios-device-with-a-mac).
# Create a development build for a physical iOS device locally
## Set up an iOS device with a development build
## Set up Xcode and Watchman
## Configure your project
Step 1:
### Install expo-dev-client
Run the following command in your project's root directory:
```sh
$ npx expo install expo-dev-client
```
Step 2:
### Plug in your device via USB and enable developer mode
1. Connect your iOS device to your Mac using a USB cable. Unlock the device and tap **Trust** if prompted.
2. Open Xcode. From the menu bar, select **Window** > **Devices and Simulators**. You will see a warning in Xcode to enable developer mode.
3. On your iOS device, open **Settings** > **Privacy & Security**, scroll down to the **Developer Mode** list item and navigate into it.
4. Tap the switch to enable **Developer Mode**. After you do so, Settings presents an alert to warn you that Developer Mode reduces your device's security. To continue enabling **Developer Mode**, tap the alert's **Restart** button.
5. After the device restarts and you unlock it, the device shows an alert confirming that you want to enable Developer Mode. Tap **Turn On**, and enter your device passcode when prompted.
Step 3:
### Run the project on your device
1. Add the `ios.bundleIdentifier` in the **app.json** file in the root directory to a unique value so that Xcode generates the provisioning profile for the app signing step.
2. Run the following command in your project's root directory and select your plugged in device from the list:
```sh
$ npx expo run:ios --device
```
> This command runs a development server after building your app. You can skip running `npx expo start` on the next page.
# Run on a physical iOS device with Expo Go
## Set up an iOS device with Expo Go
Scan the QR code to download the app from the App Store, or visit the Expo Go page on the [App Store](https://itunes.apple.com/app/apple-store/id982107779).
# Create a development build for iOS Simulator with EAS
## Set up an iOS Simulator with a development build
## Set up Xcode
## Create a development build
Step 1:
### Install EAS CLI
To build your app, you will need to install EAS CLI. You can do this by running the following command in your terminal:
```sh
$ npm install -g eas-cli
```
Step 2:
### Create an Expo account and login
Next, you will need to create an Expo account and login to the EAS CLI.
1. [Sign up](https://expo.dev/signup) for an Expo account.
2. Run the following command in your terminal to log in to the EAS CLI:
```sh
$ eas login
```
Step 3:
### Configure your project
Run the following command to create an EAS config in your project:
```sh
$ eas build:configure
```
Step 4:
### Adjust your build profile
To create a simulator-compatible development build, you'll need to update your build profile in **eas.json** to set the `ios.simulator` property to `true`:
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
}
}
}
```
Step 6:
### Create a development build
Run the following command to create a development build:
```sh
$ eas build --platform ios --profile development
```
Step 7:
### Install the development build on your simulator
After the build is complete, the CLI will prompt you to automatically download and install it on the iOS Simulator. When prompted, press Y to directly install it on the simulator.
If you miss this prompt, you can download the build from the link provided in the terminal and drag and drop it onto the iOS Simulator to install it.
# Create a development build for iOS Simulator locally
## Set up an iOS Simulator with a development build
## Set up Xcode and Watchman
## Running your app on an iOS Simulator
Step 1:
### Install expo-dev-client
Run the following command in your project's root directory:
```sh
$ npx expo install expo-dev-client
```
Step 2:
Run the following from your terminal:
```sh
$ npx expo run:ios
```
> This command runs a development server after building your app. You can skip running `npx expo start` on the next page.
# Run on iOS Simulator with Expo Go
## Set up an iOS Simulator with Expo Go
## Set up Xcode
## Install Expo Go
When you start a development server with `npx expo start` on the [start developing](/get-started/start-developing) page, press i to open the iOS Simulator. Expo CLI will install Expo Go automatically.
## 下一步
¥Next step
你有一个项目和一个开发环境。现在是时候开始开发了。
¥You have a project and a development environment. Now it's time to start developing.
## 开始开发
对 Expo 项目进行第一次更改并在你的设备上实时查看。
Step 1:
## Start a development server
To start the development server, run the following command:
```sh
$ npx expo start
```
Step 2:
## Open the app on your device
After running the command above, you will see a QR code in your terminal. Scan this QR code to open the app on your device.
If you're using an Android Emulator or iOS Simulator, you can press a or i respectively to open the app.
Note: Having problems?
---
Make sure you are on the same Wi-Fi network on your computer and your device.
If it still doesn't work, it may be due to the router configuration — this is common for public networks. You can work around this by choosing the **Tunnel** connection type when starting the development server, then scanning the QR code again.
```sh
$ npx expo start --tunnel
```
> Using the **Tunnel** connection type will make the app reloads considerably slower than on **LAN** or **Local**, so it's best to avoid tunnel when possible. You may want to install and use an emulator or simulator to speed up development if **Tunnel** is required to access your machine from another device on your network.
---
Step 3:
## Make your first change
Open the **app/(tabs)/index.tsx** file in your code editor and make a change.
```diff
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 45cfa0e..4d1b384 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -17,7 +17,7 @@ export default function HomeScreen() {
}
>
- Welcome!
+ Hello World!
```
Note: Changes not showing up on your device?
---
Expo Go is configured by default to automatically reload the app whenever a file is changed, but let's make sure to go over the steps to enable it in case somehow things aren't working.
- Make sure you have the [development mode enabled in Expo CLI](/workflow/development-mode#development-mode).
- Close the Expo app and reopen it.
- Once the app is open again, shake your device to reveal the developer menu. If you are using an emulator, press Ctrl + M for Android or Cmd ⌘ + D for iOS.
- If you see **Enable Fast Refresh**, press it. If you see **Disable Fast Refresh**, dismiss the developer menu. Now try making another change.
---
---
## File structure
Below, you can get familiar with the default project's file structure:
## Features
The default project template has the following features:
## 下一步
开发、审查和提交你的项目。
以下是继续构建应用的后续步骤:
¥Here are next steps to continue building your app:
### 重置你的项目
¥Reset your project
你可以删除样板代码并重新开始新项目。运行以下命令重置你的项目:
¥You can remove the boilerplate code and start fresh with a new project. Run the following command to reset your project:
```sh
$ npm run reset-project
```
此命令将应用中的现有文件移动到 app-example,然后创建一个带有新 index.tsx 文件的新应用目录。
¥This command will move the existing files in **app** to **app-example**, then create a new **app** directory with a new **index.tsx** file.
### 开发、审查和部署
¥Develop, review, and deploy
通过阅读“开发”部分中的文档来了解如何开发。你将学习如何创建 [UI 元素](/develop/user-interface/splash-screen-and-app-icon/)、添加 [单元测试](/develop/unit-testing/)、包含 [原生模块](/config-plugins/introduction/) 等。
¥Learn how to develop by reading the docs in the Develop section. You'll learn how to create [UI elements](/develop/user-interface/splash-screen-and-app-icon/), add [unit tests](/develop/unit-testing/), include [native modules](/config-plugins/introduction/), and more.
开发完应用后,你可以将其与 [review](/review/overview) 的队友共享。
¥Once you've developed your app, you can share it with your teammates for [review](/review/overview).
最后,你可以将你的项目 [build](/deploy/build-project/) 和 [submit](/deploy/submit-to-app-stores/) 到应用商店。
¥Finally, you can [build](/deploy/build-project/) and [submit](/deploy/submit-to-app-stores/) your project to the app stores.
### 分步指南
¥Step-by-step guide
有关从头到尾使用 Expo 构建应用的指导性分步演练,请查看 [tutorial](/tutorial/introduction/)。
¥For a guided, step-by-step walkthrough of building an app with Expo from start to finish, check out the [tutorial](/tutorial/introduction/).
# 开发
## 开发工具
Expo 工具和网站的概述,它们将帮助你完成项目构建过程的各个方面。
当你使用 Expo 创建新项目时,了解以下基本工具和网站可以帮助你完成应用开发之旅。此页面概述了推荐工具列表。
¥When you create a new project with Expo, learning about the following essential tools and websites can help you during your app development journey. This page provides an overview of a list of recommended tools.
## Expo CLI
Expo CLI 是一种开发工具,当你创建新项目时会自动与 `expo` 包一起安装。你可以通过利用 `npx`(Node.js 包运行器)来使用它。
¥Expo CLI is a development tool and is installed automatically with `expo` package when you create a new project. You can use it by leveraging `npx` (a Node.js package runner).
它旨在帮助你在应用开发阶段更快地行动。例如,你与 Expo CLI 的第一次交互是通过运行以下命令启动开发服务器:`npx expo start`。
¥It is designed to help you move faster during the app development phase. For example, your first interaction with Expo CLI is starting the development server by running the command: `npx expo start`.
以下是你在开发应用时将使用 Expo CLI 的常用命令列表:
¥The following is a list of common commands that you will use with Expo CLI while developing your app:
| 命令 | 描述 |
| ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `npx expo start` | 启动开发服务器(无论你使用的是开发版本还是 Expo Go)。 |
| `npx expo prebuild` | 使用 [预建](/workflow/prebuild/) 生成原生 Android 和 iOS 目录。 |
| `npx expo run:android` | 在本地编译原生 Android 应用。 |
| `npx expo run:ios` | 在本地编译原生 iOS 应用。 |
| `npx expo install package-name` | 用于通过向此命令添加 `--fix` 选项来安装新库或验证和更新项目中的特定库。 |
| `npx expo lint` | [设置和配置](/guides/using-eslint/) ESLint。如果 ESLint 已配置,此命令将 [本地与全局 URL 参数](/guides/using-eslint/#usage)。 |
简而言之,Expo CLI 允许你开发、编译、启动应用等。有关你可以使用 CLI 执行的更多可用选项和操作,请参阅 [Expo CLI 参考](/more/expo-cli/)。
¥In a nutshell, Expo CLI allows you to develop, compile, start your app, and more. See [Expo CLI reference](/more/expo-cli/) for more available options and actions you can perform with the CLI.
## EAS 命令行接口
¥EAS CLI
EAS CLI 用于登录你的 Expo 账户并使用不同的 EAS 服务(例如 Build、Update 或 Submit)编译你的应用。你还可以使用此工具来:
¥EAS CLI is used to log in to your Expo account and compile your app using different EAS services such as Build, Update, or Submit. You can also use this tool to:
* 将你的应用发布到应用商店
¥Publish your app to the app stores
* 创建应用的开发、预览或生产版本
¥Create a development, preview, or production build of your app
* 创建无线 (OTA) 更新
¥Create over-the-air (OTA) updates
* 管理你的应用凭据
¥Manage your app credentials
* 为 iOS 设备创建临时配置文件
¥Create an ad hoc provisioning profile for an iOS device
要使用 EAS CLI,你需要通过运行以下命令在本地计算机上全局安装它:
¥To use EAS CLI, you need to install it globally on your local machine by running the command:
```sh
$ npm install -g eas-cli
```
你可以在终端窗口中使用 `eas --help` 来了解有关可用命令的更多信息。有关完整参考,请参阅 [`eas-cli` npm 页面](https://www.npmjs.com/package/eas-cli)。
¥You can use `eas --help` in your terminal window to learn more about the available commands. For a complete reference, see [`eas-cli` npm page](https://www.npmjs.com/package/eas-cli).
## Expo Doctor
Expo Doctor 是一个命令行工具,用于诊断 Expo 项目中的问题。要使用它,请在项目的根目录中运行以下命令:
¥Expo Doctor is a command line tool used to diagnose issues in your Expo project. To use it, run the following command in your project's root directory:
```sh
$ npx expo-doctor
```
此命令执行检查并分析项目的代码库,以查找 [应用配置](/workflow/configuration/) 和 package.json 文件、依赖兼容性、配置文件和项目整体运行状况中的常见问题。检查完成后,Expo Doctor 会输出结果。
¥This command performs checks and analyzes your project's codebase for common issues in [app config](/workflow/configuration/) and **package.json** files, dependency compatibility, configuration files, and the overall health of the project. Once the check is complete, Expo Doctor outputs the results.
如果 Expo Doctor 发现问题,它会提供问题描述以及如何修复或在哪里寻求帮助的建议。
¥If Expo Doctor finds an issue, it provides a description of the problem along with advice on how to fix it or where to find help.
默认情况下,Expo Doctor 会根据 [React Native 目录](https://reactnative.directory/) 验证你项目的包,并在存在原生目录时检查应用配置属性是否正确同步。你可以在项目的 package.json 文件中配置这些检查。有关更多详细信息,请参阅 [`reactNativeDirectoryCheck`](/versions/latest/config/package-json/#reactnativedirectorycheck) 和 [`appConfigFieldsNotSyncedCheck`](/versions/latest/config/package-json/#appconfigfieldsnotsynced)。
¥By default, Expo Doctor validates your project's packages against the [React Native directory](https://reactnative.directory/) and checks if app config properties are properly synced when native directories exist. You can configure these checks in your project's **package.json** file. See [`reactNativeDirectoryCheck`](/versions/latest/config/package-json/#reactnativedirectorycheck) and [`appConfigFieldsNotSyncedCheck`](/versions/latest/config/package-json/#appconfigfieldsnotsynced) for more details.
你还可以使用 `npx expo-doctor --help` 显示使用信息。
¥You can also use `npx expo-doctor --help` to display usage information.
## Orbit
Orbit 是一款 macOS 和 Windows 应用,可实现:
¥Orbit is a macOS and Windows app that enables:
* 在物理设备和模拟器上安装并启动来自 EAS 的构建。
¥Install and launch builds from EAS on physical devices and emulators.
* 在 Android 模拟器或 iOS 模拟器上安装并启动来自 EAS 的更新。
¥Install and launch updates from EAS on Android Emulators or iOS Simulators.
* 在 Android 模拟器或 iOS 模拟器上启动零食项目。
¥Launch snack projects on Android Emulators or iOS Simulators.
* 使用本地文件安装和启动应用。Orbit 支持任何 Android .apk、iOS Simulator 兼容的 .app 或临时签名的应用。
¥Use local files to install and launch apps. Orbit supports any Android **.apk**, iOS Simulator compatible **.app**, or ad hoc signed apps.
* 查看 EAS 仪表板中的固定项目列表。
¥See a list of pinned projects from your EAS dashboard.
### 安装
¥Installation
For macOS:
你可以使用 Homebrew for macOS 下载 Orbit,也可以直接从 [GitHub 发布](https://github.com/expo/orbit/releases) 下载。
¥You can download Orbit with Homebrew for macOS, or directly from the [GitHub releases](https://github.com/expo/orbit/releases).
```sh
$ brew install expo-orbit
```
如果你希望 Orbit 在你登录时自动启动,请单击菜单栏中的 Orbit 图标,然后单击“设置”并选择“登录时启动”选项。
¥If you want Orbit to start when you log in automatically, click on the Orbit icon in the menu bar, then **Settings** and select the **Launch on Login** option.
For Windows:
> 注意:Windows 版 Orbit 处于预览阶段,仅与 x64 和 x86 机器兼容。将来会添加对其他架构的兼容性。
>
> ¥**Note**: Orbit for Windows is in preview and is only compatible with x64 and x86 machines. Compatibility for other architectures will be added in the future.
你可以直接从 [GitHub 发布](https://github.com/expo/orbit/releases) 下载适用于 Windows 的 Orbit。
¥You can download Orbit for Windows directly from the [GitHub releases](https://github.com/expo/orbit/releases).
> **info** Orbit 依赖于 macOS 和 Windows 上的 Android SDK,以及 macOS 上仅用于设备管理的 `xcrun`,这需要同时设置 [安卓工作室](/workflow/android-studio-emulator/) 和 [Xcode](/workflow/ios-simulator/)。
>
> ¥Orbit relies on the Android SDK on both macOS and Windows and `xcrun` for device management only on macOS, which requires setting up both [Android Studio](/workflow/android-studio-emulator/) and [Xcode](/workflow/ios-simulator/).
{/* ### Usage */}
## VS Code 的 Expo 工具
¥Expo Tools for VS Code
Expo Tools 是一个 VS Code 扩展,可在使用应用配置文件时改善你的开发体验。它为应用配置、EAS 配置、存储配置和 Expo Module 配置文件等文件提供自动补齐和智能感知等功能。
¥Expo Tools is a VS Code extension to improve your development experience when working with app config files. It provides features such as autocomplete and intellisense for files such as app config, EAS config, store config and Expo Module config files.
你还可以使用它来调试你的应用,使用 VS Code 的内置调试器设置断点、检查变量、通过调试控制台执行代码等。有关如何使用此扩展进行调试,请参阅 [使用 VS Code 进行调试](/debugging/tools/#debugging-with-vs-code)。
¥You can also use it to debug your app using VS Code's built-in debugger to set breakpoints, inspect variables, execute code through the debug console, and more. See [Debugging with VS Code](/debugging/tools/#debugging-with-vs-code) for how to use this extension for debugging.
## 使用测试原型小样和 Expo Go
¥Test prototypes with Snack and Expo Go
### Snack
Snack 是一个浏览器内开发环境,其工作原理与 Expo Go 类似。这是共享代码片段和试验 React Native 的好方法,无需在计算机上下载任何工具。
¥Snack is an in-browser development environment that works similarly to Expo Go. It's a great way to share code snippets and experiment with React Native without downloading any tools on your computer.
要使用它,请转到 [snack.expo.dev](https://snack.expo.dev/),在 App.js 中编辑 `` 组件,在右侧面板中选择一个平台(Android、iOS 或 Web)并实时查看更改。
¥To use it, go to [snack.expo.dev](https://snack.expo.dev/), edit the `` component in **App.js**, choose a platform (Android, iOS, or web) in the right panel and see the changes live.
### Expo
[Expo](https://expo.dev/go) 是一个免费的开源沙盒,用于学习和试验 React Native。它适用于 Android 和 iOS。
¥[Expo Go](https://expo.dev/go) is a free open-source, sandbox for learning and experimenting with React Native. It works with Android and iOS.
有关如何使用它的更多信息:
¥For more information on how to use it:
* 单击 [树摇动](/get-started/set-up-your-environment/?mode=expo-go) 转到“设置你的环境”指南
¥Click [this link](/get-started/set-up-your-environment/?mode=expo-go) to go to Set up your environment guide
* 在“你想在哪里开发?”下选择要开发的平台
¥Select a platform to develop under **Where would you like to develop?**
* 在“你想如何开发?”下选择 Expo Go
¥Select Expo Go under **How would you like to develop?**
* 按照该指南中描述的说明操作
¥Follow the instructions described in that guide
> 注意:不建议构建生产应用并将其分发到应用商店。请改用 [开发构建](/get-started/set-up-your-environment/?mode=development-build)。
>
> ¥**Note:** Not recommended for building and distributing production apps to the app stores. Instead, use [development builds](/get-started/set-up-your-environment/?mode=development-build).
Note: What if I open a project with an unsupported SDK version?
---
When running a project that was created for an unsupported SDK version in Expo Go, you'll see the following error:
```sh
"Project is incompatible with this version of Expo Go"
```
To fix this, upgrading your project to a [supported SDK version](/versions/latest/#each-expo-sdk-version-depends-on-a-react-native-version) is recommended. If you want to learn how to do it, see [Upgrade the project to a new SDK Version](#how-do-i-upgrade-my-project-from).
---
Note: How do I upgrade my project from an unsupported SDK version?
---
See [Upgrading Expo SDK guide](/workflow/upgrading-expo-sdk-walkthrough) for instructions for upgrading to a specific SDK version.
---
## React Native 目录
¥React Native directory
当你使用开发版本创建项目时,任何与 React Native 兼容的库都可以在 Expo 项目中使用。
¥Any library that is compatible with React Native works in an Expo project when you use a development build to create your project.
[reactnative.directory](https://reactnative.directory/) 是一个可搜索的 React Native 库数据库。如果你要查找的库未包含在 Expo SDK 中,请使用目录查找与你的项目兼容的库。
¥[reactnative.directory](https://reactnative.directory/) is a searchable database for React Native libraries. If a library you are looking for is not included in Expo SDK, use the directory to find a compatible library for your project.
## 验证
了解如何在 Expo 项目中设置身份验证。
Expo 可用于登录 Android、iOS 和 Web 上的许多流行提供商。[`expo-auth-session`](/versions/latest/sdk/auth-session/) 包允许 [基于浏览器的身份验证](/versions/latest/sdk/auth-session/#how-web-browser-based-authentication-flows-work)(使用 OAuth 或 OpenID Connect)到你的 Android、iOS 和 Web 项目。你还可以使用 [开发构建](/develop/development-builds/create-a-build) 的第三方提供商的原生库来实现身份验证。
¥Expo can be used to login to many popular providers on Android, iOS, and web. [`expo-auth-session`](/versions/latest/sdk/auth-session/) package allows [browser-based authentication](/versions/latest/sdk/auth-session/#how-web-browser-based-authentication-flows-work) (using OAuth or OpenID Connect) to your project for Android, iOS, and the web. You can also implement authentication using native libraries for third-party providers with [development builds](/develop/development-builds/create-a-build).
## 使用 Jest 进行单元测试
了解如何设置和配置 jest-expo 库以使用 Jest 为项目编写单元和快照测试。
[Jest](https://jest.nodejs.cn) 是最广泛使用的单元和快照 JavaScript 测试框架。在本指南中,你将学习如何在项目中设置 Jest、编写单元测试、编写快照测试,以及在将 Jest 与 React Native 结合使用时构建测试的最佳实践。
¥[Jest](https://jest.nodejs.cn) is the most widely used unit and snapshot JavaScript testing framework. In this guide, you will learn how to set up Jest in your project, write a unit test, write a snapshot test, and best practices for structuring your tests when using Jest with React Native.
你还将使用 [`jest-expo`](https://github.com/expo/expo/tree/main/packages/jest-expo) 库,这是一个 Jest 预设,可模拟 Expo SDK 的原生部分并处理 Expo 项目所需的大部分配置。
¥You will also use the [`jest-expo`](https://github.com/expo/expo/tree/main/packages/jest-expo) library, which is a Jest preset that mocks the native part of the Expo SDK and handles most of the configuration required for your Expo project.
## 安装和配置
¥Installation and configuration
创建 Expo 项目后,请按照以下说明在你的项目中安装和配置 `jest-expo`:
¥After creating your Expo project, follow the instructions below to install and configure `jest-expo` in your project:
Step 1:
Install `jest-expo` and other required dev dependencies in your project. Run the following command from your project's root directory:
For macOS/Linux:
```sh
$ npx expo install jest-expo jest @types/jest --dev
```
For Windows:
```sh
$ npx expo install jest-expo jest @types/jest "--" --dev
```
> **Note:** If your project is not using TypeScript, you can skip installing `@types/jest`.
Step 2:
Open **package.json**, add a script for running tests, and add the preset for using the base configuration from `jest-expo`:
```json package.json
{
"scripts": {
},
"jest": {
"preset": "jest-expo"
}
}
```
Step 3:
In **package.json**, add `jest-expo` as a preset so that a base for Jest's configuration is set up:
```json package.json
{
"jest": {
"preset": "jest-expo"
}
}
```
Note: Additional configuration for using
---
You can transpile node modules your project uses by configuring [`transformIgnorePatterns`](https://jest.nodejs.cn/docs/configuration#transformignorepatterns-arraystring) in your **package.json**. This property takes a regex pattern as its value:
For npm/Yarn:
```json package.json
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg)"
]
}
```
For pnpm:
```json package.json
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!(?:.pnpm/)?((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg))"
]
}
```
Jest has many configuration options, but the above configuration should cover most of your needs. However, you can always add to this pattern list. For more details, see [Configuring Jest](https://jest.nodejs.cn/docs/configuration).
---
## Install React Native Testing Library
The [React Native Testing Library (`@testing-library/react-native`)](https://callstack.github.io/react-native-testing-library/) is a lightweight solution for testing React Native components. It provides utility functions and works with Jest.
To install it, run the following command:
For macOS/Linux:
```sh
$ npx expo install @testing-library/react-native --dev
```
For Windows:
```sh
$ npx expo install @testing-library/react-native "--" --dev
```
> **warning** **Deprecated:** `@testing-library/react-native` replaces the deprecated `react-test-renderer` because `react-test-renderer` does not support React 19 and above. Remove the deprecated library from your project if you are currently using it. See [React's documentation for more information](https://react.nodejs.cn/warnings/react-test-renderer).
## Unit test
A unit test checks the smallest unit of code, usually a function. To write your first unit test, take a look at the following example:
Step 1:
Inside the **app** directory of your project, create a new file called **index.tsx**, and the following code to render a simple component:
```tsx index.tsx
import { PropsWithChildren } from 'react';
import { StyleSheet, Text, View } from 'react-native';
export const CustomText = ({ children }: PropsWithChildren) => {children};
export default function HomeScreen() {
return (
Welcome!
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
```
Step 2:
Create a **\_\_tests\_\_** directory at the root of your project's directory. If this directory already exists in your project, use that. Then, create a new file called **HomeScreen-test.tsx**. The `jest-expo` preset customizes the Jest configuration to also identify files with **-test.ts|tsx** extensions as tests.
Add the following example code in **HomeScreen-test.tsx**:
```tsx HomeScreen-test.tsx
import { render } from '@testing-library/react-native';
import HomeScreen, { CustomText } from '@/app/index';
describe('', () => {
test('Text renders correctly on HomeScreen', () => {
const { getByText } = render();
getByText('Welcome!');
});
});
```
In the above example, the `getByText` query helps your tests find relevant element in your app's user interface and make assertion whether or not the certain element exists. The React Native Testing Library provides this query, and each [query variant](https://callstack.github.io/react-native-testing-library/docs/api/queries#query-variant) differs in its return type. For more examples and detailed API information, see the React Native Testing Library's [Queries API reference](https://callstack.github.io/react-native-testing-library/docs/api/queries).
Step 3:
Run the following command in a terminal window to execute the test:
```sh
$ npm run test
```
You will see one test being passed.
## Structure your tests
Organizing your test files is important to make them easier to maintain. A common pattern is creating a **\_\_tests\_\_** directory and putting all your tests inside.
An example structure of tests next to the **components** directory is shown below:
Alternatively, you can have multiple **\_\_tests\_\_** sub-directories for different areas of your project. For example, create a separate test directory for **components**, and so on:
It's all about preferences, and it is up to you to decide how you want to organize your project directory.
## Snapshot test
> **info** **Note:** For UI testing, we recommend end-to-end tests instead of snapshot unit tests. See the [E2E tests with Maestro](/eas/workflows/examples/e2e-tests/) guide.
A [snapshot test](https://jest.nodejs.cn/docs/en/snapshot-testing) is used to make sure that UI stays consistent, especially when a project is working with global styles that are potentially shared across components.
To add a snapshot test for ``, add the following code snippet in the `describe()` in **HomeScreen-test.tsx**:
```tsx HomeScreen-test.tsx
describe('', () => {
test('CustomText renders correctly', () => {
const tree = render(Some text).toJSON();
expect(tree).toMatchSnapshot();
});
});
```
Run `npm run test` command, and you will see a snapshot created inside **\_\_tests\_\_\\\_\_snapshots\_\_** directory, and two tests passed.
## Code coverage reports
Code coverage reports can help you understand how much of your code is tested. To see the code coverage report in your project using the HTML format, in **package.json**, under `jest`, set the `collectCoverage` to true and use `collectCoverageFrom` to specify a list of files to ignore when collecting the coverage.
```json package.json
"jest": {
...
"collectCoverage": true,
"collectCoverageFrom": [
"**/*.{ts,tsx,js,jsx}",
"!**/coverage/**",
"!**/node_modules/**",
"!**/babel.config.js",
"!**/expo-env.d.ts",
"!**/.expo/**"
]
}
```
Run `npm run test`. You will see a **coverage** directory created in your project. Find the **lcov-report/index.html** and open it in a browser to see the coverage report.
>
通常,我们不建议将 index.html 文件上传到 git。在 .gitignore 文件中添加 `coverage/**/*` 以防止被跟踪。
¥Usually, we don't recommend uploading **index.html** file to git. Add `coverage/**/*` in the **.gitignore** file to prevent it from being tracked.
## Jest 流程(可选)
¥Jest flows (optional)
你还可以使用不同的流程来运行测试。以下是你可以尝试的一些示例脚本:
¥You can also use different flows to run your tests. Below are a few example scripts that you can try:
```json package.json
"scripts": {
"test": "jest --watch --coverage=false --changedSince=origin/main",
"testDebug": "jest -o --watch --coverage=false",
"testFinal": "jest",
"updateSnapshots": "jest -u --coverage=false"
}
```
有关更多信息,请参阅 Jest 文档中的 [CLI 选项](https://jest.nodejs.cn/docs/en/cli)。
¥For more information, see [CLI Options](https://jest.nodejs.cn/docs/en/cli) in Jest documentation.
## 附加信息
¥Additional information
# 导航
## 基于文件的路由
了解 Expo Router,它是一个基于文件的路由系统,以及如何在你的项目中使用它。
本指南提供了 Expo Router 和导航模式(堆栈和选项卡)的基本约定和指导。要继续,你可以在现有项目中 [使用默认模板创建项目](/get-started/create-a-project/) 或安装 [Expo Router 库手动](/router/installation/#manual-installation)。
¥This guide provides basic conventions and guidance for Expo Router and navigation patterns (stack and tabs). To follow along, you can [create a project by using the default template](/get-started/create-a-project/) or install [Expo Router library manually](/router/installation/#manual-installation) in your existing project.
## 什么是 Expo Router?
¥What is Expo Router?
Expo Router 是用于 React Native 和 Web 应用的路由框架。它允许你管理应用中屏幕之间的导航,并在多个平台(Android、iOS 和 Web)上使用相同的组件。它使用基于文件的方法来确定应用内的路由。它还提供原生导航并建立在 [React 导航](https://react-navigation.nodejs.cn/) 之上。
¥Expo Router is a routing framework for React Native and web applications. It allows you to manage navigation between screens in your app and use the same components on multiple platforms (Android, iOS and web). It uses a file-based method to determine routes inside your app. It also provides native navigation and is built on top of [React Navigation](https://react-navigation.nodejs.cn/).
## app 目录
¥app directory
该应用是一个特殊目录。你添加到此目录的任何文件都将成为原生应用内的路由,并在网络上反映该路由的相同 URL。
¥The **app** is a special directory. Any file you add to this directory becomes a route inside the native app and reflects the same URL for that route on the web.
## 创建路由
¥Create a route
在 app 目录中,通过添加包含 index.tsx 文件的文件或嵌套目录来创建路由。
¥In the **app** directory, a route is created by adding a file or a nested directory that includes **index.tsx** file.
例如,要创建应用的初始路由,你可以使用以下代码将 index.tsx 添加到应用目录:
¥For example, to create an initial route of your app, you can add **index.tsx** to the **app** directory with the following code:
```tsx app/index.tsx|collapseHeight=280
import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
Home
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
app/index.tsx 文件将与 `/` 路由匹配,创建此文件后,应用目录结构为:
¥The **app/index.tsx** file will match the `/` route and after creating this file the **app** directory structure is:
### 文件名约定
¥File name conventions
名为 index 的文件与父目录匹配,并且不添加路径段。例如,如果你通过添加 app/settings/index.tsx 来扩展应用的文件结构,它将匹配 `/settings` 路由。
¥Files named **index** match the parent directory and do not add a path segment. For example, if you expand the app's file structure by adding **app/settings/index.tsx**, it will match the `/settings` route.
> 注意:通过将 React 组件导出为默认值来定义路由文件。该文件必须使用 `.js`、`.jsx`、`.ts` 或 `.tsx` 扩展名。
>
> ¥**Note:** A route file is defined by exporting a React component as the default value. The file must use either `.js`, `.jsx`, `.ts`, or `.tsx` extension.
## \_layout 文件
¥\_layout file
目录中的布局文件用于定义共享 UI 元素(例如标题、标签栏),以便它们在不同路由之间保持不变。
¥Layout files in a directory are used to define shared UI elements such as headers, tab bars so that they persist between different routes.
每次你创建新项目时,默认情况下,应用目录将包含一个根布局文件(app/_layout)。
¥Any time you create a new project, by default the **app** directory will contain a **root layout** file (**app/_layout**).
### 根布局
¥Root layout
传统上,React Native 项目由单个根组件构成(定义为 App.js 或 index.js)。类似地,应用目录中的第一个布局文件(_layout.tsx)被视为单个根组件。
¥Traditionally, React Native projects are structured with a single root component (defined as **App.js** or **index.js**). Similarly, the first layout file (**_layout.tsx**) inside the **app** directory is considered to be the single root component.
在多个路由之间,Expo Router 中的 Root 布局文件用于在多个路由之间共享 UI,例如注入全局提供程序、主题、样式、延迟启动画面渲染直到加载资源和字体,或定义应用的根导航结构。
¥Between multiple routes, a Root layout file in Expo Router is used to share UI between multiple routes such as injecting global providers, themes, styles, delay splash screen rendering until assets and fonts are loaded, or defining your app's root navigation structure.
例如,以下代码导出一个名为 `RootLayout` 的默认 React 组件:
¥For example, the following code exports a default React component called `RootLayout`:
```tsx app/_layout.tsx
export default function RootLayout() {
return (
)
}
```
> **info** 使用 Expo Router,app/_layout.tsx 中定义的任何 React 提供程序都可以通过应用中的任何路由访问。要提高性能并减少渲染次数,请尝试将提供程序的范围缩小到仅需要它们的路由。
>
> ¥With Expo Router, any React providers defined inside **app/_layout.tsx** are accessible by any route in your app. To improve performance and cause fewer renders, try to reduce the scope of your providers to only the routes that need them.
## 堆栈导航器
¥Stack navigator
堆栈导航器是一种在应用中的不同路由之间导航的模式。它允许在屏幕之间转换并管理导航历史记录。它在概念上类似于 Web 浏览器处理导航状态的方式。
¥A stack navigator is a pattern to navigate between different routes in an app. It allows transitioning between screens and managing the navigation history. It is conceptually similar to how a web browser handles the navigation state.
例如,如果你想添加新路由 `/details`,请创建 details.tsx 文件。这将允许应用用户从 `/` 路由导航到 `/details`:
¥For example, if you want to add a new route `/details`, create **details.tsx** file. This will allow the app user to navigate from the `/` route to `/details`:
```tsx app/details.tsx|collapseHeight=300
import { View, Text, StyleSheet } from 'react-native';
export default function DetailsScreen() {
return (
Details
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
创建此路由文件后,当前文件结构如下:
¥After creating this route file, the current file structure looks like:
要允许在两个路由(`/` 和 `/details`)之间导航,请更新 Root 布局文件并向其中添加 `Stack` 组件:
¥To allow navigation between two routes (`/` and `/details`), update the Root layout file and add a `Stack` component to it:
```tsx app/_layout.tsx|collapseHeight=440
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
);
}
```
布局文件中的 `` 组件允许在堆栈中定义路由。
¥`` component in the layout file allows defining routes in a stack.
> 注意:上面示例中的 `screenOptions` 允许为堆栈内的所有路由配置选项。请参阅 [静态配置路由选项](/router/advanced/stack/#statically-configure-route-options) 了解更多信息。
>
> ¥**Note:** The `screenOptions` in the above example allows configuring options for all the routes inside a stack. See [Statically configure route options](/router/advanced/stack/#statically-configure-route-options) for more information.
## 在路由之间导航
¥Navigating between routes
Expo Router 使用名为 `Link` 的内置组件在应用中的路由之间移动。这在概念上类似于 Web 使用 `` 标签和 `href` 属性的方式。
¥Expo Router uses a built-in component called `Link` to move between routes in an app. This is conceptually similar to how web works with the `` tag and the `href` attribute.
你可以通过从 Expo Router 库导入它,然后将 `href` 属性与要导航的路由一起传递作为属性的值来使用它。例如,要从 `/` 导航到 `/details`,请在 index.tsx 文件中添加 `Link` 组件:
¥You can use it by importing it from Expo Router library and then passing the `href` prop with the route to navigate as the value of the prop. For example, to navigate from `/` to `/details`, add a `Link` component in the **index.tsx** file:
```tsx app/index.tsx|collapseHeight=300
import { Link } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
Home
View details
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
### `Link` 如何工作?
¥How does `Link` work?
`Link` 默认将子项封装在 `` 中。你可以自定义使用不同的按钮组件。
¥`Link` wraps the children in a `` by default. You can customize to use a different button component.
使用 `Link` 组件封装自定义按钮组件和 `asChild` 属性,将所有属性转发到 `Link` 组件的第一个子项。有关 `Link` 组件的 props 的更多信息,请参阅 [在页面之间导航](/router/navigating-pages/)。
¥Use the `Link` component to wrap the custom button component and the `asChild` prop which forwards all props to the first child of the `Link` component. For more information on the `Link` component's props, see [Navigate between pages](/router/navigating-pages/).
## 群组
¥Groups
创建一个组来组织类似的路由或应用的一部分。每个组都有一个布局文件,分组目录需要在括号 `(group)` 内命名。
¥A group is created to organize similar routes or a section of the app. Each group has a layout file, and the grouped directory requires a name inside parentheses `(group)`.
例如,你有 `/` 和 `/details` 路由,它们可以在 app/(home) 目录中分组。这会将文件结构更新为:
¥For example, you have the `/` and `/details` routes which can be grouped inside **app/(home)** directory. This updates the file structure to:
你还需要添加 (home)/_layout.tsx,它用于定义 `/` 和 `/details` 路由的 `Stack` 导航器。
¥You also need to add **(home)/_layout.tsx** which is used to define the `Stack` navigator for `/` and `/details` routes.
```tsx app/(home)/_layout.tsx|collapseHeight=440
import { Stack } from 'expo-router';
export default function HomeLayout() {
return (
);
}
```
Root 布局文件也发生了变化,现在包括 (home) 组,该组进一步使用 (home)/index 作为应用的初始路由。
¥The Root layout file also changes and now includes the **(home)** group which further uses **(home)/index** as the initial route of the app.
```tsx app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
);
}
```
> 注意:在上面的例子中,屏幕选项被移动到 (home)/_layout.tsx 文件。这意味着如果你在 Root 布局中向 Stack 导航器添加任何路由,它将不会使用与 Home 布局中的路由相同的屏幕选项。
>
> ¥**Note:** In the above example, the screen options are moved to **(home)/_layout.tsx** file. This means if you add any route to the Stack navigator inside the Root layout, it will not use the same screen options as the routes inside the Home layout.
## 标签导航器
¥Tab navigator
选项卡导航器是一种使用选项卡栏在应用的不同部分之间导航的常见模式。Expo Router 提供 `Tabs` 导航组件。
¥A tab navigator is a common pattern to navigate between different sections of an app using a tab bar. Expo Router provides a `Tabs` navigation component.
例如,在当前文件结构中,你有两个不同的部分:主页(`/` 和 `/details` 路由)和设置(`/settings` 路由)。添加一个特殊目录(tabs),你可以将现有的 Home 路由文件移动到其中并创建一个 settings.tsx。
¥For example, in the current file structure, you have two different sections: Home (`/` and `/details` routes) and Settings (`/settings` route). Adding a special directory **(tabs)**, you can move the existing Home route files inside it and create a **settings.tsx**.
(选项卡) 内的任何文件或目录都将成为选项卡导航器中的路由。要使用标签栏在不同路由之间切换,你需要在此目录 (tabs)/_layout 中创建布局文件并导出 `TabLayout` 组件:
¥Any file or directory inside **(tabs)** becomes a route in the tab navigator. To switch between different routes using the tab bar, you need to create a layout file inside this directory **(tabs)/_layout** and export a `TabLayout` component:
```tsx app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
);
}
```
> 注意:在 `TabLayout` 中,`(home)` 的现有 Stack 导航器现已嵌套。
>
> ¥**Note:** In `TabLayout`, the existing Stack navigator for `(home)` is now nested.
要使其工作,请通过添加 (tabs) 作为第一条路由来更新 app/_layout.tsx 文件。
¥To make this work, update the **app/_layout.tsx** file by adding **(tabs)** as the first route.
```tsx app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
);
}
```
## 未找到路由
¥Not found routes
Expo Router 提供了一个特殊文件 +not-found.tsx,用于处理 404 路由。此路由文件匹配嵌套级别中所有不匹配的路由。
¥Expo Router provides a special file **+not-found.tsx** which is used to handle routes that are 404s. This route file matches all unmatched routes from a nested level.
在应用目录中创建此文件:
¥Create this file in the **app** directory:
```tsx +not-found.tsx|collapseHeight=320
import { Link, Stack } from 'expo-router';
import { View, StyleSheet } from 'react-native';
export default function NotFoundScreen() {
return (
<>
Go to home screen
>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
## 动态路由
了解动态路由以及如何使用 Expo Router 库创建它们。
动态路由允许根据 URL 中嵌入的动态段匹配一个或多个路径。此段采用变量的形式,例如唯一标识符,并且你的应用事先不知道确切的段。
¥A **dynamic route** allows matching one or multiple paths based on a dynamic segment embedded in the URL. This segment is in the form of a variable, such as a unique identifier, and your app doesn't know the exact segment ahead of time.
本指南介绍如何使用 Expo Router 处理动态路由。
¥This guide explains how to handle dynamic routes with Expo Router.
> 本指南继续在上一个 [基于文件的路由](/develop/file-based-routing/) 中使用的示例和应用目录结构的基础上构建。
>
> ¥This guide continues to build on top of the example and the **app** directory structure used in the previous [File-based routing](/develop/file-based-routing/).
## 动态路由约定
¥Dynamic route convention
通过将文件名称括在方括号中来创建路由的动态段。例如,[id].tsx。
¥A dynamic segment of a route is created by wrapping a file's name in square brackets. For example, **[id].tsx**.
> 什么是动态段?URL 中路径的任何部分都是动态的。例如,在显示用户列表的应用屏幕中,你可能有一个路径,例如 `/details/[id]`,其中 `[id]` 是动态段,并根据用户的 `id` 显示详细信息。
>
> ¥**What is a dynamic segment?** Any segment of a path in a URL that is dynamic. For example, in an app screen where it displays a users list, you might have a path such as `/details/[id]` where the `[id]` is the dynamic segment and displays details based on the `id` of the user.
## 创建动态路由
¥Create a dynamic route
让我们考虑以下应用目录结构:
¥Let's consider the following **app** directory structure:
在上面的文件结构中,`[id]` 用于显示路由 details/[id].tsx 的信息。相同的路由将根据 `id` 的值显示唯一信息:
¥In the above file structure, the `[id]` is used to display information for the route **details/[id].tsx**. The same route will display unique information based on the value of the `id`:
| 路由 | 匹配的网址 |
| --------------------- | ------------ |
| **details/\[id].tsx** | `/details/1` |
| **details/\[id].tsx** | `/details/2` |
此动态段约定确保当应用用户从主屏幕导航到详细信息屏幕时,他们会看到路由动态段的正确信息。
¥This dynamic segment convention makes sure that when an app user navigates from the home screen to the details screen, they view the correct information for the dynamic segment of the route.
## 使用 `Link` 导航到动态路由
¥Use `Link` to navigate to a dynamic route
通过静态或使用 `href` 对象向 `Link` 组件提供查询参数,可以从一条路由导航到一条动态路由。
¥Navigating from one route to a dynamic route is done by providing query parameters to the `Link` component either statically or using the `href` object.
例如,以下代码允许你使用查询参数静态导航到动态路由:
¥For example, the following code allows you to navigate to the dynamic route statically using query parameters:
```tsx app/(home)/index.tsx|collapseHeight=300
import { Link } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
Home
View first user details
View second user details
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
你还可以使用 `href` 对象提供一个 `pathname`,它获取动态路由的值并传递 `params`:
¥You can also use the `href` object to provide a `pathname` which takes the value of the dynamic route and passes `params`:
```tsx app/(home)/index.tsx
import { Link } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
Home
View user details
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
## 从动态段访问参数
¥Access parameters from dynamic segments
URL 的动态段可以通过路由组件中的 [路由参数](/router/reference/url-parameters/) 访问。例如,你可以使用 [`useLocalSearchParams`](/router/reference/hooks/#uselocalsearchparams) 钩子,它返回所选路由的 URL 参数。
¥Dynamic segments of a URL are accessible with a [route parameter](/router/reference/url-parameters/) in the route component. For example, you can use the [`useLocalSearchParams`](/router/reference/hooks/#uselocalsearchparams) hook which returns the URL parameters for the selected route.
```tsx app/(home)/details/[id].tsx|collapseHeight=300
import { useLocalSearchParams } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function DetailsScreen() {
const { id } = useLocalSearchParams();
return (
Details of user {id}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
当 `/` 推送 `/details/1` 时,`useLocalSearchParams` 返回 `{ id: '1' }`,因为 `/details/1` 是选定的路由。
¥When `/` pushes `/details/1`, the `useLocalSearchParams` returns `{ id: '1' }` because `/details/1` is the selected route.
## 下一步
有关 Expo Router 的指南和参考列表,供进一步阅读。
如果你是第一次深入研究基于文件的路由框架,那么有很多关于 Expo Router 的知识需要学习。以下指南和参考资料将帮助你深入了解:
¥There's much to learn about Expo Router if you are diving into the file-based routing framework for the first time. The following guides and references will help you dive deep:
## 导航模式
¥Navigation patterns
## 参考资料
¥References
# 用户界面
## 启动画面和应用图标
了解如何向 Expo 项目添加启动画面和应用图标。
启动画面和应用图标是移动应用的基本元素。它们在用户体验和应用品牌推广中发挥着重要作用。本指南提供了如何创建它们并将其添加到你的应用的步骤。
¥A splash screen and an app icon are fundamental elements of a mobile app. They play an important role in the user experience and branding of the app. This guide provides steps on how to create and add them to your app.
Video Tutorial: [Create an App Icon and Splash Screen](https://www.youtube.com/watch?v=3Bsw8a1BJoQ)
***
## 启动画面
¥Splash screen
启动屏幕,也称为启动屏幕,是用户打开你的应用时看到的第一个屏幕。当应用加载时它保持可见。你还可以使用原生 [启动画面 API](/versions/latest/sdk/splash-screen) 来控制启动屏幕消失时的行为。
¥A splash screen, also known as a launch screen, is the first screen a user sees when they open your app. It stays visible while the app is loading. You can also control the behavior of when a splash screen disappears by using the native [SplashScreen API](/versions/latest/sdk/splash-screen).
[`expo-splash-screen`](/versions/latest/sdk/splash-screen) 有一个内置的 [配置插件](/config-plugins/introduction),可让你配置属性,例如启动图标和背景颜色。
¥The [`expo-splash-screen`](/versions/latest/sdk/splash-screen) has a built-in [config plugin](/config-plugins/introduction) that lets you configure properties such as the splash icon and background color.
> **warning** 请勿使用 Expo Go 或开发版本来测试你的启动画面。Expo Go 在启动画面可见时渲染你的应用图标,这可能会干扰测试。开发版本包括 `expo-dev-client`,它有自己的启动画面,可能会导致冲突。相反,使用 [预览版本](/build/eas-json/#preview-builds) 或 [生产构建](/build/eas-json/#production-builds)。
>
> ¥**Do not use Expo Go or a development build to test your splash screen**. Expo Go renders your app icon while the splash screen is visible, which can interfere with testing. Development builds include `expo-dev-client`, which has its own splash screen and may cause conflicts. **Instead, use a [preview build](/build/eas-json/#preview-builds) or a [production build](/build/eas-json/#production-builds)**.
Step 1:
### Create a splash screen icon
To create a splash screen icon, you can use this [Figma template](https://www.figma.com/community/file/1466490409418563617). It provides a bare minimum design for an icon and splash images for Android and iOS.
**Recommended:**
- Use a 1024x1024 image.
- Use a **.png** file.
- Use a transparent background.
Step 2:
### Export the splash icon as a .png
After creating a splash screen icon, export it as a **.png** and save it in the **assets/images** directory. By default, Expo uses **splash-icon.png** as the file name. If you decide to change the name of your splash screen file, make sure to use that in the next step.
> **Note:** **Currently, only .png images are supported** to use as a splash screen icon in an Expo project. If you use another image format, making a production build of your app will fail.
Step 3:
### Configure the splash screen icon
Open the app config file, and under plugins, set the following properties:
!!!IG0!!!
To test your new splash screen, build your app for [internal distribution](/tutorial/eas/internal-distribution-builds) or for production, see guides on [Android](/tutorial/eas/android-production-build/) and [iOS](/tutorial/eas/ios-production-build/).
!!!IG4!!!
Note: Configuring properties separately for Android and iOS
---
[`expo-splash-screen`](/versions/latest/sdk/splash-screen) also supports `android` and `ios` properties for configuring the splash screen for a specific platform. See the following example:
!!!IG1!!!
---
Note: Not using prebuild?
---
If your app does not use [Expo Prebuild](/workflow/prebuild) (formerly the _managed workflow_) to generate the native **android** and **ios** directories, then changes in the app config will have no effect. For more information, see [how you can customize the configuration manually](https://github.com/expo/expo/tree/main/packages/expo-splash-screen#-installation-in-bare-react-native-projects).
---
Note: Troubleshooting: New splash screen not appearing on iOS
---
For SDK 51 and below, in iOS development builds, launch screens can sometimes remain cached between builds, making it harder to test new images. Apple recommends clearing the _derived data_ directory before rebuilding, this can be done with Expo CLI by running:
```sh
$ npx expo run:ios --no-build-cache
```
See [Apple's guide on testing launch screens](https://developer.apple.com/documentation/technotes/tn3118-debugging-your-apps-launch-screen) for more information.
---
## App icon
An app's icon is what your app users see on their device's home screen and app stores. Android and iOS have different and strict requirements.
Step 1:
### Create an app icon
To create an app icon, you can use this [Figma template](https://www.figma.com/community/file/1466490409418563617). It provides a bare minimum design for an icon and splash images for Android and iOS.
Step 2:
### Export the icon image as a .png
After creating an app icon, export it as **.png** and save it in the **assets/images** directory. By default, Expo uses **icon.png** as the file name. If you decide to use a different file name, make sure to use that in the next step.
Step 3:
### Add the icon in app config
Open the app config and add the local path as the value of [`icon`](/versions/latest/config/app/#icon) property to point it to your new app icon:
!!!IG2!!!
Note: Custom configuration tips for Android and iOS
---
#### Android
Further customization of the Android icon is possible using the [`android.adaptiveIcon`](/versions/latest/config/app/#adaptiveicon) property, which will override both of the previously mentioned settings.
The Android Adaptive Icon is formed from two separate layers — a foreground image and a background color or image. This allows the OS to mask the icon into different shapes and also supports visual effects. For Android 13 and later, the OS supports a themed app icon that uses a wallpaper and theme to determine the color set by the device's theme.
The design you provide should follow the [Android Adaptive Icon Guidelines](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) for launcher icons. You should also:
- Use **.png** files.
- Use the `android.adaptiveIcon.foregroundImage` property to specify the path to your foreground image.
- Use the `android.adaptiveIcon.monochromeImage` property to specify the path to your monochrome image.
- The default background color is white; to specify a different background color, use the `android.adaptiveIcon.backgroundColor` property. You can instead specify a background image using the `android.adaptiveIcon.backgroundImage` property. Make sure that it has the same dimensions as your foreground image.
You may also want to provide a separate icon for older Android devices that do not support Adaptive Icons. You can do so with the `android.icon` property. This single icon would be a combination of your foreground and background layers.
> See [Apple best practices](https://developer.apple.com/design/human-interface-guidelines/app-icons/#Best-practices) to ensure your icon looks professional, such as testing your icon on different wallpapers and avoiding text beside your product's wordmark. Provide an icon that's at least 512x512 pixels.
#### iOS
For iOS, your app's icon should follow the [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/app-icons/). You should also:
- Use a **.png** file.
- 1024x1024 is a good size. If you have an Expo project created using `npx create-expo-app`, [EAS Build](/build/setup/) will generate the other sizes for you. In case of a bare React Native project, generate the icons on your own. The largest size EAS Build generates is 1024x1024.
- The icon must be exactly square. For example, a 1023x1024 icon is not valid.
- Make sure the icon fills the whole square, with no rounded corners or other transparent pixels. The operating system will mask your icon when appropriate.
- Use `ios.icon` to specify different icons for various system appearances (for example, dark and tinted) can be provided. If specified, this overrides the top-level icon key in the app config file. See the example below:
!!!IG3!!!
---
## 安全区域
了解如何在 Expo 项目中添加屏幕组件的安全区域。
创建安全区域可确保你的应用屏幕内容定位正确。这意味着它不会被凹口、状态栏、主页指示器和其他属于设备物理硬件或由操作系统控制的界面元素重叠。当内容重叠时,它会被这些界面元素隐藏。
¥Creating a safe area ensures your app screen's content is positioned correctly. This means it doesn't get overlapped by notches, status bars, home indicators, and other interface elements that are part of the device's physical hardware or are controlled by the operating system. When the content gets overlapped, it gets concealed by these interface elements.
以下是 Android 上应用屏幕内容被状态栏隐藏的示例。在 iOS 上,相同的内容被圆角、凹口和状态栏隐藏。
¥Here's an example of an app screen's content getting concealed by the status bar on Android. On iOS, the same content is concealed by rounded corners, notch, and the status bar.
## 使用 `react-native-safe-area-context` 库
¥Use `react-native-safe-area-context` library
[`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context) 提供了一个灵活的 API 来处理 Android 和 iOS 设备的安全区域插入。它还提供了一个 `SafeAreaView` 组件,你可以使用它代替 [``](https://rn.nodejs.cn/docs/view) 自动考虑屏幕组件中的安全区域。
¥[`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context) provides a flexible API for handling Android and iOS device's safe area insets. It also provides a `SafeAreaView` component that you can use instead of a [``](https://rn.nodejs.cn/docs/view) to account for safe areas automatically in your screen components.
使用该库,上一个示例的结果会发生变化,因为它会在安全区域内显示内容,如下所示:
¥Using the library, the result of the previous example changes as it displays the content inside a safe area, as shown below:
### 安装
¥Installation
如果你使用 [这篇博文](/get-started/create-a-project/) 创建了项目,则可以跳过安装 `react-native-safe-area-context`。此库作为 Expo Router 库的对等依赖安装。否则,请通过运行以下命令进行安装:
¥You can skip installing `react-native-safe-area-context` if you have created a project using [the default template](/get-started/create-a-project/). This library is installed as peer dependency for Expo Router library. Otherwise, install it by running the following command:
```sh
$ npx expo install react-native-safe-area-context
```
### 用法
¥Usage
你可以直接使用 [`SafeAreaView`](https://github.com/th3rdwave/react-native-safe-area-context#safeareaview) 来封装屏幕组件的内容。它是一个常规的 ``,安全区域插入应用为额外的填充或边距。
¥You can directly use [`SafeAreaView`](https://github.com/th3rdwave/react-native-safe-area-context#safeareaview) to wrap the content of your screen's component. It is a regular `` with the safe area insets applied as extra padding or margin.
```tsx app/index.tsx
import { Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function HomeScreen() {
return (
Content is in safe area.
);
}
```
Note: Using a different Expo template and don't have Expo Router installed?
---
Import and add [`SafeAreaProvider`](https://github.com/th3rdwave/react-native-safe-area-context#safeareaprovider) to the root component file (such as **App.tsx**) before using `SafeAreaView` in your screen component.
```tsx App.tsx
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() {
return (
return ...;
);
}
```
---
## Alternate: `useSafeAreaInsets` hook
Alternate to `SafeAreaView`, you can use [`useSafeAreaInsets`](https://github.com/th3rdwave/react-native-safe-area-context#usesafeareainsets) hook in your screen component. It provides direct access to the safe area insets, allowing you to apply padding for each edge of the `` using an inset from this hook.
The example below uses the `useSafeAreaInsets` hook. It applies top padding to a `` using `insets.top`.
```tsx app/index.tsx
import { Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export default function HomeScreen() {
const insets = useSafeAreaInsets();
return (
Content is in safe area.
);
}
```
The hook provides the insets in the following object:
```ts
{
top: number,
right: number,
bottom: number,
left: number
}
```
## Additional information
### Minimal example
Below is a minimal working example that uses the `useSafeAreaInsets` hook to apply top padding to a view.
#### Using react-native-safe-area-context
```tsx collapseHeight=320
import { Text, View } from 'react-native';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
function HomeScreen() {
const insets = useSafeAreaInsets();
return (
Content is in safe area.
);
}
export default function App() {
return (
);
}
```
### 与 React 导航一起使用
¥Usage with React Navigation
默认情况下,React Navigation 支持安全区域并使用 `react-native-safe-area-context` 作为对等依赖。有关详细信息,请参阅 [React 导航文档](https://react-navigation.nodejs.cn/docs/handling-safe-area/)。
¥By default, React Navigation supports safe areas and uses `react-native-safe-area-context` as a peer dependency. For more information, see the [React Navigation documentation](https://react-navigation.nodejs.cn/docs/handling-safe-area/).
### 与 Web 一起使用
¥Usage with web
如果你的目标是 Web,请按照 [使用 Expo CLI 内置的 Chrome DevTools](#usage) 中的说明设置 `SafeAreaProvider`。如果你正在进行服务器端渲染 (SSR),请参阅库文档中的 [Web SSR 部分](https://github.com/th3rdwave/react-native-safe-area-context#web-ssr)。
¥If you are targeting the web, set up `SafeAreaProvider` as described in the [usage section](#usage). If you are doing server-side rendering (SSR), see the [Web SSR section](https://github.com/th3rdwave/react-native-safe-area-context#web-ssr) in the library's documentation.
## 系统栏
了解如何在你的 Expo 项目中处理和自定义系统栏,以实现安全区域和无边框布局。
系统栏是位于屏幕边缘的 UI 元素,提供必要的设备信息和导航控件。根据移动操作系统的不同,它们包括状态栏([安卓](https://developer.android.com/design/ui/mobile/guides/foundations/system-bars) 和 [iOS 系统](https://developer.apple.com/design/human-interface-guidelines/status-bars))、标题栏(仅限 [安卓](https://medium.com/androiddevelopers/insets-handling-tips-for-android-15s-edge-to-edge-enforcement-872774e8839b#:~:text=or%20SHORT_EDGES.-,Caption%20bars,-When%20your%20app))、导航栏([安卓](https://developer.android.com/design/ui/mobile/guides/foundations/system-bars#navigation-bar) 和 [iOS 系统](https://developer.apple.com/design/human-interface-guidelines/navigation-bars))以及主页指示器(仅限 iOS)。
¥System bars are the UI elements at the edges of the screen that provide essential device information and navigation controls. Depending on the mobile OS, they include the status bar ([Android](https://developer.android.com/design/ui/mobile/guides/foundations/system-bars) and [iOS](https://developer.apple.com/design/human-interface-guidelines/status-bars)), caption bar ([Android](https://medium.com/androiddevelopers/insets-handling-tips-for-android-15s-edge-to-edge-enforcement-872774e8839b#:~:text=or%20SHORT_EDGES.-,Caption%20bars,-When%20your%20app) only), navigation bar ([Android](https://developer.android.com/design/ui/mobile/guides/foundations/system-bars#navigation-bar) and [iOS](https://developer.apple.com/design/human-interface-guidelines/navigation-bars)), and home indicator (iOS only).
这些组件用于显示设备信息,例如电池电量、时间、通知提醒,并提供从设备界面任何位置与设备的直接交互。例如,无论应用用户当前正在使用哪个应用,都可以下拉状态栏来访问快速设置和通知。
¥These components are used to display device information such as battery level, time, notification alerts, and provide direct interaction with the device from anywhere in the device's interface. For example, an app user can pull down the status bar to access quick settings and notifications regardless of which app they're currently using.
系统栏是移动体验的基础,了解如何正确使用它们对于创建你的应用至关重要。
¥System bars are fundamental to the mobile experience, and understanding how to work with them properly is important for creating your app.
## 使用安全区域处理重叠
¥Handling overlaps using safe areas
你的部分应用内容可能会绘制在系统栏后面。为此,你需要正确定位应用内容,避免重叠并确保系统栏中的控件可见。
¥Some of your app's content may draw behind the system bars. To handle this, you need to position your app's content correctly by avoiding the overlap and ensuring that the controls from the system bars are present.
以下指南将引导你了解如何使用 `SafeAreaView` 或钩子直接在屏幕的每个边缘应用内嵌图 (Insets)。
¥The following guide walks you through how to use `SafeAreaView` or a hook to apply insets directly for each edge of the screen.
### Android 上的安全区域和无边框布局
¥Safe areas and edge-to-edge layout on Android
在 [Android 上的边到边](https://expo.dev/blog/edge-to-edge-display-now-streamlined-for-android) 之前,半透明的状态栏和导航栏很常见。采用这种方法,这些状态栏后面绘制的内容已经位于其下方,通常无需考虑安全区域。
¥Before [edge-to-edge on Android](https://expo.dev/blog/edge-to-edge-display-now-streamlined-for-android), it was common to have a translucent status bar and navigation bar. With this approach, the content drawn behind these bars was already underneath them, and it was typically not necessary to factor in safe areas.
现在,[Android 上的边到边](https://expo.dev/blog/edge-to-edge-display-now-streamlined-for-android),你需要使用安全区域来确保内容不会与系统栏重叠。
¥Now, [with edge-to-edge on Android](https://expo.dev/blog/edge-to-edge-display-now-streamlined-for-android), you will need to use safe areas to ensure that content does not overlap with system bars.
## 自定义系统栏
¥Customizing system bars
系统栏可以自定义,以匹配你的应用设计,并在不同场景下提供更好的可视性。使用 Expo 时,有两个库可用于此操作:`expo-status-bar` 和 `expo-navigation-bar`(仅限 Android)。
¥System bars can be customized to match your app's design and provide better visibility in different scenarios. When using Expo, there are two libraries available for this: `expo-status-bar` and `expo-navigation-bar` (Android only).
### 状态栏配置
¥Status bar configuration
状态栏显示在 Android 和 iOS 屏幕的顶部。你可以使用 [`expo-status-bar`](/versions/latest/sdk/status-bar) 对其进行自定义。它提供了一个 `StatusBar` 组件,你可以使用该组件的 [`style`](/versions/latest/sdk/status-bar/#style) 属性或 [`setStatusBarStyle`](/versions/latest/sdk/status-bar/#statusbarsetstatusbarstylestyle-animated) 方法在应用运行时控制状态栏的外观:
¥The status bar appears at the top of the screen on both Android and iOS. You can customize it using [`expo-status-bar`](/versions/latest/sdk/status-bar). It provides a `StatusBar` component that you can use to control the appearance of the status bar while your app is running using the [`style`](/versions/latest/sdk/status-bar/#style) property or the [`setStatusBarStyle`](/versions/latest/sdk/status-bar/#statusbarsetstatusbarstylestyle-animated) method:
```tsx app/_layout.tsx
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
<>
{/* Use light text instead of dark text in the status bar to provide more contrast with a dark background. */}
>;
}
```
> 注意:在 Expo 默认模板中,`style` 属性设置为 `auto`。它会根据你应用当前使用的配色方案(亮色或暗色模式)自动选择合适的样式。
>
> ¥**Note:** In Expo default template, the `style` property is set to `auto`. It automatically picks the appropriate style depending on the color scheme (light or dark mode) currently used by your app.
要控制 `StatusBar` 的可见性,可以将 [`hidden`](/versions/latest/sdk/status-bar/#hidden) 属性设置为 `true` 或使用 [`setStatusBarHidden`](/versions/latest/sdk/status-bar/#statusbarsetstatusbarhiddenhidden-animation) 方法。
¥To control the `StatusBar` visibility, you can set the [`hidden`](/versions/latest/sdk/status-bar/#hidden) property to `true` or use the [`setStatusBarHidden`](/versions/latest/sdk/status-bar/#statusbarsetstatusbarhiddenhidden-animation) method.
在 Android 上启用无边框屏幕后,`expo-status-bar` 中依赖于不透明状态栏 [不可用](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge) 的功能将无法使用。只能自定义样式和可见性。其他属性将不执行任何操作并发出警告。
¥**With edge-to-edge enabled on Android, features from `expo-status-bar` that depend on an opaque status bar [are unavailable](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge)**. It's only possible to customize the style and visibility. Other properties will no-op and warn.
### 导航栏配置(仅限 Android)
¥Navigation bar configuration (Android only)
在 Android 设备上,导航栏会显示在屏幕底部。你可以使用 [`expo-navigation-bar`](/versions/latest/sdk/navigation-bar) 库对其进行自定义。它提供了一个 `NavigationBar` 组件,你可以使用该组件的 [`setStyle`](/versions/latest/sdk/navigation-bar/#navigationbarsetstylestyle) 方法设置导航栏的样式:
¥On Android devices, the Navigation Bar appears at the bottom of the screen. You can customize it using the [`expo-navigation-bar`](/versions/latest/sdk/navigation-bar) library. It provides a `NavigationBar` component that you can use to set the style of the navigation bar using the [`setStyle`](/versions/latest/sdk/navigation-bar/#navigationbarsetstylestyle) method:
```tsx app/_layout.tsx
import { NavigationBar } from 'expo-navigation-bar';
import { useEffect } from 'react';
useEffect(() => {
if (Platform.OS === 'android') {
// Set the navigation bar style
NavigationBar.setStyle('dark');
}
}, []);
```
要控制 `NavigationBar` 的可见性,可以使用 [`setVisibilityAsync`](/versions/latest/sdk/navigation-bar/#navigationbarsetvisibilityasyncvisibility) 方法。
¥To control the `NavigationBar` visibility, you can use the [`setVisibilityAsync`](/versions/latest/sdk/navigation-bar/#navigationbarsetvisibilityasyncvisibility) method.
在 Android 上启用无边框屏幕后,`expo-navigation-bar` 中依赖于不透明导航栏 [不可用](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge) 的功能将无法使用。只能自定义样式和可见性。其他属性将不执行任何操作并发出警告。
¥**With edge-to-edge enabled on Android, features from `expo-navigation-bar` that depend on an opaque navigation bar [are unavailable](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge)**. It's only possible to customize the style and visibility. Other properties will no-op and warn.
## 字体
了解如何使用本地文件或 Google Font 包将自定义字体集成到应用中
Android 和 iOS 都有自己的一套平台字体。为了提供一致的用户体验并增强应用的品牌,你可以使用自定义字体。
¥Android and iOS come with their own set of platform fonts. To provide a consistent user experience and enhance your app's branding, you can use custom fonts.
本指南介绍了向项目中添加和加载自定义字体的不同方法,还提供了与字体相关的其他信息。
¥This guide covers different ways you can add and load a custom font into your project and also provides additional information related to fonts.
## 添加自定义字体
¥Add a custom font
有两种方法可以将自定义字体添加到项目中:
¥There are two ways you can add a custom font into your project:
* 将字体文件添加到你的本地资源中。例如,assets/fonts 目录中的字体文件。
¥Add a font file into your local assets. For example, a font file in the **assets/fonts** directory.
* 安装 Google 字体包。例如,安装 [`@expo-google-fonts/inter`](https://www.npmjs.com/package/@expo-google-fonts/inter) 包。
¥Install a Google Font package. For example, installing [`@expo-google-fonts/inter`](https://www.npmjs.com/package/@expo-google-fonts/inter) package.
### 支持的字体格式
¥Supported font formats
Expo SDK 正式支持 Android、iOS 和 Web 平台上的 OTF 和 TTF 字体格式。如果你的字体是其他字体格式,则必须设置高级配置以在你的项目中支持该格式。
¥Expo SDK officially supports OTF and TTF font formats across Android, iOS and web platforms. If your font is in another font format, you have to set up advanced configuration to support that format in your project.
### 可变字体
¥Variable fonts
可变字体(包括 OTF 和 TTF 格式的可变字体实现)并非所有平台都支持。为了获得完整的平台支持,请使用静态字体。或者,使用 [fontTools](https://fonttools.readthedocs.io/en/latest/varLib/mutator.html) 等实用程序从变量字体中提取你想要使用的特定轴配置,并将其保存为单独的字体文件。
¥Variable fonts, including variable font implementations in OTF and TTF, do not have support across all platforms. For full platform support, use static fonts. Alternatively, use a utility such as [fontTools](https://fonttools.readthedocs.io/en/latest/varLib/mutator.html) to extract the specific axis configuration you want to use from the variable font and save it as a separate font file.
### 如何在 OTF 和 TTF 之间进行选择
¥How to choose between OTF and TTF
如果你使用的字体同时具有 OTF 和 TTF 版本,则最好使用 OTF。.otf 文件比 .ttf 文件小。有时,OTF 在某些情况下也会渲染得更好一些。
¥If the font you're using has both OTF and TTF versions, prefer OTF. The **.otf** files are smaller than **.ttf** files. Sometimes, OTF also renders slightly better in certain contexts.
## 使用本地字体文件
¥Use a local font file
将文件复制到项目的 assets/fonts 目录中。
¥Copy the file into your project's **assets/fonts** directory.
> **info** assets/fonts 目录路径是 React Native 应用中放置字体文件的常见约定。如果你遵循自定义约定,你可以将这些文件放在其他地方。
>
> ¥**assets/fonts** directory path is a common convention in React Native apps to put font files. You can place these files elsewhere if you follow a custom convention.
在项目中使用本地字体文件的两种方法:
¥Two ways to use the local font file in your project:
* 使用 [`expo-font` 配置插件](/versions/latest/sdk/font/#configuration-in-app-config) 嵌入字体文件。
¥Embed the font file with [`expo-font` config plugin](/versions/latest/sdk/font/#configuration-in-app-config).
* 在运行时使用 [`useFonts`](/versions/latest/sdk/font/#usefontsmap) 钩子异步加载字体文件。
¥Loading the font file with [`useFonts`](/versions/latest/sdk/font/#usefontsmap) hook at runtime asynchronously.
### 使用 `expo-font` 配置插件
¥With `expo-font` config plugin
`expo-font` 配置插件允许在项目的原生代码中嵌入一个或多个字体文件。它支持 Android 和 iOS 的 `ttf` 和 `otf`,而 `woff` 和 `woff2` 仅在 iOS 上受支持。这是向你的应用添加字体的推荐方法,因为它具有以下优点:
¥The `expo-font` config plugin allows embedding one or more font files in your project's native code. It supports `ttf` and `otf` for both Android and iOS, and `woff` and `woff2` are supported on iOS only. This is the recommended method for adding fonts to your app due to its benefits:
* 当应用在设备上启动时,字体立即可用。
¥Fonts are available immediately when the app starts on a device.
* 应用启动时,无需额外代码即可异步加载项目中的字体。
¥No additional code required to load fonts in a project asynchronously when the app starts.
* 由于字体已打包在应用中,因此它们在安装应用的所有设备上始终可用。
¥Fonts are consistently available across all devices where the app is installed because they're bundled within the app.
但是,这种方法也有一些限制:
¥However, this method also has some limitations:
* 不适用于 Expo Go,因为此方法需要 [创建开发构建](/develop/development-builds/create-a-build/)。
¥Doesn't work with Expo Go since this method requires [creating a development build](/develop/development-builds/create-a-build/).
要在项目中嵌入字体,请按照以下步骤操作:
¥To embed a font in a project, follow the steps below:
Step 1:
After adding a custom font file in your project, install the `expo-font` library.
```sh
$ npx expo install expo-font
```
Step 2:
Add the config plugin to your [app config](/versions/latest/config/app/#plugins) file. The configuration must contain the path to the font file using [`fonts`, `android` or `ios`](/versions/latest/sdk/font/#configurable-properties) properties which take an array of one or more font definitions. The path to each font file is relative to the project's root.
The example below showcases all valid ways a font can be specified: as an array of objects that specify `fontFamily` and other properties, or an array of paths to font files.
For Android, you can specify the `fontFamily`, `weight`, and optionally `style` (defaults to `"normal"`), which will embed the fonts as native [XML resources](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml). If you provide only the font file paths in an array, the file name becomes the font family name on Android. iOS always extracts the font family name from the font file itself.
If you plan to refer to fonts using just the `fontFamily`, provide an array of font paths (see `FiraSans-MediumItalic.ttf` below) and follow our [recommendation for file naming](#how-to-determine-which-font-family-name-to-use).
If you want to refer to fonts using a combination of `fontFamily`, `weight`, and `style`, provide an array of objects (see `Inter` below).
!!!IG3!!!
Step 3:
After embedding the font with the config plugin, create a [new development build](/develop/development-builds/create-a-build/) and install it on your device or Android Emulator or iOS Simulator.
You can use the font with `` by specifying the `fontFamily` style prop. The examples below correspond to the fonts defined in the configuration above.
!!!IG4!!!
- **Android:** Copy font files to **android/app/src/main/assets/fonts**.
- **iOS:** See [Adding a Custom Font to Your App](https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app) in the Apple Developer documentation.
#### How to determine which font family name to use
- If you provide fonts as an array of file paths (as described above), on Android, the file name (without the extension) becomes the font family name. On iOS, the font family name is read from the font file itself. We recommend naming the font file same as its [PostScript name](#what-is-postscript-name-of-a-font) so the font family name is consistent on both platforms.
- If you use the object syntax, provide the "Family Name". This can be found in the Font Book app on macOS, [fontdrop.info](https://fontdrop.info/) or other programs.
Note: What is PostScript name of a font file?
---
The **PostScript name** of a font file is a unique identifier assigned to the font that follows Adobe's PostScript standard. It is used by operating systems and apps to refer to the font. It is not a font's **display name**.
For example, Inter Black font file's PostScript name is `Inter-Black`.
_Screenshot from Font Book app on macOS._
---
### With `useFonts` hook
The `useFonts` hook from `expo-font` library allows loading the font file asynchronously. This hook keeps track of the loading state and loads the font when an app is initialized.
It works with all Expo SDK versions and with Expo Go. To load a font in a project using `useFonts` hook, follow the steps below:
Step 1:
After adding a custom font file in your project, install the `expo-font` and `expo-splash-screen` libraries.
```sh
$ npx expo install expo-font expo-splash-screen
```
The [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) library provides `SplashScreen` component that you can use to prevent rendering the app until the font is loaded and ready.
Step 2:
Map the font file using the `useFonts` hook in a top level component such as the root layout (**app/layout.tsx**) file in your project:
!!!IG5!!!
Step 3:
Use the font on the `` by using `fontFamily` style prop in a React component:
!!!IG6!!!
## Use Google Fonts
Expo has first-class support for all fonts listed in [Google Fonts](https://fonts.google.com/). They are available using [`@expo-google-fonts`](https://github.com/expo/google-fonts) library. With any of the font package from this library, you can quickly integrate that font and its variants.
Two ways to use a Google Font in your project:
- Embed the installed font with [`expo-font` config plugin](/versions/latest/sdk/font/#configuration-in-appjsonappconfigjs).
- Load the installed font with [`useFonts`](/versions/latest/sdk/font/#usefontsmap) hook at runtime asynchronously.
### With `expo-font` config plugin
> **Note:** Embedding a Google Font using `expo-font` config plugin has same benefits and limitations as embedding a custom font on your own. See [using a local font file with `expo-font` config plugin](#with-expo-font-config-plugin) for more information.
Step 1:
Install the font package. For example, to use Inter Black font, install the [`@expo-google-fonts/inter`](https://www.npmjs.com/package/@expo-google-fonts/inter) package with the command below.
```sh
$ npx expo install expo-font @expo-google-fonts/inter
```
Step 2:
Add the config plugin to your [app config](/versions/latest/config/app/#plugins) file. The configuration must contain the path to the font file using [`fonts`](/versions/latest/sdk/font/#configurable-properties) property which takes an array of one or more font files. The path to the font file is defined from the font package inside the `node_modules` directory. For example, if you have a font package named `@expo-google-fonts/inter`, then the name of the file is **Inter_900Black.ttf**.
!!!IG7!!!
Step 3:
After embedding the font with the config plugin, create a [new development build](/develop/development-builds/create-a-build/) and install it on your device or Android Emulator or iOS Simulator.
On Android, you can use the font file name. For example, `Inter_900Black`. On iOS, use the font and its weight name ([PostScript name](#what-is-postscript-name-of-a-font)). The example below demonstrates how to use [`Platform`](https://rn.nodejs.cn/docs/platform-specific-code#platform-module) to select the correct font family name for each platform:
!!!IG14!!!
!!!IG8!!!
### With `useFonts` hook
> **Note:** Loading a Google Font using `useFonts` hook has same benefits and limitations as embedding a custom font on your own. See [using a local font file with `useFonts` hook](#with-usefonts-hook) for more information.
Each google Fonts package provides the `useFonts` hook to load the fonts asynchronously. This hook keeps track of the loading state and loads the font when an app is initialized. The font package also imports the font file so you don't have to explicitly import it.
Step 1:
Install the Google Fonts package, `expo-font` and `expo-splash-screen` libraries.
```sh
$ npx expo install @expo-google-fonts/inter expo-font expo-splash-screen
```
The [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) library provides `SplashScreen` component that you can use to prevent rendering the app until the font is loaded and ready.
Step 2:
After installing the font package, map the font using the `useFonts` hook in a top level component such as the root layout (**app/layout.tsx**) file in your project:
!!!IG9!!!
Step 3:
Use the font on the `` by using `fontFamily` style prop in a React component:
!!!IG10!!!
## Additional information
### Minimal example
!!!IG15!!!
### Beyond OTF and TTF
If your font is in format other than OTF or TTF, you have to [customize the Metro bundler configuration to include it as an extra asset](/guides/customizing-metro#adding-more-file-extensions-to-assetexts) for it to work. In some cases, rendering a font format that a platform doesn't support may cause your app to crash.
For reference, the following table provides the list formats that work on each native platform:
| Format | Android | iOS | Web |
| ------ | ----------- | ----------- | ----------- |
| bdf | | | |
| dfont | | | |
| eot | | | |
| fon | | | |
| otf | | | |
| ps | | | |
| svg | | | |
| ttc | | | |
| ttf | | | |
| woff | | | |
| woff2 | | | |
### Platform built-in fonts
If you don't want to use a custom font by specifying a `fontFamily`, platform's default font will be used. Each platform has a set of built in fonts. On Android, the default font is Roboto. On iOS, it's SF Pro.
A platform's default font is usually easy-to-read. However, don't be surprised when the system default font is changed to use another font that is not easy to read. In this case, use your custom font so you have precise control over what the user will see.
### Handle `@expo/vector-icons` initial load
When the icons from `@expo/vector-icons` library load for the first time, they appear as invisible icons in your app. Once they load, they're cached for all the app's subsequent usage. To avoid showing invisible icons on your app's first load, preload during the initial loading screen with [`useFonts`](/versions/latest/sdk/font/#usefontsmap). For example:
!!!IG11!!!
Now, you can use any icon from the `Ionicons` library in a React component:
!!!IG12!!!
!!!IG16!!!
### Loading a remote font directly from the web
> **warning** **If you're loading remote fonts, make sure they are being served from an origin with CORS properly configured**. If you don't do this, your remote font might not load properly on the web platform.
Loading fonts from a local asset is the safest way to load a font in your app. When including fonts as local assets, after you submit your app to the app stores, these fonts are bundled with the app download and will be available immediately. You don't have to worry about CORS or other potential issues.
However, loading a font file directly from web is done by replacing the `require('./assets/fonts/FontName.otf')` with the URL of your font as shown in the example below.
#### Using a remote font
!!!IG13!!!
## 资源
了解如何在项目中使用静态资源,包括图片、视频、声音、数据库文件和字体。
静态资源是与应用的二进制文件(原生二进制文件)打包在一起的文件。此文件类型不是包含应用代码的应用 JavaScript 打包包的一部分。常见的静态资源类型包括图片、视频、声音、SQLite 的数据库文件和字体。这些资源可以从你的项目本地提供,也可以通过网络远程提供。
¥A **static asset** is a file that is bundled with your app's binary (native binary). This file type is not part of your app's JavaScript bundle which contain your app's code. Common types of static assets include images, videos, sounds, database files for SQLite, and fonts. These assets can be served locally from your project or remotely over the network.
本指南介绍了在项目中加载和使用静态资源的不同方式,还提供了有关如何优化和最小化资源的其他信息。
¥This guide covers different ways you can load and use static assets in your project and also provides additional information on how to optimize and minify assets.
## 在本地提供资源
¥Serve an asset locally
当资源存储在项目的文件系统中时,它可以在构建时嵌入到应用二进制文件中或在运行时加载。你可以使用 `require` 或 `import` 语句将其像 JavaScript 模块一样导入。
¥When an asset is stored in your project's file system, it can be embedded in your app binary at build time or loaded at runtime. You can import it like a JavaScript module using `require` or `import` statements.
例如,要在 App.js 中渲染名为 example.png 的图片,你可以使用 `require` 从项目的 assets/images 目录导入图片并将其传递给 `` 组件:
¥For example, to render an image called **example.png** in **App.js**, you can use `require` to import the image from the project's **assets/images** directory and pass it to the `` component:
```tsx app/index.tsx
```
在上面的例子中,打包器读取导入图片的元数据并自动提供宽度和高度。欲了解更多信息,请参阅 [静态图片资源](https://rn.nodejs.cn/docs/images#static-image-resources)。
¥In the above example, the bundler reads the imported image's metadata and automatically provides the width and height. For more information, see [Static Image Resources](https://rn.nodejs.cn/docs/images#static-image-resources).
`expo-image` 和 `expo-file-system` 等库的工作方式与使用本地资源的 `` 组件类似。
¥Libraries such as `expo-image` and `expo-file-system` work similarly to the `` component with local assets.
### 资源如何在本地提供服务
¥How are assets served locally
在开发过程中,本地存储的资源通过 HTTP 提供服务。它们会在生产应用的构建时自动打包到你的应用二进制文件中,并从设备上的磁盘提供服务。
¥Locally stored assets are served over HTTP in development. They are automatically bundled into your app binary at the build time for production apps and served from disk on a device.
### 使用 `expo-asset` 配置在构建时加载资源插件
¥Load an asset at build time with `expo-asset` config plugin
> 注意:`expo-asset` 配置插件仅适用于 SDK 51 及更高版本。如果你使用的是较旧的 SDK,则可以加载 [使用 `useAssets` 钩子](#load-an-asset-at-runtime-with-useassets-hook)。
>
> ¥**Note:** The `expo-asset` config plugin is only available for SDK 51 and above. If you are using an older SDK, you can load a [using the `useAssets` hook](#load-an-asset-at-runtime-with-useassets-hook).
要在构建时加载资源,你可以使用 `expo-asset` 库中的 [配置插件](/versions/latest/sdk/asset/#example-appjson-with-config-plugin)。该插件会将资源文件嵌入到你的原生项目中。
¥To load an asset at build time, you can use the [config plugin](/versions/latest/sdk/asset/#example-appjson-with-config-plugin) from the `expo-asset` library. This plugin will embed the asset file in your native project.
Step 1:
Install the `expo-asset` library.
```sh
$ npx expo install expo-asset
```
Step 2:
Add the config plugin to your project's [app config](/versions/latest/config/app/#plugins) file. The configuration must contain the path to the asset file using [`assets`](/versions/latest/sdk/asset/#configurable-properties) property which takes an array of one or more files or directories to link to the native project.
The path to each asset file must be relative to your project's root since the app config file is located in the project's root directory.
```json app.json
{
"expo": {
"plugins": [
[
"expo-asset",
{
"assets": ["./assets/images/example.png"]
}
]
]
}
}
```
Step 3:
After embedding the asset with the config plugin, [create a new development build](/develop/development-builds/create-a-build/). Now, you can import and use the asset in your project without using a `require` or an `import` statement.
For example, the **example.png** is linked by the above config plugin. You can directly import it into your component and use its resource name as the URI. Note that when rendering assets without using `require`, you need to explicitly provide a width / height.
```tsx app/index.tsx
import { Image } from 'expo-image';
export default function HomeScreen() {
return ;
}
```
> **info** Different file formats are supported with the `expo-asset` config plugin. For more information on these formats, see [Assets API reference](/versions/latest/sdk/asset/#configurable-properties). If you don't see a file format supported by the config plugin, you can use the [`useAssets`](#load-an-asset-at-runtime-with-useassets-hook) hook to load the asset at runtime.
### Load an asset at runtime with `useAssets` hook
The `useAssets` hook from `expo-asset` library allows loading assets asynchronously. This hook downloads and stores an asset locally and after the asset is loaded, it returns a list of that asset's instances.
Step 1:
Install the `expo-asset` library.
```sh
$ npx expo install expo-asset
```
Step 2:
Import the [`useAssets`](/versions/latest/sdk/asset/#useassetsmoduleids) hook from the `expo-asset` library in your screen component:
```tsx app/index.tsx
import { useAssets } from 'expo-asset';
export default function HomeScreen() {
const [assets, error] = useAssets([
require('path/to/example-1.jpg'),
require('path/to/example-2.png'),
]);
return assets ? : null;
}
```
## Serve an asset remotely
When an asset is served remotely, it is not bundled into the app binary at build time. You can use the URL of the asset resource in your project if it is hosted remotely. For example, pass the URL to the `` component to render a remote image:
```jsx App.js
import { Image } from 'expo-image';
function App() {
return (
);
}
```
There is no guarantee about the availability of images served remotely using a web URL because an internet connection may not be available, or the asset might be removed.
Additionally, loading assets remotely also requires you to provide an asset's metadata. In the above example, since the bundler cannot retrieve the image's width and height, those values are passed explicitly to the `` component. If you don't, the image will default to 0px by 0px.
## Additional information
### Manual optimization methods
#### Images
You can compress images using the following:
- [`guetzli`](https://github.com/google/guetzli)
- [`pngcrush`](https://pmt.sourceforge.io/pngcrush/)
- [`optipng`](http://optipng.sourceforge.net/)
Some image optimizers are lossless. They re-encode your image to be smaller without any change or loss in the pixels displayed. When you need each pixel to be untouched from the original image, a lossless optimizer and a lossless image format like PNG are a good choice.
Other image optimizers are lossy. The optimized image differs from the original image. Often, lossy optimizers are more efficient because they discard visual information that reduces file size while making the image look nearly identical to humans. Tools like `imagemagick` can use comparison algorithms like [SSIM](https://en.wikipedia.org/wiki/Structural_similarity) to show how similar two images look. It's quite common for an optimized image that is over 95% similar to the original image to be far less than 95% of the original file size.
#### Other assets
For assets like GIFs or videos, or non-code and non-image assets, it's up to you to optimize and minify those assets.
>
注意:GIF 是一种非常低效的格式。现代视频编解码器可以生成更小、质量更好的文件。
¥**Note**: GIFs are a very inefficient format. Modern video codecs can produce significantly smaller file sizes with better quality.
### 字体
¥Fonts
有关如何向应用添加自定义字体的更多信息,请参阅 [添加自定义字体](/develop/user-interface/fonts/#add-a-custom-font)。
¥See [Add a custom font](/develop/user-interface/fonts/#add-a-custom-font) for more information on how to add a custom font to your app.
## 颜色主题
了解如何在你的应用中支持浅色和夜间模式。
应用支持浅色和深色配色方案很常见。以下是 Expo 项目中支持两种模式的示例:
¥It's common for apps to support light and dark color schemes. Here is an example of how supporting both modes looks in an Expo project:
## 配置
¥Configuration
> **info** 对于 Android 和 iOS 项目,需要进行其他配置才能支持在明夜间模式之间切换。对于 Web,无需额外配置。
>
> ¥For Android and iOS projects, additional configuration is required to support switching between light and dark mode. For web, no additional configuration is required.
要配置支持的外观样式,你可以在项目的 [应用配置](/versions/latest/config/app) 中使用 [`userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle) 属性。默认情况下,当你使用 [默认模板](/get-started/create-a-project/) 创建新项目时,此属性默认设置为 `automatic`。
¥To configure supported appearance styles, you can use the [`userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle) property in your project's [app config](/versions/latest/config/app). By default, this property is set to `automatic` when you create a new project with the [default template](/get-started/create-a-project/).
这是一个配置示例:
¥Here is an example configuration:
```json app.json
{
"expo": {
"userInterfaceStyle": "automatic"
}
}
```
你还可以通过将 [`android.userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle-2) 或 [`ios.userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle-1) 设置为首选值来为特定平台配置 `userInterfaceStyle` 属性。
¥You can also configure `userInterfaceStyle` property for a specific platforms by setting either [`android.userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle-2) or [`ios.userInterfaceStyle`](/versions/latest/config/app/#userinterfacestyle-1) to the preferred value.
> **info** 如果此属性不存在,应用将默认为 `light` 样式。
>
> ¥The app will default to the `light` style if this property is absent.
创建开发版本时,你必须安装 [`expo-system-ui`](/versions/latest/sdk/system-ui/#installation) 以支持 Android 的外观样式。否则,`userInterfaceStyle` 属性将被忽略。
¥When you are creating a development build, you have to install [`expo-system-ui`](/versions/latest/sdk/system-ui/#installation) to support the appearance styles for Android. Otherwise, the `userInterfaceStyle` property is ignored.
```sh
$ npx expo install expo-system-ui
```
如果项目配置错误且未安装 `expo-system-ui`,则终端中将显示以下警告:
¥If the project is misconfigured and doesn't have `expo-system-ui` installed, the following warning will be shown in the terminal:
```sh
» android: userInterfaceStyle: Install expo-system-ui in your project to enable this feature.
```
还可以使用以下命令检查项目是否配置错误:
¥You can also use the following command to check if the project is misconfigured:
```sh
$ npx expo config --type introspect
```
Note: Using bare React Native app?
---
#### Android
Ensure that the `uiMode` flag is present on your `MainActivity` (and any other activities where this behavior is desired) in **AndroidManifest.xml**:
```xml
```
Implement the `onConfigurationChanged` method in **MainActivity.java**:
```java
import android.content.Intent;
import android.content.res.Configuration;
public class MainActivity extends ReactActivity {
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
sendBroadcast(intent);
}
}
```
#### iOS
You can configure supported styles with the [`UIUserInterfaceStyle`](https://developer.apple.com/documentation/bundleresources/information_property_list/uiuserinterfacestyle) key in your app **Info.plist**. Use `Automatic` to support both light and dark modes.
---
### Supported appearance styles
The `userInterfaceStyle` property supports the following values:
- `automatic`: Follow system appearance settings and notify about any change the user makes.
- `light`: Restrict the app to support light theme only.
- `dark`: Restrict the app to support dark theme only.
## Detect the color scheme
To detect the color scheme in your project, use `Appearance` or `useColorScheme` from `react-native`:
```tsx app/index.tsx
import { Appearance, useColorScheme } from 'react-native';
```
Then, you can use `useColorScheme()` hook as shown below:
```tsx app/index.tsx
function MyComponent() {
let colorScheme = useColorScheme();
if (colorScheme === 'dark') {
// render some dark thing
} else {
// render some light thing
}
}
```
In some cases, you will find it helpful to get the current color scheme imperatively with [`Appearance.getColorScheme()` or listen to changes with `Appearance.addChangeListener()`](https://rn.nodejs.cn/docs/appearance).
## Additional information
### Minimal example
#### useColorScheme example
```tsx
import { StatusBar } from 'expo-status-bar';
export default function App() {
const colorScheme = useColorScheme();
const themeTextStyle = colorScheme === 'light' ? styles.lightThemeText : styles.darkThemeText;
const themeContainerStyle =
colorScheme === 'light' ? styles.lightContainer : styles.darkContainer;
return (
Color scheme: {colorScheme}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 20,
},
lightContainer: {
backgroundColor: '#d0d0c0',
},
darkContainer: {
backgroundColor: '#242c40',
},
lightThemeText: {
color: '#242c40',
},
darkThemeText: {
color: '#d0d0c0',
},
});
```
### Tips
While you are developing your project, you can change your simulator's or device's appearance by using the following shortcuts:
- If using an Android Emulator, you can run `adb shell "cmd uimode night yes"` to enable dark mode, and `adb shell "cmd uimode night no"` to disable dark mode.
- If using a physical Android device or an Android Emulator, you can toggle the system dark mode setting in the device's settings.
- If working with an iOS emulator locally, you can use the Cmd ⌘ + Shift + a
用于在亮夜间模式之间切换的快捷方式。
¥shortcut to toggle between light and dark modes.
## 动画
了解如何集成 React Native 动画并将其用于你的 Expo 项目。
动画是增强和提供更好用户体验的好方法。在你的 Expo 项目中,你可以使用 React Native 中的 [动画 API](https://rn.nodejs.cn/docs/next/animations)。但是,如果你想使用性能更好的更高级动画,可以使用 [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) 库。它提供了一个 API,可以简化创建流畅、强大且可维护的动画的过程。
¥Animations are a great way to enhance and provide a better user experience. In your Expo projects, you can use the [Animated API](https://rn.nodejs.cn/docs/next/animations) from React Native. However, if you want to use more advanced animations with better performance, you can use the [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) library. It provides an API that simplifies the process of creating smooth, powerful, and maintainable animations.
## 安装
¥Installation
如果你使用 [这篇博文](/get-started/create-a-project/) 创建了项目,则可以跳过安装 `react-native-reanimated`。此库已安装。否则,请通过运行以下命令进行安装:
¥You can skip installing `react-native-reanimated` if you have created a project using [the default template](/get-started/create-a-project/). This library is already installed. Otherwise, install it by running the following command:
```sh
$ npx expo install react-native-reanimated
```
## 用法
¥Usage
### 最小的例子
¥Minimal example
下面的例子展示了如何使用 `react-native-reanimated` 库创建一个简单的动画。有关 API 和高级用法的更多信息,请参阅 [`react-native-reanimated` 文档](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/your-first-animation)。
¥The following example shows how to use the `react-native-reanimated` library to create a simple animation. For more information on the API and advanced usage, see [`react-native-reanimated` documentation](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/your-first-animation).
#### Using react-native-reanimated
```tsx
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
Easing,
} from 'react-native-reanimated';
import { View, Button, StyleSheet } from 'react-native';
export default function AnimatedStyleUpdateExample() {
const randomWidth = useSharedValue(10);
const config = {
duration: 500,
easing: Easing.bezier(0.5, 0.01, 0, 1),
};
const style = useAnimatedStyle(() => {
return {
width: withTiming(randomWidth.value, config),
};
});
return (
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 100,
height: 80,
backgroundColor: 'black',
margin: 30,
},
});
```
## 其他动画库
¥Other animation libraries
你可以在 Expo 项目中使用其他动画包,例如 [莫蒂](https://moti.fyi/)。它适用于 Android、iOS 和 Web。
¥You can use other animation packages such as [Moti](https://moti.fyi/) in your Expo project. It works on Android, iOS, and web.
## 存储数据
了解可用于在 Expo 项目中存储数据的不同库。
存储数据对于移动应用中实现的功能至关重要。根据你要存储的数据类型和应用的安全要求,可以使用不同的方法在 Expo 项目中保存数据。此页面列出了各种库,可帮助你确定哪种解决方案最适合你的项目。
¥Storing data can be essential to the features implemented in your mobile app. There are different ways to save data in your Expo project depending on the type of data you want to store and the security requirements of your app. This page lists a variety of libraries to help you decide which solution is best for your project.
## Expo 安全存储
¥Expo SecureStore
`expo-secure-store` 提供了一种在设备本地加密和安全存储键值对的方法。
¥`expo-secure-store` provides a way to encrypt and securely store key-value pairs locally on the device.
## Expo 文件系统
¥Expo FileSystem
`expo-file-system` 提供对设备本地存储的文件系统的访问。在 Expo Go 中,每个项目都有一个单独的文件系统,并且无法访问其他 Expo 项目的文件。但是,它可以将其他项目共享的内容保存到本地文件系统,并与其他项目共享本地文件。它还能够从网络 URL 上传和下载文件。
¥`expo-file-system` provides access to a file system stored locally on the device. Within Expo Go, each project has a separate file system and no access to other Expo projects' files. However, it can save content shared by other projects to the local filesystem and share local files with other projects. It is also capable of uploading and downloading files from network URLs.
## Expo SQLite
`expo-sqlite` 包使你的应用可以访问可通过类似 WebSQL 的 API 进行查询的数据库。数据库在应用重新启动后保留。你可以使用它来导入现有数据库、打开数据库、创建表、插入项目、查询和显示结果以及使用准备好的语句。
¥`expo-sqlite` package gives your app access to a database that can be queried through a WebSQL-like API. The database is persisted across restarts of your app. You can use it for importing an existing database, opening databases, creating tables, inserting items, querying and displaying results, and using prepared statements.
## 异步存储
¥Async Storage
[异步存储](https://react-native-async-storage.github.io/async-storage/) 是 React Native 应用的异步、未加密、持久键值存储。它有一个简单的 API,是存储少量数据的不错选择。对于存储不需要加密的数据(例如用户首选项或应用状态)来说,它也是一个不错的选择。
¥[Async Storage](https://react-native-async-storage.github.io/async-storage/) is an asynchronous, unencrypted, persistent key-value storage for React Native apps. It has a simple API and is a good choice for storing small amounts of data. It is also a good choice for storing data that does not need encryption, such as user preferences or app state.
## 其他库
¥Other libraries
还有其他库可用于存储不同目的的数据。例如,你的项目可能不需要加密,或者正在寻找类似于异步存储的更快的解决方案。
¥There are other libraries available for storing data for different purposes. For example, you might not need encryption in your project or are looking for a faster solution similar to Async Storage.
我们建议查看 [React Native 获取库列表](https://reactnative.directory/?search=storage) 来帮助你存储项目数据。
¥We recommend checking out [React Native for a list of libraries](https://reactnative.directory/?search=storage) to help you store your project's data.
## 下一步
有用资源列表,可帮助你了解有关在应用中实现导航和 UI 的更多信息。
# 开发构建
## 开发构建简介
为什么使用开发版本以及如何开始使用。
“开发构建”是指包含 [`expo-dev-client`](/versions/latest/sdk/dev-client/) 库的应用的 "调试" 构建版本。此库增强了内置的 React Native 开发工具,并提供了额外的功能,例如支持检查网络请求和 "launcher" UI,可让你在不同的开发服务器(例如在你机器或队友机器上运行的服务器之间切换)和应用部署(例如使用 EAS 更新发布的更新)。
¥**Development build** is the term that we use for a "Debug" build of an app that includes the [`expo-dev-client`](/versions/latest/sdk/dev-client/) library. This library augments the built-in React Native development tooling with additional capabilities, such as support for inspecting network requests and a "launcher" UI that lets you switch between different development servers (such as between a server running on your machine or a teammate's machine) and deployments of your app (such as published updates with EAS Update).
Note: Difference between Expo Go and development builds
---
When you create your first React Native project with `npx create-expo-app` and ran it with `npx expo start`, you most likely start off using the [Expo Go](https://expo.dev/go) app for development. Expo Go is a native app that the Expo team built and submitted to the [Google Play Store](https://play.google.com/store/apps/details?id=host.exp.exponent) and [Apple App Store](https://apps.apple.com/us/app/expo-go/id982107779) so you can get coding quickly. It is a sandbox app with a number of native libraries included within (see the [dependencies list](https://github.com/expo/expo/blob/main/apps/expo-go/package.json#L23)). This means that developers may update their app's JavaScript code on their local machine and see the changes on Expo Go.
A React Native app consists of two parts: the **native app (Expo Go)** and the **JavaScript bundle (`npx expo start`)**. It is immutable and when you're using the Expo Go app for development, you can only rely on the native code and tools that exist in Expo Go. The only way to get around it is to build your native app yourself instead of using Expo's pre-packaged sandbox. This is exactly what a **Development Build is, your own version of Expo Go**, where you are free to use any native libraries and change any native config.
---
Note: Native app and JavaScript bundle
---
The **native app (Expo Go)** is immutable once installed. Native build tools are required to create this bundle, and it needs to be signed to be installable on real devices. To add a new library with native code or change metadata that is shipped with the app (for example app name, icon, splash screen) the app needs to be rebuilt and re-installed on the device.
The **JavaScript bundle (`npx expo start`)** is where your app's UI code and business logic are. In production apps, there is one **main.js** bundle that is shipped with the app itself. In development, this JS bundle is live reloaded from your local machine. The main role of React Native is to provide a way for the JavaScript code to access the native APIs (Image, Camera, Notifications, and more). However, only APIs and libraries that were bundled in the **native app** can be used.
---
Video Tutorial: [Expo Go & Development Builds: which should you use?](https://www.youtube.com/watch?v=FdjczjkwQKE)
## Why use a development build (a.k.a what _can't_ you do in Expo Go and why)
Expo Go is the perfect tool for learning, prototyping, and experimenting, but most production apps will convert to using development builds sooner rather than later. It helps to know exactly what is _impossible_ in Expo Go and _why_, so you can make an informed decision on when and why to make this move.
Note: Use libraries with native code that aren't in Expo Go
---
Consider [`react-native-webview`](/versions/latest/sdk/webview/) as an example, a library that contains native code, but [is included in Expo Go](https://github.com/expo/expo/blob/main/apps/expo-go/package.json#L23). When you run `npx expo install react-native-webview` command in your project, it will install the library in your **node_modules** directory, which includes both the JS code and the native code. But the JS bundle you are building _only_ uses the JS code. Then, your JS bundle gets uploaded to Expo Go, and it interacts with the native code that was already bundled with the app.
Instead, when you try to use a library that is not included, for example, [`react-native-firebase`](/guides/using-firebase/#using-react-native-firebase), then you can use the JS code and hot reload the new bundle into Expo Go but it will immediately error because the JS code tries to call the native code from the React Native Firebase package that does not exist in Expo Go. There is no way to get the native code into the Expo Go app unless it was already included in the bundle that was uploaded to the app stores.
---
Note: Test changes in app icon, name, splash screen
---
If you're developing your app in Expo Go only, you can build a store version that will use your provided values and images; it just won't be possible to test it in Expo Go.
These native assets are shipped with the native bundle and are immutable once the app is installed. The Expo Go app does show a splash screen, which is your app icon on a solid color background. This is a dev-only emulation to view how the splash screen will probably look. However, it is limited, for example, you cannot test `SplashScreen.setOptions` to animate the splash screen.
---
Note: Remote push notifications
---
While [in-app notifications](/versions/latest/sdk/notifications/) are available in Expo Go, remote push notifications (that is, sending a push notification from a server to the app) are not. This is because a push notification service should be tied to your own push notification certificates, and while it is possible to make it work in Expo Go, it often causes confusion for production builds. It is recommended to test remote push notifications in development builds so you can ensure parity in behavior between development and production.
---
Note: Implementing App/Universal links
---
Both [Android App Links](/linking/android-app-links/) and [iOS Universal Links](/linking/ios-universal-links/) require a two-way association between the native app and the website. In particular, it requires the native app to include the linked website's URL. This is impossible with Expo Go due to the aforementioned native code immutability.
---
Note: Open projects using older SDKs (iOS device only)
---
Expo Go can only support one SDK version at a time. When a new SDK version is released, Expo Go is updated to support the newer version, and this will be the only version of Expo Go available to install from the stores.
If you're developing on an Android Device, Android Emulator, or iOS Simulator, a compatible version of Expo Go can be [downloaded and installed](https://expo.dev/go). The only platform where this is impossible is iPhone devices because Apple does not support side-loading older versions of apps.
---
## 从 Expo 转换到开发版本
如何迁移你的 Expo Go 项目以使用开发版本。
要从 Expo Go 迁移到开发版本,你需要按照以下步骤操作:
¥To migrate from Expo Go to a development build, you'll need to follow the steps below:
Step 1:
## Install the `expo-dev-client`
The Expo Dev Client library includes the launcher UI (shown in the screenshots below), dev menu, extensions to test over-the-air updates, and more. The Expo Go app has the dev menu built in, and that's why you need to install it separately for a development build.
```sh
$ npx expo install expo-dev-client
```
> We recommend using the `expo-dev-client` for the best development experience, but it is possible to use development builds without installing this library. If not using the dev client, in [Step 3](/develop/development-builds/expo-go-to-dev-build/#start-the-dev-client), start the bundler with `--dev-client`. Otherwise, it will default to opening in Expo Go.
Step 2:
## Build your native app
With Expo Go, you only needed to build the JavaScript bundle, but with development builds you also need to compile the native app. With Expo, there are two parts to building your native app:
1. Generate the native **android** and/or **ios** directories ([read more](/develop/development-builds/expo-go-to-dev-build/#cng-and-prebuild) on when and how you'll need to do this)
2. Use native build tools to compile the native app(s)
Once you've built your native app, you won't need to build it again unless you add or update a library with native code, or change any native code or configuration, such as the app name.
> After generating your **android** and/or **ios** directories, make sure you add them to **.gitignore** instead of checking them into Git. This ensures you can always regenerate the code locally or on CI when needed and never have to edit native code manually.
### Option 1: Build on your local machine
To build a native app on your local machine, follow the setup your environment guides for [Android](/workflow/android-studio-emulator/) and [iOS](/workflow/ios-simulator/) platforms. This involves setting up and configuring native build tools like Android Studio for Android and Xcode for iOS.
Once you have everything set up, run the following command:
For Build for Android:
```sh
$ npx expo run:android
```
For Build for iOS:
```sh
$ npx expo run:ios
```
By default, this will build and install the app on an Android Emulator/iOS Simulator. If you need to run the build on your phone, plug it into your computer (on Android, select trust device and allow USB debugging if prompted, and on iOS, enable [developer mode](/get-started/set-up-your-environment/?mode=development-build&buildEnv=local&platform=ios&device=physical#plug-in-your-device-via-usb-and-enable-developer-mode)) and run the above commands with the `--device` flag.
### Option 2: Build on EAS
Building on EAS servers is useful when:
- You can't or don't want to set up your local development environment
- You want to build an iOS app but don't own a Mac
- You want to share the development builds with your team
Step 3:
## Start the bundler
After building locally, `npx expo run:android|ios` will start the bundler automatically. But if you closed the bundler or are working on a dev client you built earlier, (re)start the Metro bundler with:
```sh
$ npx expo start
```
When your project has `expo-dev-client` installed, the bundler will print out **Using development build**, and the QR code it shows will link into the development build you created instead of Expo Go.
## Prebuild
[**Prebuild** ](/workflow/continuous-native-generation/#prebuild) is a concept unique to Expo projects. It refers to the process of generating the **android** and **ios** directories based on your local configuration and properties.
### When should you run prebuild
You will need to run prebuild locally if you are building via `npx expo run:android|ios`, and change any native dependencies or configuration, such as:
- Installing or updating a library containing native code
- Changing [app config](/workflow/configuration/)(`app.json`)
- Upgrading your Expo SDK version
In these cases, you'll want to rebuild the native directories with:
```sh
$ npx expo prebuild --clean
```
Then, rebuild your app with the updated native code, with:
For Build for Android:
```sh
$ npx expo run:android
```
For Build for iOS:
```sh
$ npx expo run:ios
```
### 何时不需要运行预构建
¥When you don't need to run prebuild
如果未找到现有的原生文件夹,所有 Expo 构建工具(`npx expo run:android|ios` 和 `eas build`)都将自动预构建。这意味着你首次运行 `npx expo run:android|ios` 或 `eas build` 时无需手动运行预构建。
¥All Expo build tools (`npx expo run:android|ios` and `eas build`) will **prebuild** automatically if no existing native folders are found. This means that there is no need to run prebuild manually when you're running `npx expo run:android|ios` for the first time or `eas build`.
## 在 EAS 上创建开发版本
了解如何为项目创建开发版本。
使用 `npx create-expo-app` 创建新的 Expo 应用时,你将从一个项目开始,在该项目中更新本地计算机上的 JavaScript 代码,并在 Expo Go 应用中查看更改。开发版本本质上是你自己的 Expo Go 版本,你可以自由使用任何原生库并更改任何原生配置。在本指南中,你将学习如何将运行在 Expo Go 上的项目转换为开发版本,这将使你的应用的原生端完全可定制。
¥When you create a new Expo app with `npx create-expo-app`, you start off with a project where you update the JavaScript code on your local machine and view the changes in the Expo Go app. A **development build** is essentially **your own version of Expo Go** where you are free to use any native libraries and change any native config. In this guide, you will learn how to convert your project that runs on Expo Go into a development build, which will make the native side of your app fully customizable.
Video Tutorial: [How to create a development build](https://www.youtube.com/watch?v=uQCE9zl3dXU)
## 先决条件
¥Prerequisites
这些说明假设你已经有一个在 Expo Go 上运行的 Expo 项目。
¥The instructions assume you already have an existing Expo project that runs on Expo Go.
构建原生应用的要求取决于你使用的平台、构建的目标平台以及你是在 EAS 上构建还是在本地机器上构建。
¥The requirements for building the native app depend on which platform you are using, which platform you are building for, and whether you want to build on EAS or on your local machine.
Note: Build on EAS
---
This is the easiest way to build your native app, as it requires no native build tools on your side. The builds happen on the EAS servers, which makes it possible to trigger iOS builds from non-macOS platforms.
| | Android | iOS Simulator | iPhone device |
| ----------- | ---------- | ------------- | --------------- |
| **macOS** | | | (\*) |
| **Windows** | | | (\*) |
| **Linux** | | | (\*) |
(\*) All builds that run on an iPhone device require a paid [Apple Developer](https://developer.apple.com) account for build signing.
---
Note: Build locally using the EAS CLI
---
Any EAS CLI command can be built on your local machine with the `--local` flag. This requires your local [development environment](https://rn.nodejs.cn/docs/set-up-your-environment?os=macos&platform=ios) to be set up with native build tools. Read more about [local app development](/build-reference/local-builds/).
| | Android | iOS Simulator | iPhone device |
| ----------- | ----------------- | ------------- | --------------- |
| **macOS** | | | (\*) |
| **Windows** | (\*\*) | | |
| **Linux** | | | |
(\*) All builds that run on an iPhone device require a paid [Apple Developer](https://developer.apple.com) account for build signing.
(\*\*) No first-class support, but possible with [WSL](http://expo.fyi/wsl.md).
---
Note: Build locally without EAS
---
To build locally without EAS requires your local [development environment](https://rn.nodejs.cn/docs/set-up-your-environment?os=macos&platform=ios) to be set up with native build tools. This is the only way to test your iOS build on an iPhone device without a paid Apple Developer Account (only possible on macOS). Read more about [local app compilation](/guides/local-app-development/#local-app-compilation) and see the [Expo Go to Development Build](/develop/development-builds/expo-go-to-dev-build/) guide.
| | Android | iOS Simulator | iPhone device |
| ----------- | ---------- | ------------- | ------------- |
| **macOS** | | | |
| **Windows** | | | |
| **Linux** | | | |
---
## Get started
For detailed, step-by-step instructions, see our [EAS Tutorial](/tutorial/eas/introduction). Available also as a [tutorial series](https://www.youtube.com/playlist?list=PLsXDmrmFV_AS14tZCBin6m9NIS_VCUKe2) on YouTube.
Step 1:
### Install expo-dev-client
```sh
$ npx expo install expo-dev-client
```
Note: Are you using this library in a existing (bare) React Native apps?
---
Apps that don't use [Continuous Native Generation](/workflow/continuous-native-generation/) or are created with `npx react-native`, require further configuration after installing this library. See steps 1 and 2 from [Install `expo-dev-client` in an existing React Native app](/bare/install-dev-builds-in-bare/).
---
Step 2:
### Build the native app (Android)
Sign up for an [Expo](https://expo.dev/signup) account, if you haven't already.
The [EAS CLI](/build/setup/#install-the-latest-eas-cli) installed and logged in.
```sh
$ npm install -g eas-cli && eas login
```
An [Android Emulator](/workflow/android-studio-emulator/) is optional if you want to test your
app on an emulator.
```sh
$ eas build --platform android --profile development
```
Read more about [Android builds on EAS](/tutorial/eas/android-development-build).
Step 2:
### Build the native app (iOS Simulator)
Sign up for an [Expo](https://expo.dev/signup) account, if you haven't already.
The [EAS CLI](/build/setup/#install-the-latest-eas-cli) installed and logged in.
```sh
$ npm install -g eas-cli && eas login
```
iOS Simulators are available only on macOS. Make sure you have the [iOS
Simulator](/workflow/ios-simulator/) installed.
Edit `development` profile in **eas.json** and set the [`simulator`](/eas/json/#simulator) option to `true` (you have to create a separate profile for simulator builds if you also want to create iOS device builds for this project).
```json eas.json
{
"build": {
"development": {
"ios": {
"simulator": true
}
}
}
}
```
```sh
$ eas build --platform ios --profile development
```
iOS Simulator builds can only be installed on simulators and not on real devices.
Read more about [iOS Simulator builds on EAS](/tutorial/eas/ios-development-build-for-simulators/).
Step 2:
### Build the native app (iOS device)
Sign up for an [Expo](https://expo.dev/signup) account, if you haven't already.
The [EAS CLI](/build/setup/#install-the-latest-eas-cli) installed and logged in.
```sh
$ npm install -g eas-cli && eas login
```
A paid [Apple Developer](https://developer.apple.com/) account for creating [signing
credentials](/app-signing/managed-credentials/#generating-app-signing-credentials) so the app
could be installed on an iOS device.
```sh
$ eas build --platform ios --profile development
```
iOS device builds can only be installed on iPhone devices and not on iOS Simulators.
Read more about [iOS device builds on EAS](/tutorial/eas/ios-development-build-for-devices/).
Step 3:
### Install the app
You'll need to install the native app on your device, emulator, or simulator.
#### When building on EAS
If you create your development build on EAS, the CLI will prompt you to install the app after the build is finished. You can also install previous builds from the [expo.dev](https://expo.dev/) dashboard or using [Expo Orbit](https://expo.dev/orbit).
#### When building locally using EAS CLI
When building locally the output of the build will be an archive. You may drag and drop this on your Android Emulator/iOS Simulator to install it, or use [Expo Orbit](https://expo.dev/orbit) to install a build from your local machine.
Step 4:
### Start the bundler
The development client built in **step 2** is the native side of your app (basically your own version of Expo Go). To continue developing, you'll also want to start the JavaScript bundler.
Depending on how you built the app, this may already be running, but if you close the process for any reason, there is no need to rebuild your development client. Simply restart the JavaScript bundler with:
```sh
$ npx expo start
```
This is the same command you would have used with Expo Go. It detects whether your project has `expo-dev-client` installed, in which case it will default to targeting your development build instead of Expo Go.
## 视频演练
¥Video walkthroughs
## 使用开发版本
了解如何使用项目的开发版本。
通常,从头开始创建新的原生构建需要很长时间,以至于你可能会想切换任务并失去注意力。但是,在你的设备或模拟器/模拟器上安装了开发版本后,你无需等待原生构建过程,直到你 [更改底层原生代码](#rebuild-a-development-build) 为你的应用提供支持。
¥Usually, creating a new native build from scratch takes long enough that you'll be tempted to switch tasks and lose your focus. However, with the development build installed on your device or an emulator/simulator, you won't have to wait for the native build process until you [change the underlying native code](#rebuild-a-development-build) that powers your app.
## 添加错误处理
¥Add error handling
在 App.{js|tsx} 或 [**app/_layout.tsx**](/router/basics/layout/#root-layout) 的顶部导入 `expo-dev-client`,以添加超出 React Native 默认提供的某些错误的额外上下文。特别是,`expo-dev-client` 将有助于检测与 JavaScript 和原生代码不匹配相关的情况,例如当缺少原生模块时,你应该进行新的开发构建。
¥Import `expo-dev-client` at the top of the **App.{js|tsx}** or [**app/_layout.tsx**](/router/basics/layout/#root-layout) to add additional context for certain errors beyond what is provided by default in React Native. In particular, `expo-dev-client` will help detect situations related to a mismatch between your JavaScript and native code, such as when a native module is missing and you should make a new development build.
```js App.js
import 'expo-dev-client';
```
> 这只会影响你进行此更改的应用。如果你想从单个开发应用加载多个项目,则需要将此导入语句添加到每个项目中。
>
> ¥This will only affect the application in which you make this change. If you want to load multiple projects from a single development app, you'll need to add this import statement to each project.
## 启动开发服务器
¥Start the development server
要开始开发,请运行以下命令来启动开发服务器:
¥To start developing, run the following command to start the development server:
```sh
$ npx expo start
```
要在开发客户端中打开项目:
¥To open the project inside your development client:
* 按 a 或 i 键在 Android 模拟器或 iOS 模拟器上打开你的项目。
¥Press a or i keys to open your project on an Android Emulator or an iOS Simulator.
* 在物理设备上,通过系统摄像头或 QR 码阅读器扫描 QR 码,以在设备上打开项目。
¥On a physical device, scan the QR code from your system's camera or a QR code reader to open the project on your device.
## 启动器屏幕
¥The launcher screen
如果你从设备的主屏幕启动开发版本,你将看到启动器屏幕,如下所示:
¥If you launch the development build from your device's Home screen, you will see your launcher screen, which looks similar to the following:
如果在你的本地网络上检测到打包程序,或者你已在 Expo CLI 和开发版本中登录 Expo 账户,则可以直接从此屏幕连接到它。否则,你可以通过扫描 Expo CLI 显示的二维码进行连接。
¥If a bundler is detected on your local network, or if you have signed in to an Expo account in both Expo CLI and your development build, you can connect to it directly from this screen. Otherwise, you can connect by scanning the QR code displayed by the Expo CLI.
## 重建开发版本
¥Rebuild a development build
如果你向项目添加包含原生代码 API 的库(例如 [`expo-secure-store`](/versions/latest/sdk/securestore/)),则必须重新构建开发客户端。这是因为当将库作为项目的依赖安装时,库的原生代码不会自动包含在开发客户端中。
¥If you add a library to your project that contains native code APIs, for example, [`expo-secure-store`](/versions/latest/sdk/securestore/), you will have to rebuild the development client. This is because the native code of the library is not included in the development client automatically when installing the library as a dependency on your project.
## 调试开发版本
¥Debug a development build
当你需要时,你可以通过在 Expo CLI 中按 Cmd ⌘ + d 或 Ctrl + d 或摇动手机或平板电脑来访问菜单。在这里,你将能够访问开发版本的所有功能、你需要的任何调试功能,或者切换到应用的不同版本。
¥When you need to, you can access the menu by pressing Cmd ⌘ + d or Ctrl + d in Expo CLI or by shaking your phone or tablet. Here you'll be able to access all of the functions of your development build, any debugging functionality you need, or switch to a different version of your app.
有关详细信息,请参阅 [调试](/debugging/runtime-issues/) 指南。
¥See [Debugging](/debugging/runtime-issues/) guide for more information.
## 与你的团队共享开发构建
了解如何安装并与你的团队共享开发或在多个设备上运行它。
Android 和 iOS 都提供了直接在设备上安装应用版本的方法。这使你可以完全控制在设备上放置特定版本,从而使你能够快速迭代并同时让应用的多个版本可供审查。你还可以与你的团队共享它或在多个测试设备上运行它。
¥Android and iOS both offer ways to install a build of your application directly on devices. This gives you full control of putting specific builds on devices, allowing you to iterate quickly and have multiple builds of your application available for review at the same time. You can also share it with your team or run it on multiple test devices.
## 共享网址
¥Share the URL
当开发构建准备就绪时,将为你的构建生成一个可共享的 URL,其中包含有关如何启动和运行它的说明。你可以与队友一起使用此 URL 或将其发送到你的测试设备以安装构建版本。生成的 URL 对于你的项目构建是唯一的。
¥When a development build is ready, a shareable URL is generated for your build with instructions on how to get it up and running. You can use this URL with a teammate or send it to your test device to install the build. The URL generated is unique to the build for your project.
> 如果你在创建开发版本后注册任何新的 iOS 设备,则需要创建新的开发版本才能将其安装在这些设备上。欲了解更多信息,请参阅 [内部分配](/build/internal-distribution/)。
>
> ¥If you register any new iOS devices after creating a development build, you'll need to create a new development build to install it on those devices. For more information, see [internal distribution](/build/internal-distribution/).
### 使用 EAS 仪表盘
¥Use the EAS dashboard
你还可以将你的队友引导至 EAS 仪表板中的构建页面。从那里,他们可以直接在其设备上下载构建工件。
¥You can also direct your teammate to the build page in the EAS dashboard. From there, they can download the build artifact directly on their device.
### 使用 EAS CLI
¥Use EAS CLI
你的队友也可以使用 EAS CLI 下载和安装开发版本。他们必须确保它们是从与开发版本相关联的 Expo 账户签名的,然后才能运行以下命令:
¥Your teammate can also download and install the development build using EAS CLI. They have to make sure that they are signed from the Expo account associated with the development build and then can run the following command:
```sh
$ eas build:run --profile development
```
如果开发版本的配置文件名称与 `development` 不同,则将其与 `--profile` 一起使用。
¥If the profile name for the development build is different from `development`, use it instead with `--profile`.
### 仅限 iOS 的说明
¥iOS-only instructions
> 如果你运行的是 iOS 16 或更高版本并且尚未打开开发者模式,则需要 [启用它](/guides/ios-developer-mode) 才能运行构建。(如果你使用的是企业配置,则这不适用。)
>
> ¥If you're running iOS 16 or above and haven't yet turned on Developer Mode, you'll need to [enable it](/guides/ios-developer-mode) before you can run your build. (This doesn't apply if you're using enterprise provisioning.)
你可以使用 `eas build:resign` 将适用于 iOS 的现有 .ipa 共同设计为新的临时配置文件。这有助于减少与团队分发的时间。例如,如果你想将新的测试设备添加到现有版本中,则可以使用此命令更新配置文件以包含该设备,而无需从头开始重建整个应用。欲了解更多信息,请参阅 [重新签署新凭证](/app-signing/app-credentials/#re-signing-new-credentials)。
¥You can use `eas build:resign` to codesign an existing **.ipa** for iOS to a new ad hoc provisioning profile. This helps reduce time when distributing with your team. For example, if you want to add a new test device to an existing build, you can use this command to update the provisioning profile to include the device without rebuilding the entire app from scratch. For more information, see [Re-signing new credentials](/app-signing/app-credentials/#re-signing-new-credentials).
## 下一步
¥Next steps
## 工具、工作流程和扩展
详细了解使用开发版本时可用的不同工具、工作流程和扩展。
开发构建允许你快速迭代。但是,你可以扩展开发构建的功能,以便在团队工作时提供更好的开发者体验,或者自定义构建以满足你的需求。
¥Development builds allow you to iterate quickly. However, you can extend the capabilities of your development build to provide a better developer experience when working in teams or customize the build to suit your needs.
## 工具
¥Tools
### 通道 URL
¥Tunnel URLs
有时,限制性网络条件导致难以连接到开发服务器。`npx expo start` 命令将你的开发服务器公开在可通过全球防火墙访问的公开 URL 上。如果你无法使用默认 LAN 选项连接到开发服务器,或者你想在开发过程中获得有关实现的反馈,则此选项会很有帮助。
¥Sometimes, restrictive network conditions make it difficult to connect to the development server. The `npx expo start` command exposes your development server on a publicly available URL that is accessible through firewalls from around the globe. This option is helpful if you are not able to connect to your development server with the default LAN option or if you want to get feedback on your implementation while you are developing.
要获取隧道 URL,请从命令行将 [`--tunnel` 旗](/more/expo-cli/#tunneling) 传递到 `npx expo start`。
¥To get a tunneled URL, pass the [`--tunnel` flag](/more/expo-cli/#tunneling) to `npx expo start` from the command line.
### 已发布的更新
¥Published updates
EAS CLI 的 `eas update` 命令将 JavaScript 和资源文件的当前状态打包到优化的 "update" 中。此更新存储在 Expo 的托管服务上。应用的开发版本可以加载已发布的更新,而无需检查特定提交或让开发计算机保持运行。
¥EAS CLI's `eas update` command bundles the current state of your JavaScript and asset files into an optimized "update". This update is stored on a hosting service by Expo. A development build of your app can load published updates without needing to check out a particular commit or leave a development machine running.
### 手动输入更新的 URL
¥Manually entering an update's URL
当开发版本启动时,它将公开 UI 以加载开发服务器或 "手动输入网址"。你可以手动提供将启动特定分支的 URL。URL 遵循以下模式:
¥When a development build launches, it will expose UI to load a development server, or to "Enter URL manually". You can provide a URL manually that will launch a specific branch. The URL follows this pattern:
```text
https://u.expo.dev/[your-project-id]?channel-name=[channel-name]
# Example
https://u.expo.dev/F767ADF57-B487-4D8F-9522-85549C39F43F?channel-name=main
```
要获取项目的 ID,请使用 [应用配置的 `expo.updates.url`](/versions/latest/config/app/#url) 字段中的 URL。要查看通道列表,请运行 `eas channel:list`。
¥To get your project's ID, use the URL in the [app config's `expo.updates.url`](/versions/latest/config/app/#url) field. To see a list of channels, run `eas channel:list`.
### 深层链接到更新的 URL
¥Deep linking to an update's URL
你可以通过打开 `{scheme}://expo-development-client/?url={manifestUrl}` 形式的 URL 将应用加载到具有兼容的自定义客户端版本的设备上。你需要传递以下参数:
¥You can load your app on a device that has a compatible build of your custom client by opening a URL of the form `{scheme}://expo-development-client/?url={manifestUrl}`. You'll need to pass the following parameters:
| 参数 | value |
| ------------- | -------------------------------------------------------------------------------------------- |
| `scheme` | 客户端的 URL 方案(默认为 `exp+{slug}`,其中 [`slug`](/versions/latest/config/app/#slug) 是应用配置中设置的值) |
| `manifestUrl` | 要加载的更新清单的 URL 编码 URL。网址将为 `https://u.expo.dev/[your-project-id]?channel-name=[channel-name]` |
示例:
¥Example:
```text
exp+app-slug://expo-development-client/?url=https%3A%2F%2Fu.expo.dev%2F767ADF57-B487-4D8F-9522-85549C39F43F%2F%3Fchannel-name%3Dmain
```
上例中,`scheme` 为 `exp+app-slug`,`manifestUrl` 为项目,项目 ID 为 `F767ADF57-B487-4D8F-9522-85549C39F43F`,通道为 `main`。
¥In the example above, the `scheme` is `exp+app-slug`, and the `manifestUrl` is a project with an ID of `F767ADF57-B487-4D8F-9522-85549C39F43F` and a channel of `main`.
#### 在自动化场景中使用更新深层链接
¥Using updates deep links in automation scenarios
在模拟器或模拟器上使用自动化功能(例如在 CI/CD 工作流中)启动开发构建中的更新 URL 时,你可以将 `disableOnboarding=1` 查询参数添加到 URL,以跳过安装后首次启动开发构建时出现的入门屏幕。
¥When launching an update URL in a development build on an emulator or simulator using automation, such as in a CI/CD workflow, you can add the `disableOnboarding=1` query parameter to the URL to skip the onboarding screen that appears on the first launch of a development build after installation.
#### 应用特定的深层链接
¥App-specific deep links
在开发版本中测试深层链接时,例如导航到 Expo Router 应用中的特定屏幕或在 Oauth 登录流程中测试重定向回你的应用时,请按照与深层链接到某个应用时完全相同的方式构造 URL。 你的应用的独立构建(例如,`myscheme://path/to/screen`)。
¥When testing deep links in your development build, such as when navigating to a specific screen in an Expo Router app or testing redirecting back to your app during an Oauth login flow, construct the URL exactly as you would if you were deep-linking into a standalone build of your app (for example, `myscheme://path/to/screen`).
你的项目必须已在开发版本中打开,特定于应用的深层链接才能工作。目前不支持使用特定于应用的深层链接冷启动开发版本。避免在路径中特定于应用的深层链接中使用 `expo-development-client`,因为它是用于启动更新的 URL 的保留路径。
¥Your project must be already open in the development build for an app-specific deep link to work. Cold-launching a development build with an app-specific deep link is not currently supported. Avoid using `expo-development-client` in your app-specific deep links in the path, as it is a reserved path used for launching an updated URL.
### 二维码
¥QR codes
你可以使用我们的端点生成可以通过开发版本轻松加载的二维码。
¥You can use our endpoint to generate a QR code that can be easily loaded by a development build.
当提供查询参数(例如 `appScheme` 和 `url`)时,发送到 `https://qr.expo.dev/development-client` 的请求将收到带有 SVG 图片的响应,其中包含 QR 代码,可以轻松扫描该 QR 代码以在开发版本中加载项目版本。
¥Requests send to `https://qr.expo.dev/development-client` when supplied the query parameters such as `appScheme` and `url` will receive a response with an SVG image containing a QR code that can be easily scanned to load a version of your project in your development build.
| 参数 | value |
| ----------- | ---------------------------------------------------------------------------------------------- |
| `appScheme` | 开发版本的 URL 编码深度链接方案(默认为 `exp+{slug}`,其中 [`slug`](/versions/latest/config/app/#slug) 是应用配置中设置的值) |
| `url` | 要加载的更新清单的 URL 编码 URL。网址将为 `https://u.expo.dev/[your-project-id]?channel-name=[channel-name]` |
示例:
¥Example:
```text
https://qr.expo.dev/development-client?appScheme=exp%2Bapps-slug&url=https%3A%2F%2Fu.expo.dev%2FF767ADF57-B487-4D8F-9522-85549C39F43F0%3Fchannel-name%3Dmain
```
上例中,`scheme` 为 `exp+app-slug`,`url` 为项目,项目 ID 为 `F767ADF57-B487-4D8F-9522-85549C39F43F`,通道为 `main`。
¥In the example above, the `scheme` is `exp+app-slug`, and the `url` is a project with an ID of `F767ADF57-B487-4D8F-9522-85549C39F43F` and a channel of `main`.
## 工作流程示例
¥Example workflows
这些是一些工作流程示例,可帮助你的团队充分利用你的开发构建。如果你想出对其他团队有用的其他别名,[提交 PR](https://github.com/expo/expo/tree/main/CONTRIBUTING.md#-updating-documentation) 将分享你的知识!
¥These are a few examples of workflows to help your team get the most out of your development build. If you come up with others that would be useful for other teams, [submit a PR](https://github.com/expo/expo/tree/main/CONTRIBUTING.md#-updating-documentation) to share your knowledge!
### PR 预览
¥PR previews
你可以将 CI 流程设置为在拉取请求更新时发布 EAS 更新,并添加用于查看兼容开发版本中的更改的 QR 代码。
¥You can set up your CI process to publish an EAS Update whenever a pull request is updated and add a QR code that is used to view the change in a compatible development build.
请参阅 [关于根据拉取请求发布应用预览的说明](/eas-update/github-actions/#publish-previews-on-pull-requests),使用 GitHub Actions 在你的项目中实现此工作流程,或在你选择的 CI 中用作模板。
¥See [instructions for publishing app previews on pull requests](/eas-update/github-actions/#publish-previews-on-pull-requests) to implement this workflow in your project using GitHub Actions or serve as a template in your CI of choice.
## 扩展
¥Extensions
扩展允许你使用附加功能来扩展你的开发客户端。
¥Extensions allow you to extend your development client with additional capabilities.
### 扩展开发菜单
¥Extending the dev menu
可以使用 `registerDevMenuItems` API 扩展开发菜单以包含额外的按钮:
¥The dev menu can be extended to include extra buttons by using the `registerDevMenuItems` API:
```tsx
import { registerDevMenuItems } from 'expo-dev-menu';
const devMenuItems = [
{
name: 'My Custom Button',
callback: () => console.log('Hello world!'),
},
];
registerDevMenuItems(devMenuItems);
```
这将在开发菜单中创建一个新部分,其中包含你已注册的按钮:
¥This will create a new section in the dev menu that includes the buttons you have registered:
> `registerDevMenuItems` 的后续调用将覆盖所有先前的条目。
>
> ¥Subsequent calls of `registerDevMenuItems` will override all previous entries.
### EAS 更新
¥EAS Update
EAS 更新扩展提供了在开发客户端中查看和加载已发布更新的功能。
¥The EAS Update extension provides the ability to view and load published updates in your development client.
它适用于所有 `v0.9.0` 及以上的开发客户端。要安装它,你需要最新发布的 `expo-updates`:
¥It's available for all development clients `v0.9.0` and above. To install it, you'll need the most recent publish of `expo-updates`:
```sh
$ npx expo install expo-dev-client expo-updates
```
#### 配置 EAS 更新
¥Configure EAS Update
如果你的项目中尚未配置 EAS 更新,你可以找到 [有关如何执行此操作的其他说明,请参见此处。](/eas-update/getting-started/)
¥If you have not yet configured EAS Updates in your project, you can find [additional instructions on how to do so here.](/eas-update/getting-started/)
你现在可以通过 `Extensions` 面板在开发版本中查看和加载 EAS 更新。
¥You can now view and load EAS Updates in your development build via the `Extensions` panel.
## 在应用配置中设置运行时版本
¥Set runtimeVersion in app config
当你创建项目的开发版本时,你将获得一个稳定的环境来加载在 JavaScript 中定义的应用的任何更改或其他与资源相关的更改。对应用的其他更改,无论是直接在 android 和 ios 目录中定义,还是通过你选择安装的包或 SDK 定义,都将要求你创建开发版本的新版本。
¥When you create a development build of your project, you'll get a stable environment to load any changes to your app that are defined in JavaScript or other asset-related changes. Other changes to your app, whether defined directly in **android** and **ios** directories or by packages or SDKs you choose to install, will require you to create a new build of your development build.
要在应用的 JavaScript 层和原生层之间强制执行 API 协定,你应该在应用配置中设置 [`runtimeVersion`](/eas-update/runtime-versions) 值。你制作的每个构建都将嵌入此值,并且在开发和生产中仅加载具有相同 `runtimeVersion` 的打包包。
¥To enforce an API contract between the JavaScript and native layers of your app, you should set the [`runtimeVersion`](/eas-update/runtime-versions) value in the app config. Each build you make will have this value embedded and will only load bundles with the same `runtimeVersion`, in both development and production.
## 下一步
有用资源列表,可帮助你了解有关开发构建和 EAS 构建的更多信息。
# 配置插件
## 配置插件简介
Expo 配置插件简介。
在项目中使用 [持续的原生生成 (CNG)](/workflow/continuous-native-generation/) 时,原生项目(Android 和 iOS 目录)的更改无需直接与原生项目文件交互即可实现。此外,你可以使用配置插件自动配置原生项目,而无需使用默认应用配置属性进行配置。
¥When using [Continuous Native Generation (CNG)](/workflow/continuous-native-generation/) in a project, native project (**android** and **ios** directories) changes are implemented without directly interacting with the native project files. Instead, you can use a config plugin to automatically configure your native project beyond what can be configured using the default app config props.
## 什么是配置插件
¥What is a config plugin
配置插件是一个顶层自定义配置点,它未内置于 [应用配置](/workflow/configuration) 中。使用配置插件,你可以在 CNG 项目中修改在 [prebuild](/workflow/continuous-native-generation/#usage) 过程中创建的原生项目。
¥A config plugin is a top-level custom configuration point that is not built into the [app config](/workflow/configuration). Using a config plugin, you can modify native projects created during the [prebuild](/workflow/continuous-native-generation/#usage) process in CNG projects.
配置插件在 [应用配置](/workflow/configuration) 文件的 `plugins` 属性中引用,由一个或多个插件函数组成。这些插件函数用 JavaScript 编写,并在预构建过程中执行。
¥A config plugin is referenced in the `plugins` property of the [app config](/workflow/configuration) file and is made up of one or more plugin functions. These plugin functions are written in JavaScript and are executed during the prebuild process.
## 词汇表
¥Glossary
一个典型的配置插件由一个或多个协同工作的插件函数组成。下图展示了配置插件各个部分之间的交互方式:
¥A typical config plugin is made up of one or more plugin functions that work together. The following diagram shows how the different parts of a config plugin interact with each other:
在以下指南中,我们将使用上图高亮下文解释的特定术语:
¥In the following guides, we will use the above diagram to highlight specific terminology explained below:
### 插件
¥Plugin
顶层配置插件,在你的应用配置的 `plugins` 数组中引用。这是插件的入口点。通常,它被命名为 `with`。例如,`withMyPlugin`。它由一个或多个 [插件功能](#plugin-function) 组成。
¥The top-level config plugin which is referenced in your app config's `plugins` array. This is the entry point for your plugin. Conventionally, it is named `with`. For example, `withMyPlugin`. It is made of one or more [plugin functions](#plugin-function).
### 插件功能
¥Plugin function
配置插件中的一个或多个函数称为插件函数。它们封装了执行平台特定修改的底层逻辑。从技术上讲,插件函数看起来与顶层插件本身的函数一样,并且可以独立用作插件。将插件拆分成更小的函数通常有助于测试和调试。
¥One or more functions inside a config plugin that are called *plugin functions*. They wrap the underlying logic of performing platform-specific modifications. Technically, plugin functions look just like the function for the top-level plugin itself, and could be used as a plugin independently. Breaking plugins into smaller functions is often helpful for testing and debugging.
### Mod 插件功能
¥Mod plugin function
来自 `expo/config-plugins` 库的封装函数,提供使用 `mods` 修改原生文件的安全方法。作为开发者,你将在配置插件中使用这些功能,而不是在底层 `mods` 中。
¥Wrapper functions from `expo/config-plugins` library that provide a safe way to modify native files using `mods`. As a developer, you will use these functions in your config plugin instead of underlying `mods`.
### Mod
在预构建期间直接修改原生项目文件的底层平台特定修饰符(例如 `mods.android.manifest` 和 `mods.ios.infoplist`)。
¥The underlying platform-specific modifiers (like `mods.android.manifest` and `mods.ios.infoplist`) that directly modify native project files during prebuild.
## 为什么使用配置插件
¥Why use a config plugin
配置插件可以向你的项目添加默认情况下不包含的原生配置。它们可用于生成应用图标、设置应用名称、配置 AndroidManifest.xml 和 Info.plist 等等。
¥Config plugins can add native configuration to your project that isn't included by default. They can be used to generate app icons, set the app name, configure **AndroidManifest.xml** and **Info.plist**, and so on.
在 CNG 项目中,最好避免手动修改这些原生项目,因为你无法安全地重新生成它们,否则可能会覆盖手动修改。配置插件允许你以可预测的方式修改这些原生项目,方法是将原生项目的更改合并到配置文件中,并在运行 `npx expo prebuild`(在 CI/CD 过程中手动或自动)时应用这些更改。例如,当你在应用配置中更改应用名称并运行 `npx expo prebuild` 时,该名称将在你的原生项目中自动更改,无需手动更新 AndroidManifest.xml 和 Info.plist 文件。
¥In CNG projects, it is best to avoid modifying these native projects manually, because you cannot regenerate them safely without potentially overwriting manual modifications. Config plugins allow you to modify these native projects in a *predictable way* by consolidating your native project changes into a configuration file and applying them when you run `npx expo prebuild` (either manually or automatically during a CI/CD process). For example, when you change the name of your app in app config and run `npx expo prebuild`, the name will change in your native projects automatically without the need to manually update **AndroidManifest.xml** and **Info.plist** files.
## 配置插件的特点
¥Characteristics of a config plugin
配置插件具有以下特点:
¥Config plugins have the following characteristics:
* 插件是同步函数,接受 [ExpoConfig](/workflow/configuration) 并返回修改后的 `ExpoConfig`。在极少数情况下,如果与原生项目通信的可用方法是异步的,则插件也可以是异步的,但性能不佳。
¥Plugins are **synchronous** functions that accept an [ExpoConfig](/workflow/configuration) and return a modified `ExpoConfig`. In rare cases, plugins can also be asynchronous if available methods to communicate with native projects are asynchronous, but they won't be performant.
* 插件应使用以下约定命名:`with`,例如 `withFacebook`
¥Plugins should be named using the following convention: `with`, for example, `withFacebook`
* 插件应同步运行,并且其返回值应可序列化,但添加任何 [`mods`](#mods) 除外。
¥Plugins should be synchronous and their return value should be serializable, except for adding any [`mods`](#mods)
* 插件始终在应用配置评估阶段进行评估。
¥Plugins are always evaluated during the app config evaluation phase.
* (可选)可以将第二个参数传递给插件进行配置。
¥Optionally, a second argument can be passed to the plugin to configure it
* 模块仅在 `npx expo prebuild`(预构建过程)的同步阶段进行评估,并在代码生成期间修改原生文件。因此,在配置插件中对应用配置所做的任何修改都应在模块之外进行,以确保它们在非预构建配置场景中执行。
¥Mods are only evaluated during the **syncing** phase of `npx expo prebuild` (prebuild process) and modify native files during code generation. Because of that, any modifications made to app config in a config plugin should be outside of a mod to ensure that they are executed in non-prebuild configuration scenarios.
## 开始使用
¥Get started
## 创建和使用配置插件
了解如何在 Expo 项目中创建和使用配置插件。
本指南涵盖了如何创建配置插件、如何向配置插件传递参数以及如何将多个配置插件链接在一起等章节。它还介绍了如何使用 Expo 库中的配置插件。
¥This guide covers sections how to create a config plugin, how to pass parameters to a config plugin, and how to chain multiple config plugins together. It also covers how to use a config plugin from an Expo library.
在本指南中,通过下图,你将了解配置插件层次结构的前两部分:
¥Using the diagram below, in this guide, you will learn the first two parts of the config plugin hierarchy:
> **info** 注意:以下部分使用动态 [应用配置](/workflow/configuration/)(app.config.js/app.config.ts 而不是 app.json),使用简单配置插件时无需使用动态 [应用配置](/workflow/configuration/)。但是,当你想要创建/使用接受参数的基于函数的配置插件时,必须使用动态应用配置。
>
> ¥**Note:** The following sections use dynamic [app config](/workflow/configuration/) (**app.config.js/app.config.ts** instead of **app.json**), which is not required to use a simple config plugin. However, it is required to use dynamic app config when you want to create/use a function-based config plugin that accepts parameters.
## 创建配置插件
¥Creating a config plugin
在下一节中,我们将创建一个本地配置插件,该插件将任意属性 `HelloWorldMessage` 添加到 Android 的 AndroidManifest.xml 和 iOS 的 Info.plist 中。
¥In the following section, let's create a local config plugin that adds an arbitrary property `HelloWorldMessage` to the **AndroidManifest.xml** for Android and **Info.plist** for iOS.
此示例将创建和修改以下文件。要继续操作,请在项目根目录中创建一个 plugins 目录,并在其中创建 withAndroidPlugin.ts、withIosPlugins.ts 和 withPlugin.ts 文件。
¥This example will create and modify the following files. To follow along, create a **plugins** directory in the root of your project, and inside it, create **withAndroidPlugin.ts**, **withIosPlugins.ts**, and **withPlugin.ts** files.
Step 1:
### Create Android plugin
In **withAndroidPlugin.ts**, add the following code:
```ts withAndroidPlugin.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
const withAndroidPlugin: ConfigPlugin = config => {
// Define a custom message
const message = 'Hello world, from Expo plugin!';
return withAndroidManifest(config, config => {
const mainApplication = config?.modResults?.manifest?.application?.[0];
if (mainApplication) {
// Ensure meta-data array exists
if (!mainApplication['meta-data']) {
mainApplication['meta-data'] = [];
}
// Add the custom message as a meta-data entry
mainApplication['meta-data'].push({
$: {
'android:name': 'HelloWorldMessage',
'android:value': message,
},
});
}
return config;
});
};
export default withAndroidPlugin;
```
The example code above adds a meta-data entry `HelloWorldMessage` to the **android/app/src/main/AndroidManifest.xml** file by importing `ConfigPlugin` and `withAndroidManifest` from the `expo/config-plugins` library. The [`withAndroidManifest`](/config-plugins/mods/#mod-plugins) mod plugin is an asynchronous function that accepts a config and a data object and modifies the value before returning an object.
Step 2:
### Create iOS plugin
In **withIosPlugin.ts**, add the following code:
```ts withIosPlugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins';
const withIosPlugin: ConfigPlugin = config => {
// Define the custom message
const message = 'Hello world, from Expo plugin!';
return withInfoPlist(config, config => {
// Add the custom message to the Info.plist file
config.modResults.HelloWorldMessage = message;
return config;
});
};
export default withIosPlugin;
```
The example code above adds `HelloWorldMessage` as the custom key with a custom message in **ios/\/Info.plist** file by importing the `ConfigPlugin` and `withInfoPlist` from the `expo/config-plugins` library. The [`withInfoPlist`](/config-plugins/mods/#mod-plugins) mod plugin is an asynchronous function that accepts a config and a data object and modifies the value before returning an object.
Step 3:
### Create a combined plugin
Now you can create a combined plugin that applies both platform-specific plugins. This approach allows the maintenance of platform-specific code separately while providing a single entry point.
In **withPlugin.ts**, add the following code:
```ts withPlugin.ts
import { ConfigPlugin } from 'expo/config-plugins';
import withAndroidPlugin from './withAndroidPlugin';
import withIosPlugin from './withIosPlugin';
const withPlugin: ConfigPlugin = config => {
// Apply Android modifications first
config = withAndroidPlugin(config);
// Then apply iOS modifications and return
return withIosPlugin(config);
};
export default withPlugin;
```
Step 4:
### Add TypeScript support and convert to dynamic app config
We recommend writing config plugins in TypeScript, since this will provide intellisense for the configuration objects. However, your app config is ultimately evaluated by Node.js, which does not recognize TypeScript code by default. Therefore, you will need to add a parser for the TypeScript files from the the **plugins** directory to **app.config.ts** file.
Install `tsx` library by running the following command:
```sh
$ npm install --save-dev tsx
```
Then, change the static app config (**app.json**) to the [dynamic app config (**app.config.ts**)](/workflow/configuration/#dynamic-configuration) file. You can do this by renaming the **app.json** file to **app.config.ts** and changing the content of the file as shown below. Ensure to add the following import statement at the top of your **app.config.ts** file:
```ts app.config.ts
import 'tsx/cjs';
module.exports = () => {
};
```
Step 5:
### Call the config plugin from your dynamic app config
Now, you can call the config plugin from your dynamic app config. To do this, you need to add the path to the **withPlugin.ts** file to the plugins array in your app config:
```ts app.config.ts
import "tsx/cjs";
import { ExpoConfig } from "expo/config";
plugins: [
["./plugins/withPlugin.ts"],
],
};
```
To see the custom config applied in native projects, run the following command:
```sh
$ npx expo prebuild --clean --no-install
```
To verify the custom config plugins applied, open **android/app/src/main/AndroidManifest.xml** and **ios/\/Info.plist** files:
```xml
```
```xml
HelloWorldMessageHello world, from Expo plugin!
```
## Passing a parameter to a config plugin
Your config plugin can accept parameters passed from your app config. To do so, you will need to read the parameter in your config plugin function, and then pass an object containing the parameter along with the config plugin function in your app config.
Step 1:
Considering the previous example, let's pass a custom message to the plugin. Add an `options` object in **withAndroidPlugin.ts** and update the `message` variable to use the `options.message` property:
```ts withAndroidPlugin.ts
type AndroidProps = {
message?: string;
};
const withAndroidPlugin: ConfigPlugin = (
config,
) => {
const message = options.message || 'Hello world, from Expo plugin!';
return withAndroidManifest(config, config => {
});
};
export default withAndroidPlugin;
```
Step 2:
Similarly, add an `options` object in **withIosPlugin.ts** and update the `message` variable to use the `options.message` property:
```ts withIosPlugin.ts
type IosProps = {
message?: string;
};
const message = options.message || 'Hello world, from Expo plugin!';
};
export default withIosPlugin;
```
Step 3:
Update the **withPlugin.ts** file to pass the `options` object to both plugins:
```ts withPlugin.ts
config = withAndroidPlugin(config, options);
return withIosPlugin(config, options);
};
```
Step 4:
To pass a value dynamically to the plugin, you can pass an object with the `message` property to the plugin in your app config:
```ts app.config.ts
{
plugins: [
[
"./plugins/withPlugin.ts",
{ message: "Custom message from app.config.ts" },
],
],
}
```
## Chaining config plugins
Config plugins can be chained together to apply multiple modifications. Each plugin in the chain runs in the order it appears, with the output of one plugin becoming the input for the next. This sequential execution ensures that dependencies between plugins are respected and allows you to control the precise order of modifications to your native code.
To chain config plugins, you can pass an array of plugins to the `plugins` array property in your app config. This is also supported in JSON app config file format (**app.json**).
```ts app.config.ts
module.exports = ({ config }: { config: ExpoConfig }) => {
name: 'my app',
plugins: [
[withFoo, 'input 1'],
[withBar, 'input 2'],
[withDelta, 'input 3'],
],
};
```
The `plugins` array uses `withPlugins` method under the hood to chain the plugins. If your plugins array is getting long or has complex configuration, you can use the `withPlugins` method directly to make your configuration easier to read. `withPlugins` will chain the plugins together and execute them in order.
```ts app.config.ts
import { withPlugins } from 'expo/config-plugins';
// Create a base config object
const baseConfig = {
name: 'my app',
};
// ❌ Hard to read
withDelta(withFoo(withBar(config, 'input 1'), 'input 2'), 'input 3');
// ✅ Easy to read
withPlugins(config, [
[withFoo, 'input 1'],
[withBar, 'input 2'],
// When no input is required, you can just pass the method
withDelta,
]);
// Export the base config with plugins applied
module.exports = ({ config }: { config: ExpoConfig }) => {
return withPlugins(baseConfig, plugins);
};
```
## Using a config plugin
Expo config plugins are usually included in Node.js modules. You can install them just like other libraries in your project.
For example, `expo-camera` has a plugin that adds camera permissions to the **AndroidManifest.xml** and **Info.plist**. To install it in your project, run the following command:
```sh
$ npx expo install expo-camera
```
In your [app config](/versions/latest/config/app/), you can add `expo-camera` to the list of plugins:
```json app.json
{
"expo": {
"plugins": ["expo-camera"]
}
}
```
Some config plugins offer flexibility by allowing you to pass options to customize their configuration. To do this, you can pass an array with the Expo library name as the first argument, and an object containing the options as the second argument. For example, the `expo-camera` plugin allows you to customize the camera permission message:
```json app.json
{
"expo": {
"plugins": [
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
}
]
]
}
}
```
>
info 提示:对于每个具有配置插件的 Expo 库,你都可以在库的 API 参考中找到有关它的更多信息。例如,[`expo-camera` 库有一个配置插件部分](/versions/latest/sdk/camera/#configuration-in-appjsonappconfigjs)。
¥**info** **Tip**: For every Expo library that has a config plugin, you'll find more information about that in the library's API reference. For example, the [`expo-camera` library has a config plugin section](/versions/latest/sdk/camera/#configuration-in-appjsonappconfigjs).
运行 `npx expo prebuild` 时,会编译 [`mods`](/config-plugins/introduction/#mods),并且原生文件会发生变化。
¥On running the `npx expo prebuild`, the [`mods`](/config-plugins/introduction/#mods) are compiled, and the native files change.
直到你重建原生项目(例如使用 Xcode)后,更改才会生效。如果你在没有原生目录的项目(CNG 项目)中使用配置插件,它们将在 EAS Build 的预构建步骤中或在本地运行 `npx expo prebuild|android|ios` 时应用。
¥The changes don't take effect until you rebuild the native project, for example, with Xcode. **If you're using config plugins in a project without native directories (CNG projects), they will be applied during the prebuild step in EAS Build** or when running `npx expo prebuild|android|ios` locally.
## Mods
了解模块以及如何在创建配置插件时使用它们。
本指南解释了什么是 mod 和 mod 插件、它们的工作原理以及如何在为你的 Expo 项目创建配置插件时有效地使用它们。
¥This guide explains what mods and mod plugins are, how they work, and how to use them effectively when creating config plugins for your Expo project.
在本指南中,通过下图,你将了解配置插件层次结构的后两部分:
¥Using the diagram below, in this guide, you will learn the last two parts of the config plugin hierarchy:
## 模组插件
¥Mod plugins
Mod 插件提供了一种在预构建过程中修改原生项目文件的方法。它们由 `expo/config-plugins` 库提供,封装了顶层 mod(也称为默认 [mods](#mods)),因为顶层 mod 是平台相关的,并且执行各种一开始可能难以理解的任务。
¥Mod plugins provide a way to modify native project files during the prebuild process. They are made available from `expo/config-plugins` library and wrap top-level mods (also known as *default [mods](#mods)*) because top-level mods are platform-specific and perform various tasks that can be difficult to understand at first.
> **info** 提示:如果你正在开发需要 mod 的功能,则应该使用 mod 插件,而不是直接与顶层 mod 交互。
>
> ¥**Tip:** If you are developing a feature that requires mods, you should use *mod plugins* instead of interacting with top-level mods directly.
### 可用的 mod 插件
¥Available mod plugins
`expo/config-plugins` 库中提供以下 mod 插件:
¥The following mod plugins are available in the `expo/config-plugins` library:
#### 安卓
¥Android
| 默认 Android 版本 | 模组插件 | 危险的 | 描述 |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-: | -------------------------------------------------------------------------------------- |
| `mods.android.manifest` | `withAndroidManifest`([示例](https://github.com/expo/expo/blob/main/packages/expo-notifications/plugin/src/withNotificationsAndroid.ts)) | * | 将 android/app/src/main/AndroidManifest.xml 修改为 JSON(使用 [`xml2js`][xml2js] 解析) |
| `mods.android.strings` | `withStringsXml`([示例](https://github.com/expo/expo/blob/d7fb5d254d5cb57ab06055136db72b9347d3db1e/packages/expo-navigation-bar/plugin/src/withNavigationBar.ts)) | * | 修改 android/app/src/main/res/values/strings.xml 为 JSON(用 [`xml2js`][xml2js] 解析)。 |
| `mods.android.colors` | `withAndroidColors`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/StatusBar.ts#L8)) | * | 将 android/app/src/main/res/values/colors.xml 修改为 JSON(使用 [`xml2js`][xml2js] 解析)。 |
| `mods.android.colorsNight` | `withAndroidColorsNight`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/unversioned/expo-splash-screen/withAndroidSplashStyles.ts#L5)) | * | 将 android/app/src/main/res/values-night/colors.xml 修改为 JSON(使用 [`xml2js`][xml2js] 解析)。 |
| `mods.android.styles` | `withAndroidStyles`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/unversioned/expo-splash-screen/withAndroidSplashStyles.ts#L5)) | * | 修改 android/app/src/main/res/values/styles.xml 为 JSON(用 [`xml2js`][xml2js] 解析)。 |
| `mods.android.gradleProperties` | `withGradleProperties`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/BuildProperties.ts#L5)) | * | 修改 android/gradle.properties 为 `Properties.PropertiesItem[]`。 |
| `mods.android.mainActivity` | `withMainActivity`([示例](https://github.com/expo/expo/blob/main/packages/install-expo-modules/src/plugins/android/withAndroidModulesMainActivity.ts#L2)) | | 将 android/app/src/main/<package>/MainActivity.java 修改为字符串。 |
| `mods.android.mainApplication` | `withMainApplication`([示例](https://github.com/expo/expo/blob/main/packages/expo-web-browser/plugin/src/withWebBrowserAndroid.ts#L8)) | | 将 android/app/src/main/<package>/MainApplication.java 修改为字符串。 |
| `mods.android.appBuildGradle` | `withAppBuildGradle`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/GoogleServices.ts#L5)) | | 将 android/app/build.gradle 修改为字符串。 |
| `mods.android.projectBuildGradle` | `withProjectBuildGradle`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/GoogleServices.ts#L5)) | | 将 android/build.gradle 修改为字符串。 |
| `mods.android.settingsGradle` | `withSettingsGradle`([示例](https://github.com/expo/expo/blob/main/packages/install-expo-modules/src/plugins/android/withAndroidSettingsGradle.ts#L2)) | | 将 android/settings.gradle 修改为字符串。 |
#### iOS 系统
¥iOS
| 默认 iOS 版本 | 模组插件 | 危险的 | 描述 |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------------------------------------- |
| `mods.ios.infoPlist` | `withInfoPlist`([示例](https://github.com/expo/expo/blob/main/packages/expo-location/plugin/src/withLocation.ts)) | * | 修改 ios/<name>/Info.plist 为 JSON(用 [`@expo/plist`][expo-plist] 解析)。 |
| `mods.ios.entitlements` | `withEntitlementsPlist`([示例](https://github.com/expo/expo/blob/main/packages/expo-apple-authentication/plugin/src/withAppleAuthIOS.ts)) | * | 将 ios/<name>/<product-name>.entitlements 修改为 JSON(用 [`@expo/plist`][expo-plist] 解析)。 |
| `mods.ios.expoPlist` | `withExpoPlist`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Updates.ts#L6)) | * | 将 ios/<name>/Expo.plist 修改为 JSON(Expo 更新 iOS 配置)(使用 [`@expo/plist`][expo-plist] 解析)。 |
| `mods.ios.xcodeproj` | `withXcodeProject`([示例](https://github.com/expo/expo/blob/main/packages/expo-asset/plugin/src/withAssetsIos.ts)) | * | 修改 ios/<name>.xcodeproj 为 `XcodeProject` 对象(用 [`xcode`](https://www.npmjs.com/package/xcode) 解析)。 |
| `mods.ios.podfile` | `withPodfile` ([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Maps.ts#L6) | * | 将 ios/Podfile 修改为字符串。 |
| `mods.ios.podfileProperties` | `withPodfileProperties`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/BuildProperties.ts#L4)) | * | 将 ios/Podfile.properties.json 修改为 JSON。 |
| `mods.ios.appDelegate` | `withAppDelegate`([示例](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Maps.ts#L6)) | | 将 ios/<name>/AppDelegate.m 修改为字符串。 |
> **info** 关于默认 Android 和 iOS 模块的说明: 默认模块由模块编译器提供,用于常见的文件操作。危险的修改依赖正则表达式(regex)来修改应用代码,这可能会导致构建中断。正则表达式 mod 也很难进行版本控制,因此应谨慎使用。始终选择使用应用代码来修改应用代码,即 [Expo 模块](https://github.com/expo/expo/tree/main/packages/expo-modules-core) 原生 API。
>
> ¥**Note about default Android and iOS mods:**
> Default mods are provided by the mod compiler for common file manipulation. Dangerous modifications rely on regular expressions (regex) to modify application code, which may cause the build to break. Regex mods are also difficult to version, and therefore should be used sparingly. Always opt towards using application code to modify application code, that is, [Expo Modules](https://github.com/expo/expo/tree/main/packages/expo-modules-core) native API.
## Mods
配置插件使用 mods(修改器的缩写)在预构建过程中修改原生项目文件。模块是异步函数,允许你更改平台特定文件(例如 AndroidManifest.xml 和 Info.plist)以及其他原生配置文件,而无需手动编辑它们。它们仅在 `npx expo prebuild` 的同步阶段(预构建过程)执行。
¥Config plugins use **mods** (short for modifiers) to modify native project files during the prebuild process. Mods are asynchronous functions that allow you to make changes to platform-specific files such as **AndroidManifest.xml** and **Info.plist**, and other native configuration files without having to manually edit them. They execute only during the **syncing** phase of `npx expo prebuild` (prebuild process).
它们接受一个配置和一个数据对象,然后修改它们并将其作为单个对象返回。例如,在原生项目中,`mods.android.manifest` 修改 AndroidManifest.xml,`mods.ios.plist` 修改 Info.plist。
¥They accept a config and a data object, then modify and return both of them as a single object. For example, in native projects, `mods.android.manifest` modifies **AndroidManifest.xml** and `mods.ios.plist` modifies **Info.plist**.
你不能在配置插件中直接将 mods 用作顶层函数(例如 `with.android.manifest`)。当你需要使用模组时,你可以在配置插件中使用模组插件。这些 mod 插件由 `expo/config-plugins` 库提供,封装了顶层 mod 函数,并在后台执行各种任务。要查看可用模组列表,请查看 [修改 `expo/config-plugins` 提供的插件](#available-mod-plugins)。
¥**You don't use mods as top-level functions (for example `with.android.manifest`) directly in your config plugin.** When you need to use a mod, you use *mod plugins* in your config plugins. These mod plugins are provided by the `expo/config-plugins` library and wrap top-level mod functions and behind the scenes they perform various tasks. To see a list of available mods, check out the [mod plugins provided by `expo/config-plugins`](#available-mod-plugins).
Note: How default mods work and their key characteristics
---
When a default mod resolves, it is added to the `mods` object of the app config. This `mods` object is different from the rest of the app config
because it doesn't get serialized, which means you can use it to perform actions _during_ code generation. Whenever possible, you should use available mod plugins instead of default mods since they are easier to work with.
Here is a high-level overview of how default mods work:
- The config is read using [`getPrebuildConfig`](https://github.com/expo/expo/blob/efc2db4eb1c909544e28792a15c89f8d22113c5b/packages/%40expo/prebuild-config/src/getPrebuildConfig.ts#L28) from `@expo/prebuild-config`
- All of the core functionality supported by Expo is added via plugins in `withIosExpoPlugins`. This includes name, version, icons, locales, and so on.
- The config is passed to the compiler `compileModsAsync`
- The compiler adds base mods that are responsible for reading data (like **Info.plist**), executing a named mod (like `mods.ios.infoPlist`), then writing the results to the file system
- The compiler iterates over all the mods and asynchronously evaluates them, providing some base props like the `projectRoot`
- After each mod, error handling asserts if the mod chain was corrupted by an invalid mod
Here are some key characteristics of default mods:
- `mods` are omitted from the manifest and **cannot** be accessed via `Updates.manifest`. Mods exist for the sole purpose of modifying native project files during code generation!
- `mods` can be used to read and write files safely during the `npx expo prebuild` command. This is how Expo CLI modifies the **Info.plist**, entitlements, xcproj, and so on.
- `mods` are platform-specific and should always be added to a platform-specific object:
```ts app.config.ts
module.exports = {
name: 'my-app',
mods: {
ios: {
/* iOS mods... */
},
android: {
/* Android mods... */
},
},
};
```
After mods are resolved, the contents of each mod will be written to disk. Custom mods can be added to support new native files. For example, you can create a mod to support the **GoogleServices-Info.plist**, and pass it to other mods.
---
### How mod plugins work
When a mod plugin is executed, it gets passed a `config` object with additional properties: `modResults` and `modRequest`.
#### `modResults`
The `modResults` object contains the data to modify and return. Its type depends on the mod that's being used.
#### `modRequest`
The `modRequest` object contains the following additional properties supplied by the mod compiler.
| Property | Type | Description |
| --------------------- | ------------- | ------------------------------------------------------------------------------------------------------------- |
| `projectRoot` | `string` | Project root directory for the universal app. |
| `platformProjectRoot` | `string` | Project root for the specific platform. |
| `modName` | `string` | Name of the mod. |
| `platform` | `ModPlatform` | Name of the platform used in the mods config. |
| `projectName` | `string` | (iOS only) The path component used for querying project files. For example, `projectRoot/ios/[projectName]/`. |
## Create your own mod
For example, if you want to write a mod to update the Xcode Project's "product name", you'll create a config plugin file that uses the [`withXcodeProject`](#ios) mod plugin.
```ts my-config-plugin.ts
import { ConfigPlugin, withXcodeProject, IOSConfig } from 'expo/config-plugins';
const withCustomProductName: ConfigPlugin = (config, customName) => {
return withXcodeProject(
config,
async (
) => {
config.modResults = IOSConfig.Name.setProductName({ name: customName }, config.modResults);
return config;
}
);
};
// Usage:
/// Create a config
const config = {
name: 'my app',
};
/// Use the plugin
export default withCustomProductName(config, 'new_name');
```
## Plugin module resolution
When implementing plugins, there are two fundamental approaches to consider:
1. **Plugins defined within your app's project**: These plugins live locally within your project, making them easy to customize and maintain alongside your app's code. They are ideal for project-specific customizations.
2. **Standalone package plugins**: These plugins exist as separate packages and are published to npm. This approach is ideal for reusable plugins that can be shared across multiple projects.
Both approaches provide the same capabilities for modifying your native configuration, but differ in how they're structured and imported. The sections below explain how module resolution works for each approach.
> Any resolution pattern that isn't specified below is unexpected behavior, and subject to breaking changes.
### Plugins defined within your app's project
With plugins defined within your app's project, you can implement plugins directly in your project in several ways:
#### File import
You can quickly create a plugin in your project by creating a JavaScript/TypeScript file and use it in your config like any other JS/TS file.
Imported from config
>,
],
]}
/>
In the above example, the config plugin file contains a bare minimum function:
```ts my-config-plugin.ts
module.exports = ({ config }: { config: ExpoConfig }) => {};
```
#### Inline function inside of dynamic app config
Expo config objects also support passing functions as-is to the `plugins` array. This is useful for testing, or if you want to use a plugin without creating a file.
```js app.config.ts
const withCustom = (config, props) => config;
const config = {
plugins: [
[
withCustom,
{
/* props */
},
],
withCustom,
],
};
```
One caveat to using functions instead of strings is that serialization will replace the function with the function's name. This keeps **manifests** (kind of like the **index.html** for your app) working as expected. Here is what the serialized config would look like:
```json
{
"plugins": [["withCustom", {}], "withCustom"]
}
```
### Standalone package plugins
> **info** See [Create a module with a config plugin](/modules/config-plugin-and-native-module-tutorial/) for a step-by-step guide on how to create a standalone package plugin.
Standalone package plugins can be implemented in two ways:
#### 1. Dedicated config plugin packages
These are npm packages whose sole purpose is to provide a config plugin. For a dedicated config plugin package, you can export your plugin using `app.plugin.js`:
Entry file for custom plugins
>,
],
[
'node_modules/expo-splash-screen/build/index.js',
<>
Skipped in favor of app.plugin.js
>,
],
]}
/>
#### 2. Config plugins with companion packages
When a config plugin is part of a Node module without an **app.plugin.js**, it uses the package's `main` entry point:
Node resolve to this file
>,
],
]}
/>
### Plugin resolution order
When you import a plugin package, files are resolved in this specific order:
1. **app.plugin.js in package root**
Entry file for custom plugins
>,
],
['node_modules/expo-splash-screen/build', ''],
[
'node_modules/expo-splash-screen/build/index.js',
<>
Skipped in favor of app.plugin.js
>,
],
]}
/>
2. **Package's main entry (from package.json)**
Node resolve to this file
>,
],
]}
/>
3. **Direct internal imports** (not recommended)
>
错误:避免直接导入模块内部代码,因为它会绕过标准解析顺序,并且可能会在将来的更新中中断。
¥**error** Avoid importing module internals directly as it bypasses the standard resolution order and may break in future updates.
Ignored due to direct import
>,
],
[
'node_modules/expo-splash-screen/build/index.js',
<>
expo-splash-screen/build/index.js
>,
],
]}
/>
### 为什么使用 app.plugin.js 插件
¥Why use app.plugin.js for plugins
`app.plugin.js` 方法是配置插件的首选,因为它允许与主包代码使用不同的转译设置。这一点尤为重要,因为 Node 环境通常需要与 Android、iOS 或 Web JS 环境不同的转译预设(例如,`module.exports` 而不是 `import/export`)。
¥The `app.plugin.js` approach is preferred for config plugins as it allows different transpilation settings from the main package code. This is particularly important because Node environments often require different transpilation presets compared to Android, iOS, or web JS environments (for example, `module.exports` instead of `import/export`).
[xml2js]: https://www.npmjs.com/package/xml2js
[expo-plist]: https://www.npmjs.com/package/@expo/plist
## 使用危险模块
了解危险模块以及如何在创建配置插件时使用它们。
Expo 中的危险模块可通过字符串操作和正则表达式直接访问原生项目文件。虽然 [现有模组插件](/config-plugins/mods) 是推荐的方法,但危险的 mods 可以充当现有 mod 插件无法实现的修改的逃生舱。
¥Dangerous mods in Expo provide direct access to native project files through string manipulation and regular expressions. While [existing mod plugins](/config-plugins/mods) are the recommended approach, dangerous mods serve as an escape hatch for modifications that cannot be achieved through existing mod plugins.
Note: Why are they considered dangerous?
---
Automated direct source code manipulation does not typically compose well. For example, if a dangerous mod replaces text in a source file, and a subsequent dangerous mod expects the original text to be there (perhaps it uses the original text as an anchor for a regular expression) then it is unlikely produce the desired result — depending on how it is written, it may either throw an error or log. Other types of mods are less prone to this type of problem, although it can happen with mods that manipulate source files directly like `withAndroidManifest` and `withPodfile`.
Unlike standard mods, which can run multiple times safely, dangerous mods are rarely guaranteed to be idempotent. Running the same dangerous mod multiple times may produce different results, cause duplicate modifications, or break the target file entirely.
---
## 何时使用危险模块
¥When to use a dangerous mod
在以下情况下,请考虑使用危险模块:
¥Consider using a dangerous mod when:
* 无法使用标准 mod 进行修改:你需要的修改不受现有修改插件(如 [`withAndroidManifest`](/config-plugins/mods/#android)、[`withPodfile`](/config-plugins/mods/#ios) 等)支持,或者库需要标准插件未涵盖的特定原生修改。
¥**Can't make the modification with a standard mod**: The modification you need isn't supported by existing mod plugins like [`withAndroidManifest`](/config-plugins/mods/#android), [`withPodfile`](/config-plugins/mods/#ios), and so on, or if a library requires specific native modifications that aren't covered by standard plugins.
* 与旧版 Expo SDK 的兼容性:你使用的是旧版 Expo SDK,其中不包含所需的 mod 插件。
¥**Legacy Expo SDK compatibility:** You are targeting an older Expo SDK version that doesn't include the mod plugin you need.
* 需要使用正则表达式或替换函数修改文本:你需要执行现有 mod 插件不支持的复杂文本操作。Expo 在内部使用危险的模块进行大型文件系统重构,例如,当库的名称更改时。
¥**Need to modify text with regexes or replace functions**: You need to perform intricate text manipulations that existing mod plugins do not support. Expo uses dangerous mods internally for large file system refactoring, for example, when a library's name changes.
## 如何使用危险模块
¥How to use a dangerous mod
在实际场景中,你可以按照 [创建配置插件部分](/config-plugins/plugins/#creating-a-config-plugin) 中的标准配置插件使用模式,直接在项目中使用本节中描述的示例配置插件。但是,有了现有的名为 [`withPodfile`](/config-plugins/mods/#ios) 的 mod 插件,你无需使用这个危险的 mod。以下示例仅用于演示如何创建和使用危险的模态框。
¥In a real-world scenario, you can use the example config plugin described in this section directly in your project by following the standard config plugin usage pattern from the [Creating a config plugin section](/config-plugins/plugins/#creating-a-config-plugin). However, with the existing mod plugin called [`withPodfile`](/config-plugins/mods/#ios), you don't have to use the dangerous mod. The example below is just for demonstration of how a dangerous mod can be created and used.
让我们看一个配置插件示例,该插件用于修改原生目录(ios)中的文件。当你在 Expo 项目中使用持续原生生成时,这非常有用。借助此配置插件,原生文件 (ios/Podfile) 将在 `npx expo prebuild` 命令运行时随时更新,无论你是手动运行还是使用 EAS Build。当现有 mod 插件无法编辑和更新原生目录中的文件时,此示例是一个理想的用例。
¥Let's take a look at an example config plugin to modify a file inside a native directory (**ios**). This is useful when you are using Continuous Native Generation in your Expo project. With the help of this config plugin, the native file (**ios/Podfile**) will update anytime the `npx expo prebuild` command runs, whether you run it manually or using EAS Build). This example is an ideal use case when an existing mod plugin cannot edit and update a file inside a native directory.
按照 [创建配置插件部分](/config-plugins/plugins/#creating-a-config-plugin) 中创建配置插件的目录结构和步骤(步骤 3、4 和 5),假设此配置插件是在 Expo 项目的 plugins 目录中创建的:
¥Following the directory structure and steps to create a config plugin (steps 3, 4, and 5) from [Creating a config plugin section](/config-plugins/plugins/#creating-a-config-plugin), let's assume this config plugin is created inside the **plugins** directory of your Expo project:
```tsx withCustomPodfile.ts
import { ConfigPlugin, IOSConfig, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs/promises';
import path from 'path';
const withCustomPodfile: ConfigPlugin = config => {
return withDangerousMod(config, [
'ios',
async config => {
const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile');
try {
let contents = await fs.readFile(podfilePath, 'utf8');
const projectName = IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot);
contents = addCustomPod(contents, projectName);
await fs.writeFile(podfilePath, contents);
console.log('✅ Successfully added custom pod to Podfile');
} catch (error) {
console.warn('⚠️ Podfile not found, skipping modification');
}
return config;
},
]);
};
function addCustomPod(contents: string, projectName: string): string {
if (contents.includes("pod 'Alamofire'")) {
console.log('Alamofire pod already exists, skipping');
return contents;
}
const targetRegex = new RegExp(
`(target ['"]${projectName}['"] do[\\s\\S]*?use_expo_modules!)`,
'm'
);
return contents.replace(targetRegex, `$1\n pod 'Alamofire', '~> 5.6'`);
}
export default withCustomPodfile;
```
在上面的示例中,withCustomPodfile 插件会在预构建过程中自动将 CocoaPod 依赖添加到项目的原生 ios/Podfile 中。它使用 `withDangerousMod` 直接提供对原生文件系统的访问,并在原生项目生成后、安装任何 CocoaPod 依赖之前运行。
¥In the example above, the plugin **withCustomPodfile** will add a CocoaPod dependency automatically to your project's native **ios/Podfile** during the prebuild process. It uses `withDangerousMod` to provide access to the native file system directly and run after the native project is generated, but before any CocoaPod dependency is installed.
Podfile 需要直接文本操作,这可以通过在 `addCustomMod` 函数中使用正则表达式模式来实现。此过程还需要将 CocoaPod 依赖插入到 Podfile 中的特定位置,该位置位于 `use_expo_modules!` 语句之后。
¥The **Podfile** requires direct text manipulation, which is done using a regex pattern inside `addCustomMod` function. This process also requires that the CocoaPod dependency is inserted into the **Podfile** at a specific location, which is after the `use_expo_modules!` statement.
## `withDangerousMod` 语法和要求
¥`withDangerousMod` syntax and requirements
使用 `withDangerousMod` 需要特定参数:
¥Using `withDangerousMod` requires certain parameters:
1. 原生平台(Android 或 iOS)
¥A native platform (**android** or **ios**)
2. 一个异步函数,接收具有文件系统访问权限的 `config` 对象
¥An asynchronous function that receives `config` object with file system access
3. 在原生目录中访问的相对文件名/路径
¥Relative file name/path to access inside the native directory
4. 读取现有文件,修改其内容,并写回文件
¥Reading the existing file, modifying its contents, and writing back to the file
5. (可选)在预构建过程中执行插件时,记录成功和失败状态的自定义消息。
¥(Optional) Log custom messages for success and failure state when a plugin executes during the prebuild process
以下代码片段提供了必填字段的框架,以及使用 `withDangerousMod` 时配置插件的结构:
¥The code snippet below provides a skeleton of the required field and how the config plugin can be structured when using `withDangerousMod`:
```tsx
import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs/promises';
import path from 'path';
const myPlugin: ConfigPlugin = config => {
return withDangerousMod(config, [
'platform', // 1. "ios" | "android"
async config => {
// 2. Async modification function
// 3. Build file paths
const filePath = path.join(
config.modRequest.platformProjectRoot, // Native project root
'path/to/file' // Relative path to target file
);
try {
// 4. Read existing file, modify its contents, and write back to the file
let contents = await fs.readFile(filePath, 'utf8');
contents = modifyContents(contents);
await fs.writeFile(filePath, contents);
// 5. Log success and failure states
console.log('✅ Successfully modified file');
} catch (error) {
console.warn('⚠️ File modification failed:', error);
}
return config;
},
]);
};
// Helper functions to use regex to modify the contents of the file
```
### 配置插件中的可用路径
¥Available paths in config plugins
配置插件中可用的不同路径属性:
¥Different path properties available in config plugins:
| 路径 | 类型 | 描述 |
| --------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------- |
| `config.modRequest.projectRoot` | `string` | package.json 所在的通用应用项目根目录。用于解析资源、读取 package.json 和跨平台操作。请务必验证目录是否存在且包含 package.json 文件。 |
| `config.modRequest.platformProjectRoot` | `string` | 特定于平台的项目根目录(projectRoot/android 或 projectRoot/ios)。用于平台特定的文件操作,例如修改原生配置文件。确保平台目录相对于主 `projectRoot` 存在。 |
| `config.modRequest.projectName` | `string` | \[仅限 iOS] 用于构建 iOS 文件路径的项目名称组件(例如,projectRoot/ios/\[projectName]/)。用于 iOS 特定的文件路径构建。仅适用于 iOS 平台,且应与实际的 Xcode 项目结构匹配。 |
| `config.modRequest.introspect` | `boolean` | 是否在内省模式下运行,该模式不应进行任何文件系统更改。使用 `true` 时,模块应该只读取和分析文件,而不能写入。用于配置分析和验证。 |
| `config.modRequest.ignoreExistingNativeFiles` | `boolean` | 是否忽略现有的原生文件。用于基于模板的操作,尤其会影响授权和其他原生配置,以确保与预构建预期保持一致。 |
## 使用危险模块时的注意事项
¥Considerations when using a dangerous mod
使用危险模块时,请考虑以下事项:
¥When using a dangerous mod, consider the following:
* 有限的幂等性保证。标准模组通常是幂等的,无需使用 clean 标志即可运行,而危险模组很少能保证幂等。这意味着多次运行相同的危险模块可能会产生不同的结果或导致问题。
¥**Limited idempotency guarantees.** Unlike standard mods, which are generally idempotent and can work without the clean flag, dangerous mods are **rarely guaranteed to be idempotent**. This means running the same dangerous mod multiple times may produce different results or cause issues.
* 此功能尚处于实验阶段,容易出现问题。请谨慎使用 `withDangerousMod`,因为它将来可能会发生变化。在每次 SDK 发布时,请彻底测试你的危险模块,因为当原生模板发生更改时,它们特别容易损坏。
¥**Experimental and prone to breakage.** Be careful using `withDangerousMod` as it is subject to change in the future. Test your dangerous mods thoroughly with each SDK release, as they are especially prone to breakage when native template changes occur.
* 使用标准 mod 插件。Android 和 iOS 都提供 `withAndroidManifest`、`withPodfile`、`withPodfileProperties` 等 mod 插件,用于执行常见的原生文件修改。仅在没有 [可用的现有模组插件](/config-plugins/mods/#available-mod-plugins) 来处理你的用例时,才使用危险模块。
¥**Use standard mod plugins**. Both Android and iOS offer mod plugins like `withAndroidManifest`, `withPodfile`, `withPodfileProperties`, and so on, to perform common native file modifications. Only use a dangerous mod when there are no [existing mod plugins available](/config-plugins/mods/#available-mod-plugins) to handle your use case.
* 不要假设文件存在。在读取/写入文件之前,请务必检查原生目录及其相对路径。如果你使用 CNG,你可以随时运行 `npx expo prebuild` 来创建原生 android 和 ios 目录并手动验证文件的存在。
¥**Don't assume a file exists**. Always check the native directory and the relative path to the file before reading/writing to it. If you use CNG, you can always run `npx expo prebuild` to create native **android** and **ios** directories and manually verify a file's existence.
* 危险模组优先运行。危险修改的执行顺序可能不可靠,因为危险修改会先于其他修改器运行。这可能会影响构建过程的可预测性,并可能与其他修改发生冲突。
¥**Dangerous mods run first**. The order in which dangerous mods execute might be unreliable since dangerous mods run before other modifiers. This can affect the predictability of your build process and may cause conflicts with other modifications.
## 开发和调试插件
了解 Expo 配置插件的开发最佳实践和调试技术。
开发插件是扩展 Expo 生态系统的好方法。然而,有时你会想要调试你的插件。本页面提供了一些开发和调试插件的最佳实践。
¥Developing a plugin is a great way to extend the Expo ecosystem. However, there are times you'll want to debug your plugin. This page provides some of the best practices for developing and debugging a plugin.
## 插件开发
¥Plugin development
> 使用 [修改器预览](https://github.com/expo/vscode-expo#expo-preview-modifier) 实时调试插件的结果。
>
> ¥Use [modifier previews](https://github.com/expo/vscode-expo#expo-preview-modifier) to debug the results of your plugin live.
为了使插件开发更容易,我们在 [`expo-module-scripts`](https://www.npmjs.com/package/expo-module-scripts) 中添加了插件支持。有关使用 TypeScript 和 Jest 构建插件的更多信息,请参阅 [配置插件指南](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin)。
¥To make plugin development easier, we've added plugin support to [`expo-module-scripts`](https://www.npmjs.com/package/expo-module-scripts).
Refer to the [config plugins guide](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin) for more info on using TypeScript, and Jest to build plugins.
### 安装依赖
¥Install dependencies
在提供配置插件的库中使用以下依赖:
¥Use the following dependencies in a library that provides a config plugin:
```json package.json
{
"dependencies": {},
"devDependencies": {
"expo": "^53.0.0"
},
"peerDependencies": {
"expo": ">=53.0.0"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
}
}
}
```
* 你可以更新 `expo` 的确切版本以针对特定版本进行构建。
¥You may update the exact version of `expo` to build against a specific version.
* 对于依赖于核心、稳定 API 的简单配置插件(例如仅修改 AndroidManifest.xml 或 Info.plist 的插件),你可以使用松散依赖,如上例所示。
¥For simple config plugins that depend on core, stable APIs, such as a plugin that only modifies **AndroidManifest.xml** or **Info.plist**, you can use a loose dependency such as in the example above.
* 你可能还想安装 [`expo-module-scripts`](https://github.com/expo/expo/blob/main/packages/expo-module-scripts/README.md) 作为开发依赖,但这不是必需的。
¥You may also want to install [`expo-module-scripts`](https://github.com/expo/expo/blob/main/packages/expo-module-scripts/README.md) as a development dependency, but it's not required.
### 导入配置插件包
¥Import the config plugins package
`expo/config-plugins` 和 `expo/config` 包是从 `expo` 包重新导出的。
¥The `expo/config-plugins` and `expo/config` packages are re-exported from the `expo` package.
```js
const { ... } = require('expo/config-plugins');
const { ... } = require('expo/config');
```
通过 `expo` 包导入可确保你使用 `expo` 包所依赖的 `expo/config-plugins` 和 `expo/config` 包的版本。
¥Importing through the `expo` package ensures that you are using the version of the `expo/config-plugins` and `expo/config` packages that are depended on by the `expo` package.
如果你不通过这种方式通过 `expo` 重新导出导入包,你可能会意外导入不兼容的版本(取决于使用该模块的开发者使用的包管理器中模块提升的实现细节)或者无法 完全导入模块(如果使用包管理器(例如 Yarn Berry 或 pnpm)的 "即插即用" 功能)。
¥If you do not import the package through the `expo` re-export in this way, you may accidentally be importing an incompatible version
(depending on the implementation details of module hoisting in the package manager used by the developer consuming the module) or be unable to import the module at all (if using "plug and play" features of a package manager such as Yarn Berry or pnpm).
配置类型直接从 `expo/config` 导出,因此无需安装或从 `expo/config-types` 导入:
¥Config types are exported directly from `expo/config`, so there is no need to install or import from `expo/config-types`:
```ts
import { ExpoConfig, ConfigContext } from 'expo/config';
```
### 插件开发最佳实践
¥Plugin development best practices
* 自述文件中的说明:如果插件与 React Native 模块绑定,那么你应该记录该包的手动设置说明。如果插件出现任何问题,开发者应该能够手动添加插件自动执行的项目修改。这也允许你支持未使用 [CNG](/workflow/continuous-native-generation/) 的项目。
¥**Instructions in your README**: If the plugin is tied to a React Native module, then you should document manual setup instructions for the package. If anything goes wrong with the plugin, developers should be able to manually add the project modifications that were automated by the plugin. This also allows you to support projects that are not using [CNG](/workflow/continuous-native-generation/).
* 记录插件的可用属性,并指定是否有任何必需的属性。
¥Document the available properties for the plugin, specifying if any of the properties are required.
* 如果可能,插件应该是幂等的,这意味着无论它们是在新的原生项目模板上运行,还是在已经存在更改的项目模板上再次运行,它们所做的更改都是相同的。这允许开发者在不使用 `--clean` 标志的情况下运行 `npx expo prebuild` 来同步配置更改,而无需完全重新创建原生项目。对于危险的模块,这可能更加困难。
¥If possible, plugins should be idempotent, meaning the changes they make are the same whether they are run on a fresh native project template or run again on a project template where its changes already exist. This allows developers to run `npx expo prebuild` without the `--clean` flag to sync changes to the config, rather than recreating the native project entirely. This may be more difficult with dangerous mods.
* 命名约定:如果插件函数名称适用于所有平台,请使用 `withFeatureName` 作为函数名称。如果插件是平台特定的,请使用驼峰式命名法,并在 "with" 之后紧跟平台名称。例如,`withAndroidSplash`、`withIosSplash`。
¥**Naming conventions**: Use `withFeatureName` for the plugin function name if it applies to all platforms. If the plugin is platform-specific, use a camel case naming with the platform right after "with". For example, `withAndroidSplash`, `withIosSplash`.
* 利用内置插件:如果 [应用配置](/versions/latest/config/app/) 和 [预构建配置](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/withDefaultPlugins.ts) 中已经有可用的配置,则无需为其编写配置插件。
¥**Leverage built-in plugins**: If there's already a configuration available in [app config](/versions/latest/config/app/) and [prebuild config](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/withDefaultPlugins.ts), you don't need to write a config plugin for it.
* 按平台拆分插件:在配置插件中使用函数时,请按平台拆分它们。例如,`withAndroidSplash`、`withIosSplash`。这使得在 `npx expo prebuild` 模式下使用 `--platform` 标志在 `EXPO_DEBUG` 模式下更容易遵循,因为日志将显示正在执行哪些特定于平台的功能。
¥**Split up plugins by platform**: When using functions within the config plugin, split them by platform. For example, `withAndroidSplash`, `withIosSplash`. This makes using the `--platform` flag in `npx expo prebuild` a bit easier to follow in `EXPO_DEBUG` mode, as the logging will show which platform-specific functions are being executed.
* 对你的插件进行单元测试:为复杂的修改编写 Jest 测试。如果你的插件需要访问文件系统,请使用模拟系统(我们强烈推荐 [`memfs`][memfs]),你可以在 [`expo-notifications`](https://github.com/expo/expo/blob/fc3fb2e81ad3a62332fa1ba6956c1df1c3186464/packages/expo-notifications/plugin/src/__tests__/withNotificationsAndroid-test.ts#L34) 插件测试中看到这方面的示例。
¥**Unit test your plugin**: Write Jest tests for complex modifications. If your plugin requires access to the filesystem,
use a mock system (we strongly recommend [`memfs`][memfs]), you can see examples of this in the [`expo-notifications`](https://github.com/expo/expo/blob/fc3fb2e81ad3a62332fa1ba6956c1df1c3186464/packages/expo-notifications/plugin/src/__tests__/withNotificationsAndroid-test.ts#L34) plugin tests.
* 请注意根 [**/**mocks**/**/*](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/__mocks__) 目录和 [**plugin/jest.config.js**](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/jest.config.js)。
¥Notice the root [**/__mocks__/**/*](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/__mocks__) directory and [**plugin/jest.config.js**](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/jest.config.js).
* TypeScript 插件总是比 JavaScript 插件更好。查看 [`expo-module-scripts` 插件][ems-plugin] 工具了解更多信息。
¥A TypeScript plugin is always better than a JavaScript plugin. Check out the [`expo-module-scripts` plugin][ems-plugin] tooling for more info.
* 不要通过配置插件修改 `sdkVersion`,这可能会破坏 `expo install` 等命令并导致其他意外问题。
¥Do not modify the `sdkVersion` via a config plugin, this can break commands like `expo install` and cause other unexpected issues.
### Mod 的最佳实践
¥Best practices for mods
* 避免使用正则表达式:[静态修改](#static-modification) 是关键。如果你想修改 Android gradle 文件中的值,请考虑使用 `gradle.properties`。如果你想修改 Podfile 中的某些代码,请考虑写入 JSON 并让 Podfile 读取静态值。
¥Avoid regex: [static modification](#static-modification) is key. If you want to modify a value in an Android gradle file, consider using `gradle.properties`. If you want to modify some code in the Podfile, consider writing to JSON and having the Podfile read the static values.
* 避免执行长时间运行的任务,例如发出网络请求或在 mod 中安装 Node 模块。
¥Avoid performing long-running tasks like making network requests or installing Node modules in mods.
* 不要在 mod 中添加交互式终端提示。
¥Do not add interactive terminal prompts in mods.
* 仅在危险模组中生成、移动和删除新文件。否则将会破坏 [introspection](#introspection)。
¥Generate, move, and delete new files in dangerous mods only. Failing to do so will break [introspection](#introspection).
* 利用 `withXcodeProject` 等内置配置插件来最大限度地减少文件读取和解析的次数。
¥Utilize built-in config plugins like `withXcodeProject` to minimize the amount of times a file is read and parsed.
* 坚持使用预构建内部使用的 XML 解析库,这有助于防止不必要地重新排列代码的更改。
¥Stick with the XML parsing libraries that prebuild uses internally, this helps prevent changes where code is rearranged needlessly.
## 插件结构和搭建
¥Plugin structure and scaffolding
### 版本控制
¥Versioning
默认情况下,`npx expo prebuild` 在与项目正在使用的 Expo SDK 版本关联的 [源模板][source-template] 上运行转换。SDK 版本在 app.json 中定义或从项目安装的 `expo` 版本推断。
¥By default, `npx expo prebuild` runs transformations on a [source template][source-template] associated with the Expo SDK version that a project is using. The SDK version is defined in the **app.json** or inferred from the installed version of `expo` that the project has.
例如,当 Expo SDK 升级到 React Native 的新版本时,模板可能会发生重大变化,以适应 React Native 或 Android 或 iOS 新版本的变化。
¥When Expo SDK upgrades to a new version of React Native for instance, the template may change significantly to account for changes in React Native or new releases of Android or iOS.
如果你的插件主要使用 [静态修改](#static-modification),那么它通常可以跨 SDK 版本正常运行。如果它使用正则表达式来转换应用代码,那么你肯定需要记录你的插件适用于哪个 Expo SDK 版本。在 SDK 发布周期中,有一个 [测试期](https://github.com/expo/expo/blob/main/guides/releasing/Release%20Workflow.md#stage-4---beta-release),你可以在新版本发布之前测试插件是否兼容新版本。
¥If your plugin is mostly using [static modifications](#static-modification) then it will usually work well across SDK versions. If it's using a regular expression to transform application code, then you'll definitely want to document which Expo SDK version your plugin is intended for. During the SDK release cycle, there is a [beta period](https://github.com/expo/expo/blob/main/guides/releasing/Release%20Workflow.md#stage-4---beta-release) where you can test if your plugin works with the new version before it's released.
### 插件属性
¥Plugin properties
属性用于自定义插件在预构建期间的工作方式。它们必须始终为静态值(无函数或 promise)。考虑以下类型:
¥Properties are used to customize the way a plugin works during prebuild. They must always be static values (no functions, or promises). Consider the following types:
```ts
type StaticValue = boolean | number | string | null | StaticArray | StaticObject;
type StaticArray = StaticValue[];
interface StaticObject {
[key: string]: StaticValue | undefined;
}
```
需要静态属性,因为应用配置必须可序列化为 JSON 才能用作应用清单。
¥Static properties are required because the app config must be serializable to JSON for use as the app manifest.
如果可能的话,尝试让你的插件在没有 props 的情况下工作,这将有助于像 [`expo install`](#expo-install) 或 [VS Code Expo 工具][vscode-expo] 这样的解析工具更好地工作。请记住,你添加的每个属性都会增加复杂性,使其将来更难更改,并增加你需要测试的功能数量。在可行的情况下,良好的默认值优于强制配置。
¥If possible, attempt to make your plugin work without props, this will help resolution tooling like [`expo install`](#expo-install) or [VS Code Expo Tools][vscode-expo] work better. Remember that every property you add increases complexity, making it harder to change in the future and increases the amount of features you'll need to test. Good default values are preferred over mandatory configuration when feasible.
## 开发环境
¥Development environment
### 工具
¥Tooling
我们强烈建议安装 [Expo 工具 VS Code 扩展][vscode-expo],因为这将对插件执行自动验证并显示错误信息以及配置插件开发的其他生活质量改进。
¥We highly recommend installing the [Expo Tools VS Code extension][vscode-expo]
as this will perform automatic validation on the plugins and surface error information along with other quality of life improvements for Config Plugin development.
### 设置 Playground 环境
¥Set up a playground environment
你可以使用 JS 轻松开发插件,但如果你想设置 Jest 测试并使用 TypeScript,则需要一个 monorepo。
¥You can develop plugins easily using JS, but if you want to set up Jest tests and use TypeScript, you will want a monorepo.
monorepo 将使你能够处理节点模块并将其导入你的应用配置中,就像将其发布到 npm 一样。Expo 配置插件内置了完整的 monorepo 支持,因此你只需设置一个项目即可。
¥A monorepo will enable you to work on a node module and import it in your app config like you would if it were published to npm. Expo config plugins have full monorepo support built-in so all you need to do is set up a project.
在你的 monorepo 的 `packages/` 目录中,创建一个模块,并在其中创建 [引导配置插件](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin)。
¥In your monorepo's `packages/` directory, create a module, and [bootstrap a config plugin](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin) in it.
### 手动运行插件
¥Manually run a plugin
如果你不习惯设置 monorepo,可以尝试手动运行插件:
¥If you aren't comfortable setting up a monorepo, you can try manually running a plugin:
* 使用配置插件运行包中的 `npm pack`
¥Run `npm pack` in the package with the config plugin
* 在你的测试项目中,运行 `npm install path/to/react-native-my-package-1.0.0.tgz`,这会将包添加到你的 package.json `dependencies` 对象中。
¥In your test project, run `npm install path/to/react-native-my-package-1.0.0.tgz`, this will add the package to your **package.json** `dependencies` object.
* 将包添加到 app.json 中的 `plugins` 数组中:`{ "plugins": ["react-native-my-package"] }`
¥Add the package to the `plugins` array in your **app.json**: `{ "plugins": ["react-native-my-package"] }`
* 如果你安装了 [VS Code Expo 工具][vscode-expo],自动补齐功能应该适用于该插件。
¥If you have [VS Code Expo Tools][vscode-expo] installed, autocomplete should work for the plugin.
* 如果需要更新包,请更改包的 package.json 中的 `version` 并重复该过程。
¥If you need to update the package, change the `version` in the package's **package.json** and repeat the process.
## 使用插件修改原生文件
¥Modifying native files with plugins
### 修改 AndroidManifest.xml
¥Modify AndroidManifest.xml
在使用配置插件之前,包应尝试使用内置的 AndroidManifest.xml [合并系统](https://developer.android.com/studio/build/manage-manifests)。这可用于静态的、非可选的功能,例如权限。这将确保功能在构建时合并,而不是在预构建时合并,从而最大限度地减少用户因忘记预构建而导致配置丢失的可能性。缺点是用户无法使用 [introspection](#introspection) 预览更改并调试任何潜在问题。
¥Packages should attempt to use the built-in **AndroidManifest.xml** [merging system](https://developer.android.com/studio/build/manage-manifests) before using a config plugin. This can be used for static, non-optional features like permissions. This will ensure features are merged during build-time and not prebuild-time, which minimizes the possibility of the configuration being missed due to users forgetting to prebuild. The drawback is that users cannot use [introspection](#introspection) to preview the changes and debug any potential issues.
下面是一个包的 AndroidManifest.xml 示例,它注入了所需的权限:
¥Here is an example of a package's **AndroidManifest.xml**, which injects a required permission:
```xml AndroidManifest.xml
```
如果你正在为本地项目构建插件,或者你的包需要更多控制,那么你应该实现一个插件。
¥If you're building a plugin for your local project, or if your package needs more control, then you should implement a plugin.
你可以使用内置类型和助手来简化处理复杂对象的过程。下面是将 `` 添加到默认 `` 的示例。
¥You can use built-in types and helpers to ease the process of working with complex objects.
Here's an example of adding a `` to the default ``.
```ts my-config-plugin.ts
import { AndroidConfig, ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
import { ExpoConfig } from 'expo/config';
// Using helpers keeps error messages unified and helps cut down on XML format changes.
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;
export const withMyCustomConfig: ConfigPlugin = config => {
return withAndroidManifest(config, async config => {
// Modifiers can be async, but try to keep them fast.
config.modResults = await setCustomConfigAsync(config, config.modResults);
return config;
});
};
// Splitting this function out of the mod makes it easier to test.
async function setCustomConfigAsync(
config: Pick,
androidManifest: AndroidConfig.Manifest.AndroidManifest
): Promise {
const appId = 'my-app-id';
// Get the tag and assert if it doesn't exist.
const mainApplication = getMainApplicationOrThrow(androidManifest);
addMetaDataItemToMainApplication(
mainApplication,
// value for `android:name`
'my-app-id-key',
// value for `android:value`
appId
);
return androidManifest;
}
```
### 修改 Info.plist
¥Modify Info.plist
使用 `withInfoPlist` 比静态修改 app.json 中的 `expo.ios.infoPlist` 对象更安全,因为它会读取 Info.plist 的内容并将其与 `expo.ios.infoPlist` 合并,这意味着你可以尝试防止更改被覆盖。
¥Using the `withInfoPlist` is a bit safer than statically modifying the `expo.ios.infoPlist` object in the **app.json** because it reads the contents of the Info.plist and merges it with the `expo.ios.infoPlist`, this means you can attempt to keep your changes from being overwritten.
下面是将 `GADApplicationIdentifier` 添加到 Info.plist 的示例:
¥Here's an example of adding a `GADApplicationIdentifier` to the **Info.plist**:
```ts my-config-plugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins';
// Pass `` to specify that this plugin requires a string property.
export const withCustomConfig: ConfigPlugin = (config, id) => {
return withInfoPlist(config, config => {
config.modResults.GADApplicationIdentifier = id;
return config;
});
};
```
### 修改 iOS Podfile
¥Modify iOS Podfile
iOS Podfile 是 CocoaPods(iOS 上的依赖管理器)的配置文件。它类似于 iOS 的 package.json。Podfile 是一个 Ruby 文件,这意味着你无法从 Expo 配置插件安全地修改它,而应该选择另一种方法,例如 [Expo 自动链接](/modules/autolinking) 钩子。
¥The iOS **Podfile** is the config file for CocoaPods, the dependency manager on iOS. It is similar to **package.json** for iOS.
The **Podfile** is a Ruby file, which means you **cannot** safely modify it from Expo config plugins and should opt for another approach, such as [Expo Autolinking](/modules/autolinking) hooks.
我们确实公开了一种与 Podfile 安全交互的机制,但它非常有限。版本化的 [模板 Podfile](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum/ios/Podfile) 被硬编码为从静态 JSON 文件 Podfile.properties.json 中读取,我们公开了一个 mod (`ios.podfileProperties`, `withPodfileProperties`) 来安全地从此文件中读取和写入。这由 [expo-build-properties](/versions/latest/sdk/build-properties) 使用并配置 JavaScript 引擎。
¥We do expose one mechanism for safely interacting with the Podfile, but it's very limited.
The versioned [template Podfile](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum/ios/Podfile) is hard coded to read
from a static JSON file **Podfile.properties.json**, we expose a mod (`ios.podfileProperties`, `withPodfileProperties`) to safely read and write from this file.
This is used by [expo-build-properties](/versions/latest/sdk/build-properties) and to configure the JavaScript engine.
### 添加插件到 `pluginHistory`
¥Add plugins to `pluginHistory`
创建 `_internal.pluginHistory` 是为了防止在从旧版未版本插件迁移到版本化插件时运行重复插件。
¥`_internal.pluginHistory` was created to prevent duplicate plugins from running while migrating from legacy UNVERSIONED plugins to versioned plugins.
```ts my-config-plugin.ts
import { ConfigPlugin, createRunOncePlugin } from 'expo/config-plugins';
// Keeping the name, and version in sync with it's package.
const pkg = require('my-cool-plugin/package.json');
const withMyCoolPlugin: ConfigPlugin = config => config;
// A helper method that wraps `withRunOnce` and appends items to `pluginHistory`.
export default createRunOncePlugin(
// The plugin to guard.
withMyCoolPlugin,
// An identifier used to track if the plugin has already been run.
pkg.name,
// Optional version property, if omitted, defaults to UNVERSIONED.
pkg.version
);
```
### 配置 Android 应用启动
¥Configure Android app startup
你可能会发现你的项目需要在 JS 引擎启动之前进行配置。例如,在 Android 上的 `expo-splash-screen` 中,我们需要在 MainActivity.java 的 `onCreate` 方法中指定调整大小模式。我们没有尝试通过危险的 mod 将这些更改危险地正则表达式到 `MainActivity` 中,而是使用生命周期钩子和静态设置系统来安全地确保该功能在所有受支持的 Android 语言(Java、Kotlin)、Expo 版本以及 配置插件。
¥You may find that your project requires configuration to be setup before the JS engine has started.
For example, in `expo-splash-screen` on Android, we need to specify the resize mode in the **MainActivity.java**'s `onCreate` method.
Instead of attempting to dangerously regex these changes into the `MainActivity` via a dangerous mod, we use a system of lifecycle hooks and static settings
to safely ensure the feature works across all supported Android languages (Java, Kotlin), versions of Expo, and combination of config plugins.
该系统由三个组件组成:
¥This system is made up of three components:
* `ReactActivityLifecycleListeners`:`expo-modules-core` 公开的接口,用于在调用项目 `ReactActivity` 的 `onCreate` 方法时获取原生回调。
¥`ReactActivityLifecycleListeners`: An interface exposed by `expo-modules-core` to get a native callback when the project `ReactActivity`'s `onCreate` method is invoked.
* `withStringsXml`:`expo/config-plugins` 公开的 mod,它将属性写入 Android strings.xml 文件,该库可以安全地读取 strings.xml 值并进行初始设置。字符串 XML 值遵循指定的格式以保持一致性。
¥`withStringsXml`: A mod exposed by `expo/config-plugins` which writes a property to the Android **strings.xml** file, the library can safely read the strings.xml value and do initial setup. The string XML values follow a designated format for consistency.
* `SingletonModule`(可选):`expo-modules-core` 公开的接口,用于在原生模块和 `ReactActivityLifecycleListeners` 之间创建共享接口。
¥`SingletonModule` (optional): An interface exposed by `expo-modules-core` to create a shared interface between native modules and `ReactActivityLifecycleListeners`.
考虑这个例子:我们希望在调用 `onCreate` 方法后立即将自定义 "value" 字符串设置为 Android `Activity` 上的属性。我们可以通过创建节点模块 `expo-custom`、实现 `expo-modules-core` 和 Expo 配置插件来安全地做到这一点:
¥Consider this example: We want to set a custom "value" string to a property on the Android `Activity`, directly after the `onCreate` method was invoked.
We can do this safely by creating a node module `expo-custom`, implementing `expo-modules-core`, and Expo config plugins:
首先,我们在 Android 原生模块中注册 `ReactActivity` 监听器,只有当用户在其项目中设置了 `expo-modules-core` 支持时才会调用此监听器(默认在使用 Expo CLI、Create React Native App、Ignite CLI 和 Expo 预构建引导的项目中) 。
¥First, we register the `ReactActivity` listener in our Android native module, this will only be invoked if the user has `expo-modules-core` support, setup in their project (default in projects bootstrapped with Expo CLI, Create React Native App, Ignite CLI, and Expo prebuilding).
```kotlin expo-custom/android/src/main/java/expo/modules/custom/CustomPackage.kt
package expo.modules.custom
import android.content.Context
import expo.modules.core.BasePackage
import expo.modules.core.interfaces.ReactActivityLifecycleListener
class CustomPackage : BasePackage() {
override fun createReactActivityLifecycleListeners(activityContext: Context): List {
return listOf(CustomReactActivityLifecycleListener(activityContext))
}
// ...
}
```
接下来我们实现 `ReactActivity` 监听器,它传递给 `Context` 并且能够从项目 strings.xml 文件中读取。
¥Next we implement the `ReactActivity` listener, this is passed the `Context` and is capable of reading from the project **strings.xml** file.
```kotlin expo-custom/android/src/main/java/expo/modules/custom/CustomReactActivityLifecycleListener.kt
package expo.modules.custom
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import expo.modules.core.interfaces.ReactActivityLifecycleListener
class CustomReactActivityLifecycleListener(activityContext: Context) : ReactActivityLifecycleListener {
override fun onCreate(activity: Activity, savedInstanceState: Bundle?) {
// Execute static tasks before the JS engine starts.
// These values are defined via config plugins.
var value = getValue(activity)
if (value != "") {
// Do something to the Activity that requires the static value...
}
}
// Naming is node module name (`expo-custom`) plus value name (`value`) using underscores as a delimiter
// i.e. `expo_custom_value`
// `@expo/vector-icons` + `iconName` -> `expo__vector_icons_icon_name`
private fun getValue(context: Context): String = context.getString(R.string.expo_custom_value).toLowerCase()
}
```
我们必须定义默认的 string.xml 值,用户将通过在 strings.xml 文件中使用相同的 `name` 属性在本地覆盖这些值。
¥We must define default **string.xml** values which the user will overwrite locally by using the same `name` property in their **strings.xml** file.
```xml expo-custom/android/src/main/res/values/strings.xml
```
此时,裸用户可以通过在本地 strings.xml 文件中创建字符串来配置此值(假设他们也有 `expo-modules-core` 支持设置):
¥At this point, bare users can configure this value by creating a string in their local **strings.xml** file (assuming they also have `expo-modules-core` support setup):
```xml ./android/app/src/main/res/values/strings.xml
I Love Expo
```
对于托管用户,我们可以通过 Expo 配置插件公开此功能(安全!):
¥For managed users, we can expose this functionality (safely!) via an Expo config plugin:
```js expo-custom/app.plugin.js
const { AndroidConfig, withStringsXml } = require('expo/config-plugins');
function withCustom(config, value) {
return withStringsXml(config, config => {
config.modResults = setStrings(config.modResults, value);
return config;
});
}
function setStrings(strings, value) {
// Helper to add string.xml JSON items or overwrite existing items with the same name.
return AndroidConfig.Strings.setStringItem(
[
// XML represented as JSON
// value
{ $: { name: 'expo_custom_value', translatable: 'false' }, _: value },
],
strings
);
}
```
托管 Expo 用户现在可以与此 API 进行交互,如下所示:
¥Managed Expo users can now interact with this API like so:
```json app.json
{
"expo": {
"plugins": [["expo-custom", "I Love Expo"]]
}
}
```
通过重新运行 `npx expo prebuild -p`(`eas build -p android` 或 `npx expo run:ios`),用户现在可以看到更改,并安全地应用到他们的托管项目中!
¥By re-running `npx expo prebuild -p` (`eas build -p android`, or `npx expo run:ios`) the user can now see the changes, safely applied in their managed project!
正如你从示例中看到的,我们严重依赖应用代码 (expo-modules-core) 与应用代码(原生项目)进行交互。这确保了我们的配置插件安全可靠,希望在很长一段时间内都是安全可靠的!
¥As you can see from the example, we rely heavily on application code (expo-modules-core) to interact with application code (the native project). This ensures that our config plugins are safe and reliable, hopefully for a very long time!
## 调试配置插件
¥Debugging config plugins
你可以通过运行 `EXPO_DEBUG=1 expo prebuild` 来调试配置插件。如果启用了 `EXPO_DEBUG`,将打印插件堆栈日志,这些日志对于查看运行了哪些 mod 以及它们运行的顺序非常有用。要查看所有静态插件解析错误,请启用 `EXPO_CONFIG_PLUGIN_VERBOSE_ERRORS`,只有插件作者需要这样做。默认情况下,一些自动插件错误是隐藏的,因为它们通常与版本控制问题相关并且不是很有帮助(也就是说,旧版包还没有配置插件)。
¥You can debug config plugins by running `EXPO_DEBUG=1 expo prebuild`. If `EXPO_DEBUG` is enabled, the plugin stack logs will be printed, these are useful for viewing which mods ran, and in what order they ran in. To view all static plugin resolution errors, enable `EXPO_CONFIG_PLUGIN_VERBOSE_ERRORS`, this should only be needed for plugin authors. By default, some automatic plugin errors are hidden because they're usually related to versioning issues and aren't very helpful (that is, legacy package doesn't have a config plugin yet).
运行 `npx expo prebuild --clean` 将在编译之前删除生成的原生目录。
¥Running `npx expo prebuild --clean` will remove the generated native directories before compiling.
你还可以运行 `npx expo config --type prebuild` 来打印未评估 mod 的插件结果(不生成代码)。
¥You can also run `npx expo config --type prebuild` to print the results of the plugins with the mods unevaluated (no code is generated).
可以使用 `EXPO_PROFILE=1` 来分析 Expo CLI 命令。
¥Expo CLI commands can be profiled using `EXPO_PROFILE=1`.
## 内省
¥Introspection
内省是一种高级的技术,用于读取修饰符的评估结果,而无需在项目中生成任何代码。这可用于快速调试 [静态修改](#static-modification) 的结果,而无需运行预构建。你可以使用 [预览功能](https://github.com/expo/vscode-expo#expo-preview-modifier) 或 `vscode-expo` 与内省进行实时交互。
¥Introspection is an advanced technique used to read the evaluated results of modifiers without generating any code in the project.
This can be used to quickly debug the results of [static modifications](#static-modification) without needing to run prebuild.
You can interact with introspection live, by using the [preview feature](https://github.com/expo/vscode-expo#expo-preview-modifier) of `vscode-expo`.
你可以通过在项目中运行 `expo config --type introspect` 来尝试内省。
¥You can try introspection by running `expo config --type introspect` in a project.
内省仅支持修饰符的子集:
¥Introspection only supports a subset of modifiers:
* `android.manifest`
* `android.gradleProperties`
* `android.strings`
* `android.colors`
* `android.colorsNight`
* `android.styles`
* `ios.infoPlist`
* `ios.entitlements`
* `ios.expoPlist`
* `ios.podfileProperties`
> 自省仅适用于安全修饰符(静态文件,如 JSON、XML、plist、属性),但 `ios.xcodeproj` 除外,它通常需要更改文件系统,因此不具有幂等性。
>
> ¥Introspection only works on safe modifiers (static files like JSON, XML, plist, properties), except `ios.xcodeproj` which often requires file system changes, making it non idempotent.
内省通过创建自定义基本 mod 来工作,这些自定义基本 mod 的工作方式与默认基本 mod 类似,只是它们最后不会将 `modResults` 写入磁盘。他们没有保留结果,而是将结果保存到 `_internal.modResults` 下的应用配置中,后跟 mod 的名称,例如 `ios.infoPlist` mod 保存到 `_internal.modResults.ios.infoPlist: {}`。
¥Introspection works by creating custom base mods that work like the default base mods, except they don't write the `modResults` to disk at the end.
Instead of persisting, they save the results to the app config under `_internal.modResults`, followed by the name of the mod
such as the `ios.infoPlist` mod saves to `_internal.modResults.ios.infoPlist: {}`.
作为一个现实世界的示例,`eas-cli` 使用内省来确定托管应用中的最终 iOS 权利,因此它可以在构建之前将它们与 Apple 开发者门户同步。Introspection 还可以用作方便的调试和开发工具。
¥As a real-world example, introspection is used by `eas-cli` to determine what the final iOS entitlements will be in a managed app, so it can sync them with the Apple Developer Portal before building. Introspection can also be used as a handy debugging and development tool.
## 旧版插件
¥Legacy plugins
为了使 `eas build` 与经典 `expo build` 服务一样工作,我们添加了对 "旧版插件" 的支持,当它们安装在项目中时,它们会自动应用于项目。
¥To make `eas build` work the same as the classic `expo build` service, we added support for "legacy plugins" which are applied automatically to a project when they're installed in the project.
例如,假设一个项目安装了 `expo-camera`,但其 app.json 中没有 `plugins: ['expo-camera']`。Expo CLI 会自动将 `expo-camera` 添加到插件中,以确保将所需的摄像头和麦克风权限添加到项目中。用户仍然可以通过手动将 `expo-camera` 插件添加到 `plugins` 数组来自定义 `expo-camera` 插件,并且手动定义的插件将优先于自动插件。
¥For instance, say a project has `expo-camera` installed but doesn't have `plugins: ['expo-camera']` in their **app.json**.
Expo CLI would automatically add `expo-camera` to the plugins to ensure that the required camera and microphone permissions are added to the project.
The user can still customize the `expo-camera` plugin by adding it to the `plugins` array manually, and the manually defined plugins will take precedence over the automatic plugins.
你可以通过运行 `expo config --type prebuild` 并查看 `_internal.pluginHistory` 属性来调试添加了哪些插件。
¥You can debug which plugins were added by running `expo config --type prebuild` and seeing the `_internal.pluginHistory` property.
这将显示一个对象,其中包含使用 `expo/config-plugins` 中的 `withRunOnce` 插件添加的所有插件。
¥This will show an object with all plugins that were added using `withRunOnce` plugin from `expo/config-plugins`.
请注意,`expo-location` 使用 `version: '11.0.0'`,`react-native-maps` 使用 `version: 'UNVERSIONED'`。这意味着以下内容:
¥Notice that `expo-location` uses `version: '11.0.0'`, and `react-native-maps` uses `version: 'UNVERSIONED'`. This means the following:
* `expo-location` 和 `react-native-maps` 都安装在项目中。
¥`expo-location` and `react-native-maps` are both installed in the project.
* `expo-location` 正在使用项目 `node_modules/expo-location/app.plugin.js` 中的插件
¥`expo-location` is using the plugin from the project's `node_modules/expo-location/app.plugin.js`
* 项目中安装的 `react-native-maps` 版本没有插件,因此它依靠 `expo-cli` 附带的未版本化插件来提供旧版支持。
¥The version of `react-native-maps` installed in the project doesn't have a plugin, so it's falling back on the unversioned plugin that is shipped with `expo-cli` for legacy support.
```json
{
_internal: {
pluginHistory: {
'expo-location': {
name: 'expo-location',
version: '11.0.0',
},
'react-native-maps': {
name: 'react-native-maps',
version: 'UNVERSIONED',
},
},
},
};
```
为了获得最稳定的体验,你应该尝试在项目中没有 `UNVERSIONED` 插件。这是因为 `UNVERSIONED` 插件可能不支持你项目中的原生代码。例如,假设你的项目中有一个 `UNVERSIONED` Facebook 插件,如果 Facebook 原生代码或插件有重大更改,这将破坏你的项目预构建方式并导致构建时出错。
¥For the most *stable* experience, you should try to have no `UNVERSIONED` plugins in your project. This is because the `UNVERSIONED` plugin may not support the native code in your project.
For instance, say you have an `UNVERSIONED` Facebook plugin in your project, if the Facebook native code or plugin has a breaking change, that will break the way your project prebuilds and cause it to error on build.
## 静态修改
¥Static modification
插件可以使用正则表达式转换应用代码,但如果模板随着时间的推移而发生变化,那么这些修改就会很危险,那么正则表达式将变得难以预测(同样,如果用户手动修改文件或使用自定义模板)。以下是一些不应手动修改的文件示例以及替代方案。
¥Plugins can transform application code with regular expressions, but these modifications are dangerous if the template changes over time then the regex becomes hard to predict (similarly if the user modifies a file manually or uses a custom template). Here are some examples of files you shouldn't modify manually, and alternatives.
### Android Gradle 文件
¥Android Gradle Files
Gradle 文件是用 Groovy 或 Kotlin 编写的。它们用于管理 Android 应用中的依赖、版本控制和其他设置。不要直接使用 `withProjectBuildGradle`、`withAppBuildGradle` 或 `withSettingsGradle` mods 修改它们,而是使用静态 `gradle.properties` 文件。
¥Gradle files are written in either Groovy or Kotlin. They are used to manage dependencies, versioning, and other settings in the Android app.
Instead of modifying them directly with the `withProjectBuildGradle`, `withAppBuildGradle`, or `withSettingsGradle` mods, utilize the static `gradle.properties` file.
`gradle.properties` 是一个静态键/值对,groovy 文件可以从中读取。例如,假设你想控制 Groovy 中的某些切换:
¥The `gradle.properties` is a static key/value pair that groovy files can read from. For example, say you wanted to control some toggle in Groovy:
```properties gradle.properties
# @info Safely modified using the withGradleProperties() mod. #
expo.react.jsEngine=hermes
# @end #
```
然后在 Gradle 文件中:
¥Then later in a Gradle file:
```groovy app/build.gradle
```
* 对于 `gradle.properties` 中的键,使用驼峰式大小写,以 `.` 分隔,并且通常以 `expo` 前缀开头,表示该属性由预构建管理。
¥For keys in the `gradle.properties`, use camel case separated by `.`s, and usually starting with the `expo` prefix to denote that the property is managed by prebuild.
* 要访问该属性,请使用两种全局方法之一:
¥To access the property, use one of two global methods:
* `property`:获取一个属性,如果该属性未定义则抛出错误。
¥`property`: Get a property, throw an error if the property is not defined.
* `findProperty`:如果属性丢失,则获取属性而不引发错误。这通常可以与 `?:` 运算符一起使用来提供默认值。
¥`findProperty`: Get a property without throwing an error if the property is missing. This can often be used with the `?:` operator to provide a default value.
一般来说,你应该只通过 Expo [自动链接][autolinking] 与 Gradle 文件交互,这提供了与项目文件的编程接口。
¥Generally, you should only interact with the Gradle file via Expo [Autolinking][autolinking], this provides a programmatic interface with the project files.
### iOS 应用代理
¥iOS AppDelegate
某些模块可能需要向项目的 AppDelegate 添加委托方法。这可以通过使用 [AppDelegate 订阅者](/modules/appdelegate-subscribers/) 安全地完成,也可以通过 `withAppDelegate` 模块(强烈不建议)危险地完成。使用 AppDelegate 订阅者允许原生 Expo 模块以安全可靠的方式响应重要事件。
¥Some modules may need to add delegate methods to the project AppDelegate. This can be done safely by using [AppDelegate subscribers](/modules/appdelegate-subscribers/) or dangerously via the `withAppDelegate` mod (*strongly discouraged*).
Using AppDelegate subscribers allows native Expo modules to react to important events in a safe and reliable way.
以下是一些 AppDelegate 订阅者的实际示例。此外,你还可以在 GitHub ([一个这样的例子](https://github.com/bamlab/react-native-app-security/blob/c1a861cbd348f404ec18ffae90d1c9bdc66bc00d/ios/RNASAppLifecyleDelegate.swift)) 的社区代码库中找到许多示例。
¥Below are some examples of the AppDelegate subscribers in action. Additionally, you will find many examples in community repositories on GitHub ([one such example](https://github.com/bamlab/react-native-app-security/blob/c1a861cbd348f404ec18ffae90d1c9bdc66bc00d/ios/RNASAppLifecyleDelegate.swift)).
* `expo-linking`:[**LinkingAppDelegateSubscriber.swift**](https://github.com/expo/expo/blob/b4ca25a4319d7148258ebd5121d1df40a3b1333e/packages/expo-linking/ios/LinkingAppDelegateSubscriber.swift#L14) (openURL)
* `expo-notifications`:[**NotificationsAppDelegateSubscriber.swift**](https://github.com/expo/expo/blob/bd469e421856f348d539b1b57325890147935dbc/packages/expo-notifications/ios/EXNotifications/PushToken/EXPushTokenManager.m) (didRegisterForRemoteNotificationsWithDeviceToken、didFailToRegisterForRemoteNotificationsWithError、didReceiveRemoteNotification)
### iOS CocoaPods Podfile
Podfile 可以使用正则表达式进行自定义(这被认为是危险的,因为这些类型的更改无法很好地组合,并且多个更改可能会发生冲突),但更可靠的方法是在名为 Podfile.properties.json 的 JSON 文件中设置配置值。请参阅下文如何使用 `podfile_properties` 自定义 Podfile:
¥The **Podfile** can be customized with a regular expression (this is considered dangerous because these types of changes do not compose well and multiple changes are likely to collide), but it's more reliable to instead set configuration values in JSON file called **Podfile.properties.json**. See how `podfile_properties` is used to customize the **Podfile** below:
```ruby Podfile
require 'json'
# @info Import a JSON file and parse it in Ruby #
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
# @end #
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
target 'yolo27' do
use_expo_modules!
# ...
# podfile_properties['your_property']
end
```
一般来说,你应该只通过 Expo [自动链接][autolinking] 与 Podfile 交互,这提供了与项目文件的编程接口。
¥Generally, you should only interact with the Podfile via Expo [Autolinking][autolinking], this provides a programmatic interface with the project files.
### 自定义基础修饰符
¥Custom base modifiers
Expo CLI `npx expo prebuild` 命令使用 [`@expo/prebuild-config`][prebuild-config] 来获取默认的基本修饰符。这些默认值仅管理公共文件的子集,如果你想管理自定义文件,你可以通过添加新的基本修饰符在本地执行此操作。
¥The Expo CLI `npx expo prebuild` command uses [`@expo/prebuild-config`][prebuild-config] to get the default base modifiers. These defaults only manage a subset of common files, if you want to manage custom files you can do that locally by adding new base modifiers.
例如,假设你想要添加对管理 `ios/*/AppDelegate.h` 文件的支持,你可以通过添加 `ios.appDelegateHeader` 修饰符来实现。
¥For example, say you wanted to add support for managing the `ios/*/AppDelegate.h` file, you could do this by adding a `ios.appDelegateHeader` modifier.
> 此示例使用 `tsx` 来实现简单的本地 TypeScript 支持,这并不是绝对必要的。[了解更多](/guides/typescript/#appconfigjs)。
>
> ¥This example uses `tsx` for simple local TypeScript support, this isn't strictly necessary. [Learn more](/guides/typescript/#appconfigjs).
```ts withAppDelegateHeaderBaseMod.ts
import { ConfigPlugin, IOSConfig, Mod, withMod, BaseMods } from 'expo/config-plugins';
import fs from 'fs';
/**
* A plugin which adds new base modifiers to the prebuild config.
*/
export function withAppDelegateHeaderBaseMod(config) {
return BaseMods.withGeneratedBaseMods<'appDelegateHeader'>(config, {
platform: 'ios',
providers: {
// Append a custom rule to supply AppDelegate header data to mods on `mods.ios.appDelegateHeader`
appDelegateHeader: BaseMods.provider({
// Get the local filepath that should be passed to the `read` method.
getFilePath({ modRequest: { projectRoot } }) {
const filePath = IOSConfig.Paths.getAppDelegateFilePath(projectRoot);
// Replace the .m with a .h
if (filePath.endsWith('.m')) {
return filePath.substr(0, filePath.lastIndexOf('.')) + '.h';
}
// Possibly a Swift project...
throw new Error(`Could not locate a valid AppDelegate.h at root: "${projectRoot}"`);
},
// Read the input file from the filesystem.
async read(filePath) {
return IOSConfig.Paths.getFileInfo(filePath);
},
// Write the resulting output to the filesystem.
async write(filePath: string, { modResults: { contents } }) {
await fs.promises.writeFile(filePath, contents);
},
}),
},
});
}
/**
* (Utility) Provides the AppDelegate header file for modification.
*/
export const withAppDelegateHeader: ConfigPlugin> = (
config,
action
) => {
return withMod(config, {
platform: 'ios',
mod: 'appDelegateHeader',
action,
});
};
// (Example) Log the contents of the modifier.
export const withSimpleAppDelegateHeaderMod = config => {
return withAppDelegateHeader(config, config => {
console.log('modify header:', config.modResults);
return config;
});
};
```
要使用这个新的基本模组,请将其添加到插件数组中。基本 mod 必须在使用该 mod 的所有其他插件之后最后添加,这是因为它必须在进程结束时将结果写入磁盘。
¥To use this new base mod, add it to the plugins array. The base mod **MUST** be added last after all other plugins that use the mod, this is because it must write the results to disk at the end of the process.
```js app.config.js
// Required for external files using TS
require('tsx/cjs');
import {
withAppDelegateHeaderBaseMod,
withSimpleAppDelegateHeaderMod,
} from './withAppDelegateHeaderBaseMod.ts';
export default ({ config }) => {
if (!config.plugins) config.plugins = [];
config.plugins.push(
withSimpleAppDelegateHeaderMod,
// Base mods MUST be last
withAppDelegateHeaderBaseMod
);
return config;
};
```
有关详细信息,请参阅 [增加支持的 PR](https://github.com/expo/expo-cli/pull/3852) 了解此功能。
¥For more info, see [the PR that adds support](https://github.com/expo/expo-cli/pull/3852) for this feature.
## expo 安装
¥expo install
使用 `npx expo install` 命令安装 Node 模块时,如果它包含配置插件,它将自动添加到项目的应用配置中。这使得设置更容易,并有助于防止用户忘记添加插件。但是,这确实有一些注意事项:
¥When a node module is installed with the `npx expo install` command, if it includes a config plugin, it will be added to the project's app config automatically. This makes setup easier and helps prevent users from forgetting to add a plugin. However, this does come with a couple of caveats:
1. `npx expo install` 仅使用根 app.config.js 文件自动将配置插件添加到应用清单中。添加此规则是为了防止将 `lodash` 等流行软件包误认为是配置插件并破坏预构建。
¥`npx expo install` only adds config plugins using the root **app.config.js** file automatically to the app manifest. This rule was added to prevent popular packages like `lodash` from being mistaken for a config plugin and breaking the prebuild.
2. 目前没有机制可以检测配置插件是否具有强制属性。因此,`expo install` 只会添加插件,而不会尝试添加任何额外的属性。例如,`expo-camera` 包含可选的额外属性,因此 `plugins: ['expo-camera']` 有效,但如果它包含必需的属性,则 `expo-camera` 会抛出错误。
¥There is currently no mechanism for detecting if a config plugin has mandatory props. Because of this, `expo install` will only add the plugin, and not attempt to add any extra props. For example, `expo-camera` has optional extra props, so `plugins: ['expo-camera']` is valid, but if it had mandatory props, then `expo-camera` would throw an error.
3. 仅当用户的项目使用静态应用配置(app.json 和 app.config.json)时,才能自动添加插件。如果用户在带有 app.config.js 的项目中运行 `expo install expo-camera`,他们会看到如下警告:
¥Plugins can only be automatically added when the user's project uses a static app config (**app.json** and **app.config.json**). If the user runs `expo install expo-camera` in a project with an **app.config.js**, they'll see a warning like:
```sh
Cannot automatically write to dynamic config at: app.config.js
Please add the following to your app config
{
"plugins": [
"expo-camera"
]
}
```
[config-docs]: /versions/latest/config/app/
[prebuild-config]: https://github.com/expo/expo-cli/tree/main/packages/prebuild-config#readme
[cli-prebuild]: /more/expo-cli/#prebuild
[source-template]: https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum
[vscode-expo]: https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools
[ems-plugin]: https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin
[memfs]: https://www.npmjs.com/package/memfs
[autolinking]: /more/glossary-of-terms/#autolinking
## 使用 patch-project
了解如何使用 patch-project 在 Expo 项目中创建、生成、应用和保存原生更改。
> **important** 注意:`patch-project` 是一项实验性功能。
>
> ¥**Note**: `patch-project` is an experimental feature.
`patch-package` 是一款 Expo 配置插件和命令行接口 (CLI) 工具,可在运行 `npx expo prebuild` 后生成并应用补丁以保留原生更改。此工具对于希望保留自定义设置而无需了解如何编写配置插件的原生应用开发者非常有用,它可以有效地生成与 [持续的原生生成 (CNG)](/workflow/continuous-native-generation/) 兼容的自动化解决方案。
¥`patch-package` is an Expo config plugin and command-line interface (CLI) tool that generates and applies patches to preserve native changes after running `npx expo prebuild`. This tool is useful for native app developers who want to preserve customizations without needing to know how to write a config plugin, effectively generating an automatic solution that works with [Continuous Native Generation (CNG)](/workflow/continuous-native-generation/).
本指南解释了如何使用 `patch-project`、何时使用它以及它的局限性。
¥This guide explains how to use `patch-project`, when to use it, and its limitations.
## patch-project 如何工作
¥How patch-project works
`patch-project` 使用一种受 Git 启发的方法来生成和自动应用补丁。使用此命令行工具需要在你的项目中执行以下步骤:
¥`patch-project` uses an approach to generate and automatically apply patches, which is inspired by Git. Using this command line tool requires the following steps in your project:
### 安装
¥Installation
要开始使用,你需要在项目中安装该工具:
¥To get started, you need to install the tool in your project:
```sh
$ npx expo install patch-project
```
此命令会自动将 `patch-project` 配置插件添加到你的 [应用配置](/workflow/configuration/):
¥This command will automatically add the `patch-project` config plugin to your [app config](/workflow/configuration/):
```json app.json
{
"expo": {
"plugins": [
"patch-project"
]
}
}
```
### 从现有自定义项生成补丁
¥Generate patches from existing customizations
假设你手动修改了项目中的原生目录(android 和 ios)。要为这些原生目录生成补丁,你可以运行以下命令:
¥Let's assume you manually modified native directories (**android** and **ios**) in your project. To generate patches for these native directories, you can run the following command:
```sh
$ npx patch-project
```
> **info** 注意:如果你想要为特定平台生成补丁,可以使用 `--platform` 选项并运行 `npx patch-project --platform android` 或 `npx patch-project --platform ios`。
>
> ¥**Note**: In scenarios where you want to generate patches for a specific platform, you can use the `--platform` option and run `npx patch-project --platform android` or `npx patch-project --platform ios`.
这些补丁生成后会保存在 cng-patches 目录中。
¥These patches, when generated, are saved in the **cng-patches** directory.
每个文件都将以平台名称作为前缀,后跟校验和值。例如:
¥Each file will be prefixed with a platform's name followed by a checksum value. For example:
```bash
ios+eee880ad7b07965271d2323f7057a2b4.patch
```
### 在预构建期间应用补丁
¥Apply patches during prebuild
生成补丁后,它们会在随后运行 `npx expo prebuild` 命令时自动应用。`patch-project` 配置插件可以检测现有补丁并应用它们来恢复你的自定义设置。
¥Once you have generated patches, they are automatically applied when subsequently running the `npx expo prebuild` command. The `patch-project` config plugin detects the existing patches and applies them to restore your customizations.
## 何时使用 patch-project
¥When to use patch-project
你可以在以下场景中使用 `patch-project`:
¥You can use `patch-project` in the following scenarios:
* 迁移现有的 React Native 应用(由于包含大量原生自定义内容而非常复杂),并将这些原生自定义内容重新创建为配置插件将非常耗时。
¥**Migrating existing React Native apps** that are complex because they contain extensive native customizations and re-creating these native customizations as config plugins would be time-consuming.
* 在 Expo 项目中过渡到采用持续原生生成 (CNG) 时,保留对 android 和/或 ios 目录所做的手动更改。
¥**Preserving manual changes** made to **android** and/or **ios** directories while transitioning to adopt Continuous Native Generation (CNG) in your Expo project.
* 在编写配置插件之前需要测试原生更改时,可以快速进行原型设计。
¥**Quick prototyping** when you need to test native changes before writing config plugins.
* 补丁将在后续运行 `npx expo prebuild` 命令时自动应用。这比 `patch-package`(通常用于为 npm 库生成补丁)等工具更有优势,因为后者不会在预构建过程中保留并自动应用补丁。
¥**Patches are applied automatically** when running the `npx expo prebuild` command subsequently. This is an advantage over tools like `patch-package` (commonly used for generating patches for npm libraries), which do not preserve and automatically apply patches during the prebuild process.
## 限制和注意事项
¥Limitations and considerations
补丁在 Expo SDK 版本升级期间可能会失效,原因如下:
¥Patches may become invalid during Expo SDK version upgrades because:
* 模板和/或文件结构更改:预构建模板会随着 SDK 版本的更新而不断演变,包括原生目录中的新变化和文件更新。这将影响 cng-patches 目录中已生成的 diff,这些 diff 可能不再适用。
¥**Template and/or file structure changes**: The prebuild template evolves between SDK versions with new changes and file updates in native directories. This will affect the already generated diff in the **cng-patches** directory, which may no longer apply.
* 插件冲突:CNG 补丁可能很危险,当其他插件修改相同的文件时,可能会中断。例如,如果你添加了一个新插件,它会更新 MainApplication.kt 文件,并且与你现有的补丁程序冲突,则补丁程序可能无法正确应用。在这种情况下,你可能需要重新生成补丁。
¥**Plugin conflicts**: CNG patches can be dangerous and may break when other plugins modify the same files. For example, if you add a new plugin that updates **MainApplication.kt** and conflicts with your existing patches, the patches may no longer apply correctly. In such cases, you may need to regenerate patches.
* iOS .pbxproj 变更:在 iOS 项目中,将补丁应用于 .pbxproj 文件可能很脆弱,因为该文件包含 UUID,而运行像 `npx expo prebuild --clean` 这样的命令可能会更改这些 ID。例如,如果你添加了一个小部件扩展或进行了其他项目配置更改,基于补丁程序的方法可能无法可靠地工作。你可以查看生成的 cng-patches/ios-* 文件,并仅保留必要的补丁。尽可能减少补丁程序的使用可以降低应用补丁程序时失败的风险。
¥**iOS .pbxproj changes**: In iOS projects, applying patches to **.pbxproj** files can be fragile since this file contains UUIDs and running a command like `npx expo prebuild --clean` can change these IDs. For example, if you're adding a widget extension or making other project configuration changes, patch-based approaches may not work reliably. You can review the generated **cng-patches/ios-*** and keep only the necessary patch. Having the patch as minimal as possible would reduce the risk of failures when applying patches.
建议每次 SDK 升级后重新生成补丁。
¥It is recommended to regenerate patches after each SDK upgrade.
# 调试
## 错误和警告
了解 Expo 项目中的 Redbox 错误和堆栈跟踪。
使用 Expo 开发应用时,你会遇到 Redbox 错误或 Yellowbox 警告。这些日志记录经验由 [React Native 中的日志框](https://rn.nodejs.cn/blog/2020/07/06/version-0.63) 提供。
¥When developing an application using Expo, you'll encounter a **Redbox** error or **Yellowbox** warning. These logging experiences are provided by [LogBox in React Native](https://rn.nodejs.cn/blog/2020/07/06/version-0.63).
## 红盒错误和黄盒警告
¥Redbox error and Yellowbox warning
当致命错误阻止你的应用运行时,会显示 Redbox 错误。将显示黄框警告,通知你可能存在问题,你应该在发布应用之前解决它。
¥A Redbox error is displayed when a fatal error prevents your app from running. A Yellowbox warning is displayed to inform you that there is a possible issue and you should probably resolve it before shipping your app.
你还可以使用 `console.warn("Warning message")` 和 `console.error("Error message")` 自行创建警告和错误。触发红框的另一种方法是抛出错误而不捕获它:`throw Error("Error message")`。
¥You can also create warnings and errors on your own with `console.warn("Warning message")` and `console.error("Error message")`. Another way to trigger the redbox is to throw an error and not catch it: `throw Error("Error message")`.
> 这是使用 Expo CLI 调试 React Native 应用的简要介绍。有关详细信息,请参阅 [调试](/debugging/runtime-issues/)。
>
> ¥This is a brief introduction to debugging a React Native app with Expo CLI. For in-depth information, see [Debugging](/debugging/runtime-issues/).
## 堆栈跟踪
¥Stack traces
当你在开发过程中遇到错误时,你将看到错误消息和堆栈跟踪,这是应用崩溃时最近进行的调用的报告。此堆栈跟踪会显示在你的终端和 Expo Go 应用中,或者如果你创建了开发版本。
¥When you encounter an error during development, you'll see the error message and a **stack trace**, which is a report of the recent calls your application made when it crashed. This stack trace is shown both in your terminal and the Expo Go app or if you have created a development build.
该堆栈跟踪非常有价值,因为它为你提供了错误发生的位置。例如,在下图中,错误来自文件 HomeScreen.js,并由该文件的第 7 行引起。
¥This stack trace is **extremely valuable** since it gives you the location of the error's occurrence. For example, in the following image, the error comes from the file **HomeScreen.js** and is caused on line 7 in that file.
当你查看该文件的第 7 行时,你将看到引用了一个名为 `renderDescription` 的变量。错误消息描述未找到该变量,因为该变量未在 HomeScreen.js 中声明。这是一个典型的例子,说明如果你花时间解读错误消息和堆栈跟踪,它们会有多大帮助。
¥When you look at that file, on line 7, you will see that a variable called `renderDescription` is referenced. The error message describes that the variable is not found because the variable is not declared in **HomeScreen.js**. This is a typical example of how helpful error messages and stack traces can be if you take the time to decipher them.
调试错误是开发中最令人沮丧但又令人满意的部分之一。请记住,你永远不会孤单。Expo 社区以及 React 和 React Native 社区是你遇到困难时提供帮助的重要资源。其他人很可能也遇到了与你相同的错误。请务必阅读文档,搜索 [forums](https://chat.expo.dev/)、[GitHub 问题](https://github.com/expo/expo/issues/) 和 [Stack Overflow](https://stackoverflow.com/)。
¥Debugging errors is one of the most frustrating but satisfying parts of development. Remember that you're never alone. The **Expo community** and the React and React Native communities are great resources for help when you get stuck. There's a good chance someone else has run into your exact error. Make sure to read the documentation, search the [forums](https://chat.expo.dev/), [GitHub issues](https://github.com/expo/expo/issues/), and [Stack Overflow](https://stackoverflow.com/).
## 调试运行时问题
了解可用于调试 Expo 项目的不同技术。
无论你是在本地开发应用、将其发送给选定的 Beta 测试人员,还是将应用实时发布到应用商店,你总会发现自己在调试问题。将错误分为两类很有用:
¥Whether you're developing your app locally, sending it out to select beta testers, or launching your app live to the app stores, you'll always find yourself debugging issues. It's useful to split errors into two categories:
* 开发中遇到的错误
¥Errors you encounter in the development
* 你(或你的用户)在生产中遇到的错误
¥Errors you (or your users) encounter in production
让我们来看看处理上述每种情况时的推荐做法。
¥Let's go through recommended practices when dealing with each of the above situations.
## 开发错误
¥Development errors
它们是你在开发应用时遇到的常见错误。深入研究它们并不总是那么简单。通常,在使用 [Expo CLI](/more/expo-cli/) 运行应用时进行调试就足够了。
¥They are common errors that you encounter while developing your app. Delving into them isn't always straightforward. Usually, debugging when running your app with [Expo CLI](/more/expo-cli/) is enough.
调试这些问题的一种方法是查看 [堆栈跟踪](/debugging/errors-and-warnings/#stack-traces)。但是,在某些情况下,仅查看堆栈跟踪还不够,因为跟踪的错误消息可能更加神秘。对于此类错误,请按照以下步骤操作:
¥One way you can debug these issues is by looking at the [stack trace](/debugging/errors-and-warnings/#stack-traces). However, in some scenarios, looking at the stack trace isn't enough as the error message traced might be a little more cryptic. For such errors, follow the steps below:
* 在 Google 和 [Stack Overflow](https://stackoverflow.com/questions) 中搜索错误消息,你可能不是第一个遇到此问题的人。
¥Search for the error message in Google and [Stack Overflow](https://stackoverflow.com/questions), it's likely you're not the first person to ever run into this.
* 隔离引发错误的代码。此步骤对于修复模糊错误至关重要。去做这个:
¥**Isolate the code that's throwing the error**. This step is *vital* in fixing obscure errors. To do this:
* 恢复到代码的工作版本。这甚至可能是一个完全空白的 `npx create-expo-app` 项目。
¥Revert to a working version of your code. This may even be a completely blank `npx create-expo-app` project.
* 一点一点地应用你最近的更改,直到它崩溃。
¥Apply your recent changes piece by piece, until it breaks.
* 如果你在每个 "piece" 中添加的代码很复杂,你可能希望简化你正在做的事情。例如,如果你使用 Redux 等状态管理库,你可以尝试将其从等式中完全删除,以查看问题是否出在你的状态管理中(这在 React 应用中很常见)。
¥If the code you're adding in each "piece" is complex, you may want to simplify what you're doing. For example, if you use a state management library such as Redux, you can try removing that from the equation completely to see if the issue lies in your state management (which is common in React apps).
* 这应该可以缩小错误的可能来源,并为你提供更多信息,以便你在互联网上搜索遇到相同问题的其他人。
¥This should narrow down the possible sources of the error, and provide you with more information to search the internet for others who have had the same problem.
* 使用断点(或 `console.log`)来检查并确保特定代码正在运行,或者变量具有特定值。使用 `console.log` 进行调试并不被认为是最佳实践,但是,它快速、简单,并且常常提供一些有启发性的信息。
¥Use breakpoints (or `console.log`s) to check and make sure a certain piece of code is being run, or that a variable has a certain value. Using `console.log` for debugging isn't considered the best practice, however, it's fast, easy, and oftentimes provides some illuminating information.
尽可能简化代码以追踪错误源是调试应用的好方法,并且它会变得更加容易。这就是为什么许多开源存储库在你提出问题时需要 [最小可重复示例](https://stackoverflow.com/help/minimal-reproducible-example)。它可确保你隔离问题并准确识别问题发生的位置。如果你的应用太大且太复杂,无法执行此操作,请尝试提取你要在空白 `npx create-expo-app` 项目中添加的功能,然后从那里开始。
¥Simplifying code as much as possible to track down the source of error is a great way to debug your app and it gets exponentially easier. That's why many open-source repositories require a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) when you open an issue. It ensures you have isolated the issue and identified exactly where the problem occurs. If your app is too large and complex to do that, try and extract the functionality you're trying to add in a blank `npx create-expo-app` project, and go from there.
### 原生调试
¥Native debugging
你可以通过在本地生成源代码并从该源构建来使用 Android Studio 和 Xcode 执行完整的原生调试。
¥You can perform full native debugging with Android Studio and Xcode by generating source code locally and building from that source.
#### 安卓工作室
¥Android Studio
Step 1:
Generate the native code for your project by running the following command:
```sh
$ npx expo prebuild -p android
```
This will add an **android** directory at the root of your project.
Step 2:
Open the project in Android Studio by running the command:
```sh
$ open -a "/Applications/Android Studio.app" ./android
```
Step 3:
Build the app from Android Studio and connect the debugger. See [Google's documentation](https://developer.android.com/studio/debug#startdebug) for more information.
> You can delete the **android** directory when you are done with this process. This ensures that your project remains managed by Expo CLI. Keeping the directory around and manually modifying it outside of `npx expo prebuild` means you'll need to manually upgrade and configure native libraries yourself.
#### Xcode
> This is only available for macOS users and requires Xcode to be installed.
Step 1:
Generate the native code for your project by running the following command:
```sh
$ npx expo prebuild -p ios
```
This will add an **ios** directory at the root of your project.
Step 2:
Open the project in Xcode by running the command which is a shortcut to open the `.xcworkspace` file from your project's **ios** directory in Xcode.
```sh
$ xed ios
```
Step 3:
Build the app with Cmd ⌘ + r or by pressing the play button in the upper left corner of Xcode.
Step 4:
You can now utilize [**Low-level debugger (LLDB)**](https://developer.apple.com/library/archive/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html) and all of the other [Xcode debugging tools](https://developer.apple.com/documentation/metal/debugging_tools) to examine the native runtime.
> You can delete the **ios** directory when you are done with this process. This ensures that your project remains managed by Expo CLI. Keeping the directory around and manually modifying it outside of `npx expo prebuild` means you'll need to manually upgrade and configure native libraries yourself.
## Production errors
Errors or bugs in your production app can be much harder to solve, mainly because you have less context around the error (that is, where, how, and why did the error occur?).
**The best first step in addressing a production error is to reproduce it locally.** Once you reproduce an error locally, you can follow the [development debugging process](#development-errors) to isolate and address the root cause.
> **info** **Tip**: Sometimes, running your app in **production mode** locally will show errors that normally wouldn't be thrown. You can run the app locally in production by running `npx expo start --no-dev --minify`.
> `--no-dev` tells the server not to be run in development mode, and `--minify` is used to minify your code the same way it is for production JavaScript bundles.
### Production app is crashing
It can be a frustrating scenario when a production app crashes. There is very little information to look into when it happens. It's important to reproduce the issue, and even if you can't do that, to find any related crash reports.
Start by reproducing the crash using your production app and then **find an associated crash report**. For Android, you can use `adb logcat` and for iOS you can use the Console app in Xcode.
Video Tutorial: [How to use Logcat & macOS Console to debug](https://www.youtube.com/watch?v=LvCci4Bwmpc)
#### Crash reports using adb logcat
If your Android app is on Google Play, refer to the crashes section of the [Google Play Console](https://play.google.com/console/about/), or connect your Android device to your computer and run the following command:
```sh
$ adb logcat
```
The Android Debug Bridge (`adb`) program is part of the Android SDK and allows you to view streaming logs. An alternative to avoid installing Android SDK is to use [WebADB](https://webadb.com/) in Chrome.
#### Crash reports using Console app
If your iOS app is on TestFlight or the App Store, you can use the [Crashes Organizer](https://developer.apple.com/news/?id=nra79npr) in Xcode.
If not, you can use the **Console** app in Xcode by connecting your device to your Mac. Follow the steps below on how to access the Console app:
Step 1:
Open Xcode app, and then open **Devices and Simulators** window by pressing Shift + Cmd ⌘ + 2.
Step 2:
If you have connected a physical device, select it under **Devices**. Otherwise, if you are using a simulator, select it under **Simulators**.
Step 3:
Click on **Open Console** button shown in the window to open the console app.
This will open the console app for you to view logs from your device or simulator.
有关更多信息,请参阅 Apple 的 [使用崩溃报告和设备日志诊断问题](https://developer.apple.com/documentation/xcode/diagnosing-issues-using-crash-reports-and-device-logs) 指南。
¥For more information, see Apple's [Diagnosing Issues Using Crash Reports and Device Logs](https://developer.apple.com/documentation/xcode/diagnosing-issues-using-crash-reports-and-device-logs) guide.
### 应用在某些(较旧)设备上崩溃
¥App crashes on certain (older) devices
这可能表明存在性能问题。你可能需要通过分析器运行你的应用,以更好地了解哪些进程正在终止应用,以及 [React Native 为此提供了一些很棒的文档](https://rn.nodejs.cn/docs/profiling)。我们还建议使用 [React Native DevTools](/debugging/tools/#debugging-with-react-native-devtools) 和内置的 [profiler](/debugging/tools/#profiling-javascript-performance),这样可以非常轻松地识别应用中的 JavaScript 性能瓶颈。
¥This might indicate that there is a performance issue. You likely need to run your app through a profiler to get a better idea of what processes are killing the app, and [React Native provides some great documentation for this](https://rn.nodejs.cn/docs/profiling). We also recommend using [React Native DevTools](/debugging/tools/#debugging-with-react-native-devtools) and the included [profiler](/debugging/tools/#profiling-javascript-performance), which makes it super easy to identify JavaScript performance sinks in your app.
### 使用错误报告服务
¥Using error reporting services
在生产应用中实现崩溃和错误报告服务有几个好处,例如:
¥Implementing a crash and bug reporting service in your production app offers several benefits, such as:
* 实时洞察生产部署,并提供重现崩溃和错误的信息。
¥Real-time insights on production deployments with information to reproduce crashes and bugs.
* 设置警报系统以获取有关致命 JavaScript 错误或你配置的任何其他事件的通知。
¥Setting up an alert system to get notified about fatal JavaScript errors or any other event you configure.
* 使用 Web 仪表板查看异常的详细信息,例如堆栈跟踪、设备信息等。
¥Using a web dashboard to see details on exceptions such as stack traces, device information, and so on.
使用 Expo,你可以集成 [Sentry](/guides/using-sentry/) 或 [BugSnag](/guides/using-bugsnag/) 等报告服务,以实时获得更多见解。
¥With Expo, you can integrate a reporting service like [Sentry](/guides/using-sentry/) or [BugSnag](/guides/using-bugsnag/) to get more insights in real-time.
## 卡住?
¥Stuck?
Expo 社区以及 React 和 React Native 社区是你遇到困难时提供帮助的重要资源。其他人很可能遇到了与你相同的错误,因此请务必阅读文档,搜索 [forums](https://chat.expo.dev/)、[GitHub 问题](https://github.com/expo/expo/issues/) 和 [Stack Overflow](https://stackoverflow.com/)。
¥The Expo community and the React and React Native communities are great resources for help when you get stuck. There's a good chance someone else has run into the same error as you, so make sure to read the documentation, search the [forums](https://chat.expo.dev/), [GitHub issues](https://github.com/expo/expo/issues/), and [Stack Overflow](https://stackoverflow.com/).
## 调试和分析工具
了解可用于在运行时检查你的 Expo 项目的不同工具。
React Native 由 JavaScript 和原生代码组成。在调试时做出这种区分非常重要。如果 JavaScript 代码抛出错误,你可能无法使用原生代码的调试工具找到它。本页列出了一些可帮助你调试 Expo 项目的工具。
¥React Native consists of both JavaScript and native code. Making this distinction is very important when it comes to debugging. If an error is thrown from the JavaScript code, you might not find it using debugging tools for native code. This page lists a few tools to help you debug your Expo project.
## 开发者菜单
¥Developer menu
Developer 菜单提供对有用调试功能的访问。它内置于开发客户端和 Expo Go 中。如果你使用的是模拟器、模拟器或通过 USB 连接的设备,则可以在 Expo CLI 启动开发服务器的终端中按 m 打开此菜单。
¥The **Developer menu** provides access to useful debugging functions. It is built into dev clients and Expo Go. If you are using an emulator, simulator, or have a device connected via USB, you can open this menu by pressing m in the terminal where Expo CLI has started the development server.
Note: Alternative options to open the Developer menu
---
- Android device (without USB): Shake the device vertically.
- Android Emulator or device (with USB):
- Press Cmd ⌘ + m or Ctrl + m.
- Run the following command in the terminal to simulate pressing the menu button:
```sh
$ adb shell input keyevent 82
```
- iOS device (without USB):
- Shake the device.
- Touch three fingers to the screen.
- iOS Simulator or device (with USB):
- Press Ctrl + Cmd ⌘ + z or Cmd ⌘ + d
---
Once the Developer menu is open, it will appear as below:
The Developer menu provides the following options:
- **Copy link**: To copy the dev server address in dev client or [`exp://`](/linking/into-your-app/#test-a-link-using-expo-go) link in Expo of your app.
- **Reload**: To reload you app. Usually, not necessary since Fast Refresh is enabled by default.
- **Go Home**: To leave your app and navigate back to the dev client's or Expo Go app's Home screen.
- **Toggle performance monitor**: To view the performance information about your app.
- **Toggle element inspector**: To enable or disable the element inspector overlay.
- **Open JS debugger**: To open React Native DevTools which provides access to Console, Sources, Network (**Expo only**), Memory, Components, and Profiler, tabs for apps using Hermes. For more information, see the [Debugging with React Native DevTools](#debugging-with-react-native-devtools) section.
- **Fast Refresh**: To toggle automatic refreshing of the JS bundle whenever you make changes to files in your project using a text editor.
Now, let's explore some of these options in details.
### Toggle performance monitor
Opens up a small overlay that provides the following performance information about your app:
- RAM usage of a project.
- JavaScript heap (this is an easy way to know of any memory leaks in your application).
- Two Views. The top indicates the number of views for the screen and the bottom indicates the number of views in the component.
- Frames Per Second for the UI and JS threads. The UI thread is used for native Android or iOS UI rendering. The JS thread is where most of your logic runs, including API calls, touch events, and so on.
### Toggle element inspector
Opens up the element inspector overlay:
This overlay has capabilities to:
- Inspect: Inspect elements
- Perf: Show Performance overlay
- Network: Show network details
- Touchables: Highlight touchable elements
## Debugging with React Native DevTools
> **info** **Starting from React Native 0.76**, React Native DevTools has replaced Chrome DevTools.
**React Native DevTools** is a modern debugging tool for Expo and React Native apps. It allows you to gain insights into the JavaScript code of your app by accessing the [Console](#interacting-with-the-console), [Sources](#pausing-on-breakpoints), [Network](#inspecting-network-requests) (**Expo only**), and [Memory](#insepcting-memory) tabs. It also has **built-in support for React DevTools** such as [Components](#inspecting-components) and [Profiler](#profiling-javascript-performance) tabs. All of these inspectors can be accessed using [dev clients](/more/glossary-of-terms/#dev-clients) or Expo Go.
You can use the React Native DevTools on any app using [Hermes](/guides/using-hermes/). **To open it, start your app and press j in the terminal where Expo was started**. Once you have opened the React Native DevTools, it will appear as below:
### Pausing on breakpoints
You can pause your app on specific parts of your code. To do this, set the breakpoint under the Sources tab by clicking the line number or add the `debugger` statement in your code.
Once your app is executing code that has a breakpoint, it will entirely pause your app. This allows you to inspect all variables and functions in that scope. You can also execute code in the [Console](#interacting-with-the-console) tab as part of your app.
### Pausing on exceptions
If your app throws unexpected errors, it can be hard to find the source of the error. You can use React Native DevTools to pause your app and inspect the stack trace and variables the moment it throws an error.
> **info** Some errors might be caught by other components in your app, such as Expo Router. In these cases, you can turn on **Pause on caught exceptions**. It will enable you to inspect any thrown error, even when handled properly.
### Interacting with the console
The **Console** tab gives you access to an interactive terminal, connected directly to your app. You can write any JavaScript inside this terminal to execute snippets of code as if it were part of your app. The code is executed in the global scope by default. But, when using breakpoints from the [Sources](#pausing-on-breakpoints) tab, it executes in the scope of the reached breakpoint. This allows you to invoke methods and access variables throughout your app.
### Inspecting network requests (Expo only)
> **info** The Network tab in React Native DevTools is only available when you have `expo` installed in your project.
The **Network** tab gives you insights into the network requests made by your app. You can inspect each request and response by clicking on them. This includes `fetch` requests, external loaded media, and in some cases, even requests made by native modules.
> **info** See the [Inspecting network traffic](#inspecting-network-traffic) for alternative ways to inspect network requests.
### Inspecting memory
The **Memory** tab allows you to inspect the memory usage and take a heap snapshot of your app's JavaScript code.
### Inspecting components
The **Components** tab allows you to inspect the React components in your app. You can view the props, and styles of each component by hovering that component in React Native DevTools. This is a great way to debug your app's UI and understand how your components are structured.
### Profiling JavaScript performance
> **warning** Profiles are not yet symbolicated with sourcemaps, and [can only be used in debug builds](https://github.com/facebook/hermes/issues/760). These limitations will be addressed in upcoming releases.
The **Profiler** tab allows you to record and analyze the performance of your app's JavaScript. You can start recording, interact with your app, and stop recording to analyze the profile.
> **info** To profile the native runtime, use the tools included in Android Studio or Xcode.
## Debugging with VS Code
> **warning** VS Code debugger integration is experimental. For the most stable debugging experience, [use the React Native DevTools](#debugging-with-react-native-devtools).
VS Code is a popular code editor, which has a built-in debugger. This debugger uses the same system as the React Native DevTools — the inspector protocol.
You can use this debugger with the [Expo Tools](https://github.com/expo/vscode-expo#readme) VS Code extension. This debugger allows you to set breakpoints, inspect variables, and execute code through the debug console.
To start debugging:
- Connect your app
- Open VS Code command palette (based on your computer, it's either Ctrl + Shift + p or Cmd ⌘ + Shift + p)
- Run the **Expo: Debug ...** VS Code command.
This will attach VS Code to your running app.
Alternatively, if you want a fully-featured IDE setup in VS Code, you might want to check out the [Radon IDE](https://ide.swmansion.com/) extension (paid with a 30-day free trial). It turns your editor into a powerful environment designed specifically for React Native and Expo projects, with advanced debugging, a network inspector, router integration, and other built-in tools.
## React Native Debugger
> **warning** The React Native Debugger requires Remote JS debugging, which has been deprecated since [React Native 0.73](https://rn.nodejs.cn/docs/other-debugging-methods#remote-javascript-debugging-deprecated).
The React Native Debugger is a standalone app that wraps the React DevTools, Redux DevTools, and React Native DevTools. Unfortunately, it requires the [deprecated Remote JS debugging workflow](https://github.com/jhen0409/react-native-debugger/discussions/774) and is incompatible with Hermes.
If you are using Expo **SDK 50** or **above**, you can use the [Expo dev tools plugins](/debugging/devtools-plugins) equivalents to the React Native Debugger:
- [React Native DevTools](#debugging-with-react-native-devtools)
- [Redux DevTools](/debugging/devtools-plugins/#redux)
If you are using Expo SDK 49 or below, you can use the React Native Debugger. This section provides quick get started instructions. For in-depth information, check its [documentation](https://github.com/jhen0409/react-native-debugger#documentation).
You can install it via the [release page](https://github.com/jhen0409/react-native-debugger/releases), or if you're on macOS you can run:
```sh
$ brew install react-native-debugger
```
### Startup
After firing up React Native Debugger, you'll need to specify the port (shortcuts: Cmd ⌘ + t on macOS, Ctrl + t on Linux/Windows) to `8081`. After that, run your project with `npx expo start`, and select `Debug remote JS` from the Developer Menu. The debugger should automatically connect.
In the debugger console, you can see the Element tree, as well as the props, state, and children of whatever element you select. You also have the Chrome console on the right, and if you type `$r` in the console, you will see the breakdown of your selected element.
If you right-click anywhere in the React Native Debugger, you'll get some handy shortcuts to reload your JS, enable/disable the element inspector, network inspector, and to log and clear your `AsyncStorage` content.
### 检查网络流量
¥Inspecting network traffic
使用 React Native 调试器来调试网络请求很容易:右键单击 React Native 调试器中的任意位置并选择 `Enable Network Inspect`。这将启用“网络”选项卡并允许你检查 `fetch` 和 `XMLHttpRequest` 的请求。
¥It's easy to use the React Native Debugger to debug your network request: right-click anywhere in the React Native Debugger and select `Enable Network Inspect`. This will enable the Network tab and allow you to inspect requests of `fetch` and `XMLHttpRequest`.
然而有 [一些限制](https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md#limitations),所以还有一些其他替代方案,所有这些都需要使用代理:
¥There are however [some limitations](https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md#limitations), so there are a few other alternatives, all of which require using a proxy:
* [Charles 代理](https://www.charlesproxy.com/documentation/configuration/browser-and-system-configuration/)(~50 美元,我们的首选工具)
¥[Charles Proxy](https://www.charlesproxy.com/documentation/configuration/browser-and-system-configuration/) (~$50 USD, our preferred tool)
* [代理人](https://proxyman.io)(提供免费版本或 49 至 59 美元)
¥[Proxyman](https://proxyman.io) (Free version available or $49 to $59 USD)
* [mitmproxy](https://medium.com/@rotxed/how-to-debug-http-s-traffic-on-android-7fbe5d2a34#.hnhanhyoz)
* [提琴手](http://www.telerik.com/fiddler)
¥[Fiddler](http://www.telerik.com/fiddler)
## 调试生产应用
¥Debugging production apps
实际上,应用经常附带错误。实现崩溃和错误报告系统可以帮助你实时了解生产应用。详细信息请参见 [使用错误报告服务](/debugging/runtime-issues/#using-error-reporting-services)。
¥In reality, apps often ship with bugs. Implementing a crash and bug reporting system can help you get real-time insights of your production apps. See [Using error reporting services](/debugging/runtime-issues/#using-error-reporting-services) for more details.
## 开发工具插件
了解如何使用开发工具插件来检查和调试你的 Expo 项目。
你的本地开发环境中提供了开发工具插件,可帮助你调试应用。它们由你添加到项目中的少量代码组成,这些代码支持应用和外部 Chrome 窗口之间的双向通信。此设置提供显示工具来检查应用、触发某些测试行为等。
¥Dev tools plugins are available in your local development environment to help you debug your app. They consist of a small amount of code you add to a project that enables two-way communication between the app and an external Chrome window. This setup provides display tools to inspect the app, trigger certain behaviors for testing, and more.
开发工具插件类似于可在开发版本和 Expo Go 中使用的 Flipper 插件,并且不需要向项目添加原生模块或配置插件。
¥Dev tools plugins are similar to Flipper plugins that are available in Development builds and Expo Go, and do not require adding native modules or config plugins to your project.
## 将开发工具插件添加到项目中
¥Add a dev tools plugin to a project
要将开发工具插件添加到你的应用,请将其作为包安装并添加一个小片段以将代码连接到你的应用。此代码从应用的根组件调用,以在应用和插件之间建立双向通信。然后,该插件可以在应用在开发模式下运行的整个过程中检查应用的各个方面。
¥To add a dev tool plugin to your app, install it as a package and add a small snippet to connect the code to your app. This code is invoked from the app's root component to establish a two-way communication between your app and the plugin. Then, the plugin can inspect aspects of your app for the entire time your app runs in development mode.
所有 [Expo 开发工具插件](#expo-dev-tools-plugins) 和使用 [我们的创作工具](/debugging/create-devtools-plugins) 创建的插件都会导出一个钩子,你可以使用该钩子将插件连接到你的应用。当应用未在开发模式下运行时,钩子及其返回的任何函数都将无操作。
¥All [Expo dev tools plugins](#expo-dev-tools-plugins) and plugins created with [our creation tool](/debugging/create-devtools-plugins) export a hook that you can use to connect the plugin to your app. The hook and any functions returned from it will no-op when the app is not running in development mode.
某些插件钩子需要与插件如何检查你的应用相关的参数。例如,用于检查 React Navigation 状态的插件可能需要引用导航根。
¥Some plugin hooks require parameters that relate to how the plugin inspects your app. For instance, a plugin for inspecting the React Navigation state might require a reference to the navigation root.
要开始使用该插件,请使用应用根组件中的钩子:
¥To start using the plugin, use the hook in your app's root component:
```jsx App.js
import { useMyDevToolsPlugin } from 'my-devtools-plugin';
export default App() {
useMyDevToolsPlugin();
return (/* rest of your app */)
}
```
在某些情况下,你可能需要直接与插件交互。所有插件都通过 `expo/devtools` 的导出进行通信,你可以通过 `useDevToolsPluginClient` 发送和收听消息。请务必将与插件的 Web 用户界面使用的插件名称相同的插件名称传递给 `useDevToolsPluginClient`:
¥In some cases, you may need to interact with a plugin directly. All plugins communicate through exports from `expo/devtools`, and you can send and listen to messages through `useDevToolsPluginClient`. Be sure to pass the same plugin name to `useDevToolsPluginClient` as is used by the plugin's web user interface:
```jsx App.js
import { useDevToolsPluginClient } from 'expo/devtools';
export default App() {
const client = useDevToolsPluginClient('my-devtools-plugin');
useEffect(() => {
// receive messages
client?.addMessageListener("ping", (data) => {
alert(`Received ping from ${data.from}`);
});
// send messages
client?.sendMessage("ping", { from: "app" });
}, []);
return (/* rest of your app */)
}
```
### 与 Expo Go 和 Development 版本的兼容性
¥Compatibility with Expo Go and Development builds
开发工具插件应该只包含 JavaScript 代码。它们通常与 Expo Go 和 [开发构建](/develop/development-builds/introduction/) 兼容,并且不需要创建新的开发版本来添加插件。如果插件检查的包的底层模块包含原生代码并且不是 Expo Go 的一部分,请创建一个新的开发版本以使用该包中的组件和插件。
¥Dev tools plugins should only include JavaScript code. They are generally compatible with Expo Go and [Development builds](/develop/development-builds/introduction/) and should not require creating a new development build to add the plugin. If a package's underlying module that the plugin inspects includes native code and is not part of Expo Go, create a new development build to use both the component and the plugin from that package.
例如,检查 [React Native Firebase](/guides/using-firebase/#using-react-native-firebase) 的开发工具插件将无法与 Expo Go 一起使用。React Native Firebase 包含不属于 Expo Go 的原生代码。要使用开发工具插件和 React Native Firebase,请创建开发版本。
¥For example, a dev tools plugin that inspects [React Native Firebase](/guides/using-firebase/#using-react-native-firebase) will not work with Expo Go. React Native Firebase includes native code that is not part of Expo Go. To use the dev tools plugin and React Native Firebase, create a development build.
## 使用开发工具插件
¥Using a dev tools plugin
安装开发工具插件并将连接所需的代码添加到你的项目后,你可以使用 `npx expo start` 启动开发服务器。然后按 shift + m 打开可用开发工具插件列表。选择你要使用的插件,它将在新的 Chrome 窗口中打开。
¥After installing the dev tools plugin and adding the connecting required code to your project, you can start the dev server up with `npx expo start`. Then press shift + m to open the list of available dev tools plugins. Select the plugin you want to use, and it will open in a new Chrome window.
> 使用 Expo CLI 启动开发服务器时,可以选择按 ? 显示所有命令。这显示了其他命令,包括打开更多工具的快捷方式。还可以在此菜单中选择开发工具插件。
>
> ¥When starting the dev server with the Expo CLI, there is an option to press ? to **show all commands**. This shows additional commands, including the shortcut to open **more tools**. Dev tools plugins can also be selected in this menu.
## Expo 开发工具插件
¥Expo dev tools plugins
Expo 提供了一些用于常见调试任务的开发工具插件。请按照以下说明开始在你的应用中使用它们。
¥Expo provides some dev tools plugins for common debugging tasks. Follow the instructions below to start using them in your app.
> 注意:以下每个开发工具插件钩子只会在开发模式下启用插件。它不会影响你的生产包。
>
> ¥**Note**: Each of the following dev tools plugin hooks will only enable the plugin in development mode. It doesn't affect your production bundle.
### React 导航
¥React Navigation
受 [`@react-navigation/devtools`](https://github.com/react-navigation/react-navigation/tree/main/packages/devtools) 的启发,React Navigation 开发工具插件允许查看 [React 导航](https://react-navigation.nodejs.cn/) 操作和状态的历史记录。你还可以倒回到导航历史记录中的先前点,并将深层链接发送到你的应用。由于 Expo Router 是基于 React Navigation 构建的,因此该插件与 [Expo 路由](/router/introduction) 完全兼容。
¥Inspired by [`@react-navigation/devtools`](https://github.com/react-navigation/react-navigation/tree/main/packages/devtools), the React Navigation dev tools plugin allows seeing the history of [React Navigation](https://react-navigation.nodejs.cn/) actions and state. You can also rewind to previous points in your navigation history and send deep links to your app. Since Expo Router is built upon React Navigation, this plugin is fully compatible with [Expo Router](/router/introduction).
要使用该插件,请首先安装该软件包:
¥To use the plugin, start by installing the package:
```sh
$ npx expo install @dev-plugins/react-navigation
```
将导航根传递给应用入口点中的插件:
¥Pass the navigation root to the plugin in your app's entry point:
```jsx app/_layout.js
import { useEffect, useRef } from 'react';
import { useNavigationContainerRef, Slot } from 'expo-router';
import { useReactNavigationDevTools } from '@dev-plugins/react-navigation';
export default Layout() {
const navigationRef = useNavigationContainerRef();
useReactNavigationDevTools(navigationRef);
return ;
}
```
```jsx App.js
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { useReactNavigationDevTools } from '@dev-plugins/react-navigation';
export default function App() {
const navigationRef = useNavigationContainerRef();
useReactNavigationDevTools(navigationRef);
return (
{/* ... */}
);
}
```
在终端中,运行 `npx expo start`,按 shift + m 打开开发工具列表,然后选择 React Navigation 插件。这将打开插件的网络界面,在你浏览应用时显示你的导航历史记录。
¥In the terminal, run `npx expo start`, press shift + m to open the list of dev tools, and then select the React Navigation plugin. This will open the plugin's web interface, showing your navigation history as you navigate through your app.
### Apollo 客户端
¥Apollo Client
受 [`react-native-apollo-devtools`](https://github.com/razorpay/react-native-apollo-devtools) 的启发,Apollo Client 开发工具插件允许检查 Apollo Client 的缓存、查询和修改。
¥Inspired by [`react-native-apollo-devtools`](https://github.com/razorpay/react-native-apollo-devtools), the Apollo Client dev tools plugin allows inspecting cache, query, and mutation for the Apollo Client.
要使用该插件,请首先安装该软件包:
¥To use the plugin, start by installing the package:
```sh
$ npx expo install @dev-plugins/apollo-client
```
然后将你的客户端实例传递给应用根组件中的插件或将应用的其余部分封装在 `ApolloProvider` 中的位置:
¥Then pass your client instance to the plugin in your app's root component or where you wrap the rest of your app in the `ApolloProvider`:
```jsx App.js
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';
import { useApolloClientDevTools } from '@dev-plugins/apollo-client';
const client = new ApolloClient({
uri: 'https://demo.test.com/',
cache: new InMemoryCache(),
});
export default function App() {
useApolloClientDevTools(client);
return {/* ... */};
}
```
在终端中,运行 `npx expo start`,按 shift + m 打开开发工具列表,然后选择 Apollo Client 插件。这将打开插件的 Web 界面,显示你的应用执行 Apollo 客户端操作时的查询历史记录、缓存和突变。
¥In the terminal, run `npx expo start`, press shift + m to open the list of dev tools, and then select the Apollo Client plugin. This will open the plugin's web interface, showing your query history, cache, and mutations as your app performs Apollo Client operations.
### 反应查询
¥React Query
受 [`react-query-native-devtools`](https://github.com/bgaleotti/react-query-native-devtools) 的启发,React Query 开发工具插件可让你探索数据和查询、缓存状态,以及从 [TanStack 查询](https://tanstack.com/query/latest/) 的缓存中重新获取和删除查询。
¥Inspired by [`react-query-native-devtools`](https://github.com/bgaleotti/react-query-native-devtools), the React Query dev tools plugin lets you explore data and queries, cache status, and refetch and remove queries from the cache from [TanStack Query](https://tanstack.com/query/latest/).
要使用该插件,请首先安装该软件包:
¥To use the plugin, start by installing the package:
```sh
$ npx expo install @dev-plugins/react-query
```
然后将你的客户端实例传递给应用根组件中的插件或将应用的其余部分封装在 `QueryClientProvider` 中的位置:
¥Then pass your client instance to the plugin in your app's root component or where you wrap the rest of your app in the `QueryClientProvider`:
```jsx App.js
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useReactQueryDevTools } from '@dev-plugins/react-query';
const queryClient = new QueryClient({});
export default function App() {
useReactQueryDevTools(queryClient);
return {/* ... */};
}
```
在终端中,运行 `npx expo start`,按 shift + m 打开开发工具列表,然后选择 React Query 插件。这将打开插件的 Web 界面,显示应用中使用的查询。
¥In the terminal, run `npx expo start`, press shift + m to open the list of dev tools, and then select the React Query plugin. This will open the plugin's web interface, displaying queries as they are used in your app.
### Redux
`redux-devtools-expo-dev-plugin` 基于 [Redux DevTools](https://github.com/reduxjs/redux-devtools/)(来自 Chrome 扩展)。它提供了一个实时操作列表以及它们如何影响状态,以及从 DevTools 倒带、重播和调度操作的能力。
¥The `redux-devtools-expo-dev-plugin` is based on the [Redux DevTools](https://github.com/reduxjs/redux-devtools/) (from the Chrome extension). It provides a live list of actions and how they affect the state, and the ability to rewind, replay, and dispatch actions from the DevTools.
要使用该插件,请首先安装该软件包:
¥To use the plugin, start by installing the package:
```sh
$ npx expo install redux-devtools-expo-dev-plugin
```
如果你使用的是 `@reduxjs/toolkit`,请通过传入 `devTools: false` 来修改 `configureStore` 调用以禁用内置开发工具。然后,通过连接 `devToolsEnhancer()` 添加 Expo DevTools 插件增强器。`configureStore` 调用将如下所示:
¥If you're using `@reduxjs/toolkit`, modify the `configureStore` call to disable the built-in dev tools by passing in `devTools: false`. Then, add in the Expo DevTools plugin enhancer by concatenating the `devToolsEnhancer()`. The `configureStore` call is going to look like the following:
```js store.js
import devToolsEnhancer from 'redux-devtools-expo-dev-plugin';
const store = configureStore({
reducer: rootReducer,
devTools: false,
enhancers: getDefaultEnhancers => getDefaultEnhancers().concat(devToolsEnhancer()),
});
```
在终端中,运行 `npx expo start`,按 shift + m 打开开发工具列表,然后选择 `redux-devtools-expo-dev-plugin`。这将打开插件的 Web 界面,在分派操作时显示商店的操作和内容。
¥In the terminal, run `npx expo start`, press shift + m to open the list of dev tools, and then select `redux-devtools-expo-dev-plugin`. This will open the plugin's web interface, displaying the actions and contents of your store as actions are dispatched.
有关完整的安装和使用说明,包括如果你直接使用 `redux` 而不是 `@reduxjs/toolkit`、[设置持久性以使用户在重新加载之间保持登录状态](https://github.com/matt-oakes/redux-devtools-expo-dev-plugin)。
¥For complete installation and usage instructions, including if you're using `redux` directly rather than `@reduxjs/toolkit`, [see the project's README](https://github.com/matt-oakes/redux-devtools-expo-dev-plugin).
### TinyBase
TinyBase 开发工具插件将 TinyBase Store Inspector 连接到你的应用,允许你查看和更新应用商店的内容。
¥The TinyBase dev tools plugin connects the TinyBase Store Inspector to your app, allowing you to view and update the contents of your app's store.
要使用该插件,请首先安装该软件包:
¥To use the plugin, start by installing the package:
```sh
$ npx expo install @dev-plugins/tinybase
```
然后将你的客户端实例传递给应用根组件中的插件或使用商店的 `Provider` 封装应用其余部分的位置:
¥Then pass your client instance to the plugin in your app's root component or where you wrap the rest of your app with the store's `Provider`:
```jsx App.js
import { createStore } from 'tinybase';
import { useValue, Provider } from 'tinybase/lib/ui-react';
import { useTinyBaseDevTools } from '@dev-plugins/tinybase';
const store = createStore().setValue('counter', 0);
export default function App() {
useTinyBaseDevTools(store);
return {/* ... */};
}
```
在终端中,运行 `npx expo start`,按 shift + m 打开开发工具列表,然后选择 Tinybase 插件。这将打开插件的网络界面,显示修改后的商店内容。
¥In the terminal, run `npx expo start`, press shift + m to open the list of dev tools, and then select the Tinybase plugin. This will open the plugin's web interface, displaying the contents of your store as it is modified.
## 创建开发工具插件
了解如何创建开发工具插件以增强你的开发体验。
> 提示:查看 [Expo DevTools 插件](https://github.com/expo/dev-plugins) 了解完整示例。
>
> ¥**Tip**: Check out the [Expo DevTools Plugins](https://github.com/expo/dev-plugins) for complete examples.
你可以创建一个开发工具插件,无论是用于检查通用框架或库的各个方面,还是特定于你的自定义代码的内容。本指南将引导你创建开发工具插件。
¥You can create a dev tools plugin, whether that's for inspecting aspects of a common framework or library or something specific to your custom code. This guide will walk you through creating a dev tools plugin.
## 什么是开发工具插件?
¥What is a dev tools plugin?
开发工具插件在本地开发环境中的 Web 浏览器中运行,并连接到你的 Expo 应用。
¥A dev tools plugin runs in your web browser in your local development environment and connects to your Expo app.
插件由三个关键元素组成:
¥A plugin consists of three key elements:
* 用于显示开发工具 Web 用户界面的 Expo 应用。
¥An Expo app to display the dev tools web user interface.
* 用于 Expo CLI 识别的 expo-module.config.json。
¥An **expo-module.config.json** for Expo CLI recognition.
* 调用 `expo/devtools` API 以便应用与开发工具的 Web 界面来回通信。
¥Calls to `expo/devtools` API for the app to communicate back and forth with the dev tool's web interface.
插件可以在 npm 上分发或包含在应用的 monorepo 中。它们通常会导出一个钩子,该钩子可在应用的根组件中使用,以便在应用在调试模式下运行时启动与 Web 界面的双向通信。
¥Plugins can be distributed on npm or included inside your app's monorepo. They typically export a single hook that can be used in your app's root component to initiate two-way communication with the web interface when your app is running in debug mode.
Step 1:
## Create a plugin
### Create a new plugin project
`create-dev-plugin` will set up a new plugin project for you. Run the following command to create a new plugin project:
```sh
$ npx create-dev-plugin@latest
```
`create-dev-plugin` will prompt you for the name of your plugin, a description, and the name of the hook that will be used by consumers of your plugin.
The plugin project will contain the following directories:
- **src** - this exports the hook that will be used inside the consuming app to connect it to the plugin.
- **webui** - this contains the web user interface for the plugin.
### Customize a plugin's functionality
The template includes a simple example of sending and receiving messages between the plugin and the app. `useDevToolsPluginClient`, imported from `expo/devtools`, provides functionality for sending and receiving messages between the plugin and the app.
The client object returned by `useDevToolsPluginClient` includes:
#### addMessageListener
Listens for a message matching the typed string and invokes the callback with the message data.
!!!IG0!!!
#### sendMessage
Listens for a message matching the typed string and invokes the callback with the message data.
!!!IG1!!!
Edit the Expo app inside the **webui** directory to customize the user interface that displays diagnostic information from your app or triggers test scenarios:
!!!IG2!!!
Edit the hook in the **src** directory to customize what diagnostic information is sent to the plugin or how the app should respond to any messages from the web user interface:
!!!IG3!!!
If you update the hook to return functions that will be called by the app, you will also need to update **src/index.ts** so it exports no-op functions when the app is not running in debug mode:
!!!IG4!!!
Step 2:
## Test a plugin
Since the plugin web UI is an Expo app, you can test it just like you would any other Expo app, with `npx expo start`, except that you will run it in the browser only. The template includes a convenience command to run the plugin in local development mode:
```sh
$ npm run web:dev
```
Step 3:
## Build a plugin for distribution
To prepare your plugin for distribution or use within your monorepo, you will need to build the plugin with the following command:
```sh
$ npm run build:all
```
This command will build the hook code into the **build** directory, and the web user interface into the **dist** directory.
Step 4:
## Use the plugin
Import the plugin's hook into your app's root component and call it to connect your app to the plugin:
!!!IG5!!!
# 审核
## 分发应用以供审核的概述
了解如何使用应用商店、内部分发和 EAS 更新分发你的应用以供审核。
本页概述了与你的团队共享应用预览版本以进行 QA 和审核的三种方法:使用 EAS Update 的应用商店测试轨道、内部分发和开发版本。
¥This page outlines three approaches to sharing a preview version of your app with your team for QA and review: app store testing tracks, internal distribution, and development builds with EAS Update.
Note: Can I use Expo Go for reviewing releases?
---
Even though Expo Go is an open-source sandbox that can be good for previewing isolated prototypes on Android and iOS, it is not intended for production apps. It should be avoided during the preview process of your app.
---
## App store testing tracks
When distributing apps through app store testing tracks, you can only use release builds. You cannot use this method to distribute development builds. An alternative approach is to use ["Internal distribution"](#internal-distribution-with-eas-build), which works with both release and development builds.
Note: Android: Google Play Beta
---
Before a complete public release, [Google Play beta](https://support.google.com/googleplay/android-developer/answer/9845334?visit_id=638740965629093187-3840249980&rd=1) is another option to distribute your app to testers. You can set up either an internal, closed, or open test track and control who has access to the app.
Each test track has its own requirements. For the internal track, you can only invite up to 100 testers. Both closed and open tracks support larger groups of testers. In closed tracks, you need to invite testers, while in open tracks, anyone can join your program.
To use Google Play beta, you need to upload your app as an AAB (Android App Bundle) to the Google Play Console, set up a test track, and invite users via email or a shareable link. Testers can install the app through the Play Store, and you can collect feedback and crash reports directly from the Google Play Console.
---
Note: iOS: TestFlight
---
TestFlight is another option to distribute your app to iOS devices. TestFlight also requires a paid Apple Developer account. TestFlight's internal testing option allows you to create test groups that include up to 100 members of your Apple Developer account team, who then download the app through the TestFlight app. Some teams prefer TestFlight because it doesn't require a new build to add new testers, and apps stay updated automatically.
TestFlight also includes an external testing option that allows you to share your app with up to 10,000 users via an email or a public link.
Both internal and external test distribution in TestFlight require you to [upload your app](/submit/ios) to App Store Connect and wait for the automated review before you can share a build. However, external test builds will need to go through a more formal App Store review (which is distinct from the review that your app must undergo before production release) before being distributed.
---
## Internal distribution with EAS Build
[Internal distribution](/build/internal-distribution/) is a feature provided by EAS that allows developers to create builds and easily share them with a URL. The URL can be opened on a device to install the app. The app is provided as an installable APK for Android or an ad hoc provisioned app for iOS.
As soon as an internal distribution build is created, it is available for download and installation — no need to fill out any forms or wait for approval/processing. You can use internal distribution to share both release and development builds.
## Development builds and EAS Update
You can use [development builds](/develop/development-builds/introduction/) to load previews of your app during the review stage by publishing an update with [EAS Update](/eas-update/introduction/). After sharing a development build through internal distribution and installing it, you can launch any update that you published with EAS Update, as long as it is compatible with the installed build. Learn more about [Runtime versions and updates](/eas-update/runtime-versions/).
Note: You can use the EAS dashboard to launch updates and share a link to a specific update.
---
---
Note: You can explore and launch updates directly from a development build.
---
---
Note: You can configure GitHub Actions to automatically publish updates on PRs and commits.
---
---
这种方法非常强大,因为它允许你以运行 `eas update` 的速度响应反馈。与你的团队共享应用的新版本可能需要几秒钟,你可以这样做而无需重建应用或将其上传到商店测试轨道。
¥This approach is uniquely powerful because it allows you to respond to feedback as quickly as you can run `eas update`. It can take seconds to share a new version of your app with your team, and you can do so without needing to rebuild the app or upload it to a store test track.
}
description={
<>
Learn how to use in your project to launch different app versions
and preview a published update inside a development build.
>
## 与你的团队分享预览
通过在分支上发布更新与你的团队共享应用预览。
在分支上做出更改后,你可以通过发布更新与你的团队共享这些更改。这允许你在审核期间获得有关更改的反馈。
¥Once you've made changes on a branch, you can share them with your team by publishing an update. This allows you to get feedback on your changes during review.
以下步骤概述了发布更改预览并与团队共享的基本流程。如需更全面的资源,请参阅 [预览更新](/eas-update/preview/) 指南。
¥The following steps will outline a basic flow for publishing a preview of your changes, and then sharing it with your team. For a more comprehensive resource, see the [Preview updates](/eas-update/preview/) guide.
## 发布更改预览
¥Publish a preview of your changes
你可以通过运行以下 [EAS 命令行接口](/develop/tools/#eas-cli) 命令来发布当前更改的预览:
¥You can publish a preview of your current changes by running the following [EAS CLI](/develop/tools/#eas-cli) command:
```sh
$ eas update --auto
```
此命令将在当前分支名称下发布更新。
¥This command will publish an update under the current branch name.
## 与你的团队共享
¥Share with your team
发布预览后,你将在终端窗口中看到如下输出:
¥Once the preview is published, you'll see output like this in the terminal window:
```sh
✔ Published!
...
EAS Dashboard https://expo.dev/accounts/your-account/projects/your-project/updates/708b05d8-9bcf-4212-a052-ce40583b04fd
```
与审阅者共享 EAS 仪表板链接。打开链接后,他们可以单击预览按钮。他们将看到一个二维码,可以扫描该二维码在其设备上打开预览。
¥Share the **EAS dashboard** link with a reviewer. After opening the link, they can click on the **Preview** button. They will see a QR code that they can scan to open the preview on their device.
## 自动创建预览
¥Create previews automatically
你可以使用 [EAS 工作流程](/eas/workflows/get-started/) 在每次提交时自动创建预览。首先,你需要 [配置你的项目](/eas/workflows/get-started/#configure-your-project),在项目根目录下添加一个名为 .eas/workflows/publish-preview-update.yml 的文件,然后添加以下工作流配置:
¥You can automatically create previews on every commit with [EAS Workflows](/eas/workflows/get-started/). First, you'll need to [configure your project](/eas/workflows/get-started/#configure-your-project), add a file named **.eas/workflows/publish-preview-update.yml** at the root of your project, then add the following workflow configuration:
```yaml .eas/workflows/publish-preview-update.yml
name: Publish preview update
on:
push:
branches: ['*']
jobs:
publish_preview_update:
name: Publish preview update
type: update
params:
branch: ${{ github.ref_name || 'test' }}
```
上述工作流程将在每次提交到每个分支时发布更新。你也可以使用以下 EAS CLI 命令手动运行此工作流程:
¥The workflow above will publish an update on every commit to every branch. You can also run this workflow manually with the following EAS CLI command:
```sh
$ eas workflow:run publish-preview-update.yml
```
了解更多关于 [工作流示例指南](/eas/workflows/examples) 的常见模式。
¥Learn more about common patterns with the [workflows examples guide](/eas/workflows/examples).
## 了解更多
¥Learn more
## 如何使用 Expo Orbit 启动更新
了解如何在审核工作流程中使用 Expo Orbit 打开更新。
[Expo 轨道](https://expo.dev/orbit) 是一款 macOS 和 Windows 应用,旨在加快从 EAS 安装和运行构建的速度。它使运行构建和更新变得像在 Orbit 中按“打开”一样简单。
¥[Expo Orbit](https://expo.dev/orbit) is a macOS and Windows app designed to speed up installing and running builds from EAS. It makes running your builds and updates as easy as pressing **Open in Orbit**.
Note: How does automatic installation and launching of updates work?
---
When you launch an update, Orbit will look for the latest development build that matches the runtime version and target platform of the update. If a compatible build is found, the update will install automatically on the target device and launch with a deep link that points to the update.
If you don't have any development builds available, either because they have all expired, you haven't created one, you don't use EAS Build, or you are [building your app locally](/guides/local-app-development/), then Orbit will prompt you on how to proceed. Click **Launch with deep link** in the prompt to open the update if you already have a compatible development build installed on your target device.
---
## Prerequisites
- **Install the Orbit app** before following the steps in this guide. You can download it directly from [GitHub releases](https://github.com/expo/orbit/releases) or see the [alternative method](/build/orbit/#installation) to install it.
- After installing the app, sign in to your Expo account from **Settings**.
## Preview an update with Expo Orbit
Previewing with Expo Orbit requires you to have an update published. If you haven't published an update, see [Publish an update](/eas-update/getting-started/#publish-an-update) before following the steps in the next section.
### Install and launch the update
>
注意:使用 Expo Orbit 启动更新不支持在物理 iOS 设备上进行。它支持 Android 设备/模拟器或 iOS 模拟器。
¥**Note**: Launching updates using Expo Orbit is not supported on physical iOS devices. It is supported on Android devices/emulators or iOS Simulators.
发布更新后,请按照以下步骤在 Android 模拟器或 iOS 模拟器上打开它:
¥After the update is published, follow these steps to open it on an Android Emulator or iOS Simulator:
* 导航到项目的更新选项卡。
¥Navigate your project's **Updates** tab.
* 选择你要预览的更新。
¥Select the update you want to preview.
* 单击预览。这将打开预览对话框。
¥Click **Preview**. This will open the **Preview** dialog.
* 在使用 Orbit 打开下,选择一个平台来启动更新。
¥Under **Open with Orbit**, select a platform to launch the update.
* Orbit 将在选定的 Android 模拟器或 iOS 模拟器上安装并启动更新。
¥Orbit will install and launch the update on the selected Android Emulator or iOS Simulator.
你现在可以使用 Expo Orbit 无缝启动和查看更新。
¥You can now seamlessly launch and review updates using Expo Orbit.
# 部署
## 为应用商店构建你的项目
了解如何为你的应用创建生产版本,该版本已准备好使用 EAS Build 从命令行提交到应用商店。
无论你是使用 [EAS](/build/setup/) 还是 [locally](/guides/local-app-development/) 构建了原生应用二进制文件,应用开发之旅的下一步都是将你的应用提交到商店。为此,你需要创建一个生产版本。
¥Whether you have built a native app binary using [EAS](/build/setup/) or [locally](/guides/local-app-development/), the next step in your app development journey is to submit your app to the stores. To do so, you need to create a **production build**.
生产版本提交到应用商店,向公众发布或作为商店辅助测试流程(如 TestFlight)的一部分。本指南介绍如何使用 [EAS](#production-builds-using-eas) 和 [locally](#production-builds-locally) 创建生产版本。还可以使用任何能够编译 Android 和 iOS 应用的 CI 服务为 Expo 应用创建生产版本。
¥Production builds are submitted to app stores for release to the general public or as part of a store-facilitated testing process such as TestFlight. This guide explains how to create production builds with [EAS](#production-builds-using-eas) and [locally](#production-builds-locally). It is also possible to create production builds for Expo apps with any CI service capable of compiling Android and iOS apps.
## 推进推出
¥Production builds using EAS
生产版本必须通过各自的应用商店安装。你无法直接将它们安装在你的 Android 模拟器、iOS 模拟器或设备上。唯一的例外是,如果你在构建配置文件中明确为 Android 设置了 `"buildType": "apk"`。但是,建议在提交到商店时使用 aab,这是默认配置。
¥Production builds must be installed through their respective app stores. You cannot install them directly on your Android Emulator, iOS Emulator, or device. The only exception to this is if you explicitly set `"buildType": "apk"` for Android on your build profile. However, it is recommended to use **aab** when submitting to stores, and this is the default configuration.
### `eas.json` 配置
¥`eas.json` configuration
当你创建第一个构建时,已经在 eas.json 中创建了用于构建生产构建的最小配置:
¥A minimal configuration for building a production build in **eas.json** is already created when you create your first build:
```json eas.json
{
"build": {
"production": {}
}
}
```
### 创建生产版本
¥Create a production build
要创建生产版本,请为平台运行以下命令:
¥To create a production build, run the following command for a platform:
```sh
$ eas build --platform android
```
```sh
$ eas build --platform ios
```
你可以通过将 `--message` 传递给构建命令(例如 `eas build --platform ios --message "Some message"`)来将消息附加到构建。该消息将显示在 EAS 仪表板上。当你想要为你的团队指定构建目的时,它会派上用场。
¥You can attach a message to the build by passing `--message` to the build command, for example, `eas build --platform ios --message "Some message"`. The message will appear on the EAS dashboard. It comes in handy when you want to specify the purpose of the build for your team.
或者,你可以使用 `--platform all` 选项同时为 Android 和 iOS 构建:
¥Alternatively, you can use `--platform all` option to build for Android and iOS at the same time:
```sh
$ eas build --platform all
```
## 开发者账户
¥Developer account
你需要拥有要提交应用的应用商店的开发者账户。
¥You will need to have a developer account for the app store you want to submit your app.
Note: Google Play Developer membership is required to distribute to the Google Play Store.
---
You can build and sign your app using EAS Build, but you can't upload it to the Google Play Store unless you have a membership, a one-time $25 USD fee.
---
Note: Apple Developer Program membership is required to build for the Apple App Store.
---
If you are going to use EAS Build to create production builds for the Apple App Store, you need access to an account with a $99 USD [Apple Developer Program](https://developer.apple.com/programs) membership.
---
## App signing credentials
Before the build process can start for app stores, you need a store developer account and generate or provide app signing credentials.
Whether you have experience with generating app signing credentials or not, EAS CLI can do the heavy lifting. You can opt-in for EAS CLI to handle the app signing credentials process.
### Android app signing credentials
- If you have not yet generated a keystore for your app, use EAS CLI by selecting `Generate new keystore`, and then you are done. The keystore is stored securely on EAS servers.
- If you want to manually generate your keystore, see the [manual Android credentials guide](/app-signing/local-credentials#android-credentials) for more information.
### iOS app signing credentials
- If you have not generated a provisioning profile and/or distribution certificate yet, use EAS CLI by signing in to your Apple Developer Program account and following the prompts.
- If you want to manually generate your credentials, see the [manual iOS credentials guide](/app-signing/local-credentials#ios-credentials) for more information.
## Wait for the build to complete
By default, the `eas build` command will wait for your build to complete, but you can interrupt it if you prefer not to wait. Instead, use the builds details page link prompted by EAS CLI to monitor the build progress and read the build logs. You can also find this page by visiting [your build dashboard](https://expo.dev/builds) or running the following command:
```sh
$ eas build:list
```
If you are a member of an organization and your build is on its behalf, you will find the build details on [the build dashboard for that account](https://expo.dev/accounts/[account]/builds).
## Create builds automatically
You can automatically create builds on commits to specific branches with [EAS Workflows](/eas/workflows/get-started/). First, you'll need to [configure your project](/eas/workflows/get-started/#configure-your-project), add a file named **.eas/workflows/create-builds.yml** at the root of your project, then add the following workflow configuration:
```yaml .eas/workflows/create-builds.yml
name: Create builds
on:
push:
branches: ['main']
jobs:
build_android:
name: Build Android app
type: build
params:
platform: android
profile: production
build_ios:
name: Build iOS app
type: build
params:
platform: ios
profile: production
```
The workflow above will create Android and iOS builds on every commit to your project's `main` branch. You can also run this workflow manually with the following EAS CLI command:
```sh
$ eas workflow:run create-builds.yml
```
Learn more about common patterns with the [workflows examples guide](/eas/workflows/examples).
## Production builds locally
To create a production build locally, see the following React Native guides for more information on the necessary steps for Android and iOS.
These guides assume your project has **android** and/or **ios** directories containing the respective native projects. If you use [Continuous Native Generation](/workflow/continuous-native-generation) then you will need to run [prebuild](/workflow/prebuild) to generate the directories before following the guides.
>
注意:按照以下指南,在第四步中,当你为 Android 构建发布 .aab 时,从 android 目录运行 `./gradlew app:bundleRelease` 而不是 `npx react-native build-android --mode=release`。
¥**Note**: Following the guide below, in step four, when you build the release **.aab** for Android, run `./gradlew app:bundleRelease` from the **android** directory instead of `npx react-native build-android --mode=release`.
## 下一步
¥Next step
## 提交至应用商店
了解如何使用 EAS Submit 从命令行将应用提交到 Google Play 商店和 Apple App Store。
EAS Submit 是一项托管服务,允许使用 EAS CLI 上传应用二进制文件并将其提交到应用商店。本指南介绍如何使用 EAS Submit 将你的应用提交到 Google Play Store 和 Apple App Store。
¥**EAS Submit** is a hosted service that allows uploading and submitting app binaries to the app stores using EAS CLI. This guide describes how to submit your app to the Google Play Store and Apple App Store using EAS Submit.
Video Tutorial: [How to quickly publish to the App Store & Play Store with EAS Submit](https://www.youtube.com/watch?v=-KZjr576tuE)
## 苹果应用商店
¥Apple App Store
需要 Apple 开发者账户才能将你的应用提交到 Apple App Store。你可以在 [苹果开发者门户](https://developer.apple.com/account/) 上注册 Apple 开发者账户。
¥An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/).
在 app.json 中包含你应用的 bundle identifier:
¥Include your app's bundle identifier in **app.json**:
```json app.json
{
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp"
}
}
```
安装 EAS CLI 并使用你的 Expo 账户登录:
¥Install EAS CLI and login with your Expo account:
```sh
$ npm install -g eas-cli && eas login
```
你需要一个准备好提交到商店的生产版本。你可以使用 [EAS 构建](/build/introduction/) 创建一个:
¥You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform ios --profile production
```
或者,你也可以在自己的电脑上使用 `eas build --platform ios --profile production --local` 或 Xcode 构建应用。
¥Alternatively, you can build the app on your own computer with `eas build --platform ios --profile production --local` or with Xcode.
完成所有先决条件后,即可启动提交流程。
¥Once you have completed all the prerequisites, you can start the submission process.
运行以下命令将构建提交到 Apple App Store:
¥Run the following command to submit a build to the Apple App Store:
```sh
$ eas submit --platform ios
```
该命令将引导你逐步完成提交应用的过程。
¥The command will lead you step by step through the process of submitting the app.
## 谷歌应用商店
¥Google Play Store
需要 Google Play 开发者账户才能将你的应用提交到 Google Play 商店。你可以在 [Google Play 管理中心注册页面](https://play.google.com/apps/publish/signup/) 上注册 Google Play 开发者账户。
¥A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up for a Google Play Developer account on the [Google Play Console sign-up page](https://play.google.com/apps/publish/signup/).
EAS 要求你上传并配置 Google 服务账户密钥,才能将你的 Android 应用提交到 Google Play 商店。你可以按照 [使用 EAS 上传 Google 服务帐号密钥,用于 Play 商店提交](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) 指南创建一个。
¥EAS requires you to upload and configure a Google Service Account Key to submit your Android app to the Google Play Store. You can create one with the [uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) guide.
点击 [谷歌游戏控制台](https://play.google.com/apps/publish/) 中的“创建应用”创建一个应用。
¥Create an app by clicking **Create app** in the [Google Play Console](https://play.google.com/apps/publish/).
安装 EAS CLI 并使用你的 Expo 账户登录:
¥Install EAS CLI and login with your Expo account:
```sh
$ npm install -g eas-cli && eas login
```
在 app.json 中包含你应用的包名:
¥Include your app's package name in **app.json**:
```json app.json
{
"android": {
"package": "com.yourcompany.yourapp"
}
}
```
你需要一个准备好提交到商店的生产版本。你可以使用 [EAS 构建](/build/introduction/) 创建一个:
¥You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform android --profile production
```
或者,你可以使用 `eas build --platform android --profile production --local` 或 Android Studio 在自己的计算机上构建应用。
¥Alternatively, you can build the app on your own computer with `eas build --platform android --profile production --local` or with Android Studio.
你必须至少手动上传一次你的应用。这是 Google Play 商店 API 的限制。
¥You have to upload your app manually at least once. This is a limitation of the Google Play Store API.
请参阅 [首次提交 Android 应用](https://expo.fyi/first-android-submission) 指南了解如何操作。
¥Learn how with the [first submission of an Android app](https://expo.fyi/first-android-submission) guide.
完成所有先决条件后,即可启动提交流程。
¥Once you have completed all the prerequisites, you can start the submission process.
运行以下命令将构建提交到 Google Play Store:
¥Run the following command to submit a build to the Google Play Store:
```sh
$ eas submit --platform android
```
该命令将引导你逐步完成提交应用的过程。
¥The command will lead you step by step through the process of submitting the app.
## 自动构建和提交
¥Build and submit automatically
你可以使用 [EAS 工作流程](/eas/workflows/get-started/) 自动创建构建并将其提交到应用商店。首先,你需要 [配置你的项目](/eas/workflows/get-started/#configure-your-project),在项目根目录下添加一个名为 .eas/workflows/build-and-submit.yml 的文件,然后添加以下工作流配置:
¥You can automatically create builds and submit them to the app stores with [EAS Workflows](/eas/workflows/get-started/). First, you'll need to [configure your project](/eas/workflows/get-started/#configure-your-project), add a file named **.eas/workflows/build-and-submit.yml** at the root of your project, then add the following workflow configuration:
```yaml .eas/workflows/build-and-submit.yml
name: Build and submit
on:
push:
branches: ['main']
jobs:
build_android:
name: Build Android app
type: build
params:
platform: android
profile: production
build_ios:
name: Build iOS app
type: build
params:
platform: ios
profile: production
submit_android:
name: Submit Android
type: submit
needs: [build_android]
params:
build_id: ${{ needs.build_android.outputs.build_id }}
submit_ios:
name: Submit iOS
type: submit
needs: [build_ios]
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
```
上述工作流程将在每次提交到项目的 `main` 分支时创建 Android 和 iOS 版本,然后分别将它们提交到 Google Play 和 Apple App Store。你也可以使用以下 EAS CLI 命令手动运行此工作流程:
¥The workflow above will create Android and iOS builds on every commit to your project's `main` branch, then submit them to the Google Play and Apple App Store respectively. You can also run this workflow manually with the following EAS CLI command:
```sh
$ eas workflow:run build-and-submit.yml
```
了解更多关于 [工作流示例指南](/eas/workflows/examples) 的常见模式。
¥Learn more about common patterns with the [workflows examples guide](/eas/workflows/examples).
## 手动提交到应用商店
¥Manual submission to app stores
你也可以手动将你的应用提交到 Google Play 商店和 Apple App Store。
¥You can also submit your app manually to the Google Play Store and Apple App Store.
## 下一步
¥Next step
## 应用存储元数据
简要概述如何使用 EAS 元数据来自动化和维护应用商店的存在。
> **warning** EAS 元数据处于预览阶段,可能会发生重大更改。
>
> ¥EAS Metadata is in preview and subject to breaking changes.
将应用提交到应用商店时,你需要提供元数据。此过程很漫长,并且通常涉及不适用于你的应用的复杂主题。你提供的信息经过审核后,如果存在任何问题,你需要重新启动此流程。
¥When submitting your app to app stores, you need to provide metadata. This process is lengthy and is often about complex topics that don't apply to your app. After the information you provide gets reviewed and if there is any issue with it, you need to restart this process.
EAS 元数据使你能够从命令行自动化和维护此信息,而不是通过应用商店仪表板中的多个表单。它还可以立即识别众所周知的应用商店限制,这些限制可能会在漫长的审核队列后触发拒绝。本指南展示了如何使用 EAS 元数据来自动化和维护你的应用商店的存在。
¥**EAS Metadata** enables you to automate and maintain this information from the command line instead of going through multiple forms in the app store dashboards. It can also instantly identify well-known app store restrictions that could trigger a rejection after a lengthy review queue. This guide shows how to use EAS Metadata to automate and maintain your app store presence.
## 先决条件
¥Prerequisites
EAS 元数据目前仅支持 Apple App Store。
¥EAS Metadata currently **only supports the Apple App Store**.
> 使用 VS Code?在 store.config.json 文件中安装 [Expo 工具扩展](https://github.com/expo/vscode-expo#readme) 以实现自动补齐、建议和警告。
>
> ¥Using VS Code? Install the [Expo Tools extension](https://github.com/expo/vscode-expo#readme) for auto-complete, suggestions, and warnings in your **store.config.json** files.
## 创建存储配置
¥Create a store config
EAS 元数据使用 [store.config.json](/eas/metadata/config/) 文件来保存你要上传到应用商店的所有信息。该文件位于 Expo 项目的根目录下。
¥EAS Metadata uses [store.config.json](/eas/metadata/config/) file to hold all the information you want to upload to the app stores. This file is located at the root of your Expo project.
在项目目录的根目录下创建一个新的 store.config.json 文件,如下例所示:
¥Create a new **store.config.json** file at the root of your project directory as shown in the example below:
```json store.config.json
{
"configVersion": 0,
"apple": {
"info": {
"en-US": {
"title": "Awesome App",
"subtitle": "Your self-made awesome app",
"description": "The most awesome app you have ever seen",
"keywords": ["awesome", "app"],
"marketingUrl": "https://example.com/en/promo",
"supportUrl": "https://example.com/en/support",
"privacyPolicyUrl": "https://example.com/en/privacy"
}
}
}
}
```
上面的示例文件包含 JSON 架构。将示例值替换为你自己的值。它通常包含你应用的 `title`、`subtitle`、`description`、`keywords` 和 `marketingUrl` 等。
¥The above example file contains JSON schema. Replace the example values with your own. It is usually contains your app's `title`, `subtitle` , `description`, `keywords`, and `marketingUrl` and so on.
从上面的示例中需要记住的重要一点是 `configVersion` 属性。它有助于不向后兼容的版本控制更改。
¥**An important thing to remember from the above example is the `configVersion` property.** It helps with versioning changes that are not backward compatible.
> 有关可在 store.config.json 中定义的属性的更多信息,请参阅 [EAS 元数据架构](/eas/metadata/schema/#config-schema)。
>
> ¥For more information on properties that can be defined in **store.config.json**, see [Schema for EAS Metadata](/eas/metadata/schema/#config-schema).
## 上传存储配置
¥Upload the store config
> 在将 store.config.json 推送到应用商店之前,你必须上传应用的新二进制文件。请参阅 [应用商店提交](/deploy/submit-to-app-stores/) 了解更多信息。提交并处理二进制文件后,你可以继续执行以下步骤。
>
> ¥Before pushing the **store.config.json** to the app stores, you must upload a new binary of your app. See [App Store submissions](/deploy/submit-to-app-stores/) for more information. After the binary is submitted and processed, you can continue with the step below.
创建 store.config.json 文件并添加与应用相关的必要信息后,你可以通过运行以下命令将存储配置推送到应用商店:
¥After you have created the **store.config.json** file and added the necessary information related to your app, you can push the store config to the app stores by running the command:
```sh
$ eas metadata:push
```
如果 EAS 元数据在你的存储配置中遇到任何问题,它会在运行此命令时向你发出警告。当没有错误,或者你确认推送但可能存在问题时,它会尝试上传尽可能多的内容。
¥If EAS Metadata runs into any issues with your store config, it will warn you when running this command. When there are no errors, or you confirm to push it with possible issues, it will try to upload as much as possible.
当你修改 store.config.json 文件并希望将最新更改推送到应用商店时,你还可以重复使用此命令。
¥You can also re-use this command when you modify the **store.config.json** file and want to push the latest changes to the app stores.
## 下一步
¥Next steps
## 发送无线更新
了解如何发送无线更新以向用户推送关键错误修复和改进。
你可以向用户发送包含关键错误修复和改进的无线更新。
¥You can send over-the-air updates containing critical bug fixes and improvements to your users.
## 开始使用
¥Get started
> 如果你之前发布过 [previews](/review/share-previews-with-your-team/) 或创建过 [build](/deploy/build-project/),则可能已经设置了更新并可以跳过此部分。
>
> ¥If you've published [previews](/review/share-previews-with-your-team/) or created a [build](/deploy/build-project/) before, you may have already set up updates and can skip this section.
要设置更新,请运行以下 [EAS 命令行接口](/develop/tools/#eas-cli) 命令:
¥To set up updates, run the following [EAS CLI](/develop/tools/#eas-cli) command:
```sh
$ eas update:configure
```
命令完成后,你需要进行新构建,然后才能继续下一部分。
¥After the command completes, you'll need to make new builds before continuing to the next section.
## 发送更新
¥Send an update
要发送更新,请运行以下 [EAS 命令行接口](/develop/tools/#eas-cli) 命令:
¥To send an update, run the following [EAS CLI](/develop/tools/#eas-cli) command:
```sh
$ eas update --channel production
```
此命令将创建一个更新,并将其提供给配置为在 `production` 通道上接收更新的应用版本。此通道在 [**eas.json**](/eas/json/#channel) 中定义。
¥This command will create an update and make it available to builds of your app that are configured to receive updates on the `production` channel. This channel is defined in [**eas.json**](/eas/json/#channel).
你可以通过强制关闭应用并重新打开两次来验证更新是否有效。更新应在第二次启动时应用。
¥You can verify the update works by force closing the app and reopening it two times. The update should be applied on the second launch.
## 自动发送更新
¥Send updates automatically
你可以使用 [EAS 工作流程](/eas/workflows/get-started/) 自动发送更新。首先,你需要执行 [配置你的项目](/eas/workflows/get-started/#configure-your-project) 操作,在项目根目录下添加一个名为 .eas/workflows/send-updates.yml 的文件,然后添加以下工作流配置:
¥You can automatically send updates with [EAS Workflows](/eas/workflows/get-started/). First, you'll need to [configure your project](/eas/workflows/get-started/#configure-your-project), add a file named **.eas/workflows/send-updates.yml** at the root of your project, then add the following workflow configuration:
```yaml .eas/workflows/send-updates.yml
name: Send updates
on:
push:
branches: ['main']
jobs:
send_updates:
name: Send updates
type: update
params:
channel: production
```
上述工作流程将在每次提交到项目的 `main` 分支时,通过无线方式向 `production` 更新渠道发送更新。你也可以使用以下 EAS CLI 命令手动运行此工作流程:
¥The workflow above will send an over-the-air update for the `production` update channel on every commit to your project's `main` branch. You can also run this workflow manually with the following EAS CLI command:
```sh
$ eas workflow:run send-updates.yml
```
了解更多关于 [工作流示例指南](/eas/workflows/examples) 的常见模式。
¥Learn more about common patterns with the [workflows examples guide](/eas/workflows/examples).
## 了解更多
¥Learn more
你可以通过我们的 [更新指南](/eas-update/introduction/) 了解如何使用 [推出更新](/eas-update/rollouts/)、[优化资源](/eas-update/optimize-assets/) 等。
¥You can learn how to [rollout an update](/eas-update/rollouts/), [optimize assets](/eas-update/optimize-assets/), and more with our [update guides](/eas-update/introduction/).
## 发布你的 Web 应用
了解如何使用 EAS Hosting 部署你的 Web 应用。
> **warning** EAS Hosting 处于预览阶段,可能会发生变化。
>
> ¥**EAS Hosting** is in preview and subject to changes.
如果你正在构建通用应用,则可以使用 [EAS 托管](/eas/hosting/introduction/) 快速部署你的 Web 应用。它是一种用于部署使用 Expo Router 和 React 构建的 Web 应用的服务。
¥If you are building a universal app, you can quickly deploy your web app using [EAS Hosting](/eas/hosting/introduction/). It is a service for deploying web apps built with Expo Router and React.
## 先决条件
¥Prerequisites
开始之前,在项目的 app.json 文件中,确保 [`expo.web.output`](/versions/latest/config/app/#output) 属性为 `static` 或 `server`。
¥Before you begin, in your project's **app.json** file, ensure that the [`expo.web.output`](/versions/latest/config/app/#output) property is either `static` or `server`.
## 导出你的 Web 项目
¥Export your web project
要部署你的 Web 应用,你需要创建 Web 项目的静态构建。通过运行以下命令将你的 Web 项目导出到 dist 目录中:
¥To deploy your web app, you need to create a static build of your web project. Export your web project into a **dist** directory by running the following command:
```sh
$ npx expo export --platform web
```
> 请记住,每次在部署之前对 Web 应用进行更改时,都要重新运行此命令。
>
> ¥Remember to re-run this command every time before deploying when you make changes to your web app.
## 初始部署
¥Initial deployment
要发布你的 Web 应用,请运行以下 [EAS 命令行接口](/develop/tools/#eas-cli) 命令:
¥To publish your web app, run the following [EAS CLI](/develop/tools/#eas-cli) command:
```sh
$ eas deploy
```
首次运行此命令后,系统将提示你为项目选择一个预览子域。此子域是用于创建预览 URL 的前缀,用于生产部署。例如,在 `https://test-app--1234.expo.app` 中,`test-app` 是预览子域。
¥After running this command for the first time, you'll be prompted to select a preview subdomain for your project. This subdomain is a prefix used to create a preview URL and is used for production deployments. For example, in `https://test-app--1234.expo.app`, `test-app` is the preview subdomain.
部署完成后,EAS CLI 将输出预览 URL 以访问你部署的应用。
¥Once your deployment is complete, the EAS CLI will output a preview URL to access your deployed app.
## 生产部署
¥Production deployment
要创建生产部署,请运行以下 [EAS 命令行接口](/develop/tools/#eas-cli) 命令:
¥To create a production deployment, run the following [EAS CLI](/develop/tools/#eas-cli) command:
```sh
$ eas deploy --prod
```
部署完成后,EAS CLI 将输出生产 URL 以访问你部署的应用。
¥Once your deployment is complete, the EAS CLI will output a production URL to access your deployed app.
## 自动部署
¥Deploy automatically
你可以使用 [EAS 工作流程](/eas/workflows/get-started/) 自动将你的应用部署到 Web 上。首先,你需要 [配置你的项目](/eas/workflows/get-started/#configure-your-project),在项目根目录下添加一个名为 .eas/workflows/deploy-web.yml 的文件,然后添加以下工作流配置:
¥You can automatically deploy your app to the web with [EAS Workflows](/eas/workflows/get-started/). First, you'll need to [configure your project](/eas/workflows/get-started/#configure-your-project), add a file named **.eas/workflows/deploy-web.yml** at the root of your project, then add the following workflow configuration:
```yaml .eas/workflows/deploy-web.yml
name: Deploy web
on:
push:
branches: ['main']
jobs:
deploy_web:
name: Deploy web
type: deploy
params:
prod: true
```
上述工作流程将在每次提交到项目的 `main` 分支时创建 Web 部署。你也可以使用以下 EAS CLI 命令手动运行此工作流程:
¥The workflow above will create a web deployment on every commit to your project's `main` branch. You can also run this workflow manually with the following EAS CLI command:
```sh
$ eas workflow:run deploy-web.yml
```
了解更多关于 [工作流示例指南](/eas/workflows/examples) 的常见模式。
¥Learn more about common patterns with the [workflows examples guide](/eas/workflows/examples).
## 了解更多
¥Learn more
你可以了解有关设置 [部署别名](/eas/hosting/deployments-and-aliases/)、使用 [自定义域](/eas/hosting/custom-domain/) 或 [部署 API 路由](/router/reference/api-routes/#deployment) 的更多信息。
¥You can learn more about setting up [deployment aliases](/eas/hosting/deployments-and-aliases/), using a [custom domain](/eas/hosting/custom-domain/), or [deploying an API Route](/router/reference/api-routes/#deployment).
# 监控
## 监控服务
了解如何在发布 Expo 和 React Native 应用后监控其使用情况。
发布应用后,你可以跟踪匿名使用数据,以了解用户如何使用你的应用。此数据包括正在使用哪些更新、用户何时遇到错误等。
¥Once your app is released, you can track anonymized usage data to give you insights on how users use your app. This data includes which updates are in use, when users experience bugs, and more.
## EAS Insights
Expo 提供 [`expo-insights`](/eas-insights/introduction/) 库,用于跟踪与 [EAS 更新](/deploy/send-over-the-air-updates/) 相关的信息。此数据包括应用版本、平台、操作系统版本和更新采用情况。安装并在应用商店发布生产版本后,你将能够在项目仪表板上看到其他数据:
¥Expo provides the [`expo-insights`](/eas-insights/introduction/) library, which tracks information related to [EAS Update](/deploy/send-over-the-air-updates/). This data includes the app version, platform, OS version, and update adoption. After you install it and release production builds on the app stores, you'll be able to see additional data on your project dashboard:
从以下指南开始:
¥Get started with the following guide:
## LogRocket
你可以通过 [LogRocket](https://logrocket.com) 获得更多见解。LogRocket 记录用户会话并在用户使用你的应用时识别错误。你可以按更新 ID 过滤会话,还可以在 EAS 仪表板上连接到你的 LogRocket 账户,以快速访问应用的会话数据。
¥You can get more insights with [LogRocket](https://logrocket.com). LogRocket records user sessions and identifies bugs as your users use your app. You can filter sessions by update IDs and also connect to your LogRocket account on the EAS dashboard to get quick access to your app's session data.
从以下指南开始:
¥Get started with the following guide:
## Sentry
[Sentry](http://getsentry.com/) 是一个崩溃报告平台,它提供对生产部署的实时洞察,并提供重现和修复崩溃的信息。
¥[Sentry](http://getsentry.com/) is a crash reporting platform that provides real-time insight into production deployments with information to reproduce and fix crashes.
它会通知你用户在使用你的应用时遇到的异常或错误,并在网络仪表板上为你组织它们。自动报告的异常包括堆栈跟踪、设备信息、版本和其他相关上下文。你还可以提供特定于你的应用的其他上下文,例如当前路由和用户 ID。
¥It notifies you of exceptions or errors that your users run into while using your app and organizes them for you on a web dashboard. Reported exceptions include stacktraces, device info, version, and other relevant context automatically. You can also provide additional context that is specific to your app, such as the current route and user ID.
从以下指南开始:
¥Get started with the following guide:
## 维克斯
¥Vexo
[维克斯](https://www.vexo.co/) 可帮助你了解用户如何与你的 Expo 应用互动、识别摩擦点并提高参与度。它通过简单的两行集成提供实时用户分析,并提供完整的仪表板,深入了解用户活动、应用性能和采用趋势,以及热图、会话重播等功能。
¥[Vexo](https://www.vexo.co/) helps you understand how users interact with your Expo app, identify friction points, and improve engagement. It provides real-time user analytics with a simple two-line integration and offers a complete dashboard with insights into user activity, app performance, and adoption trends, along with features like heatmaps, session replays, and more.
从以下指南开始:
¥Get started with the following guide:
## BugSnag
[BugSnag](https://www.bugsnag.com/) 是一种稳定性监控解决方案,可提供丰富的端到端错误报告和分析,以快速、精确地重现和修复错误。BugSnag 支持全栈,其中包含 50 多个平台的开源库,包括 React Native。
¥[BugSnag](https://www.bugsnag.com/) is a stability monitoring solution that provides rich, end-to-end error reporting and analytics to reproduce and fix errors with speed and precision. BugSnag supports the full stack with open-source libraries for more than 50 platforms, including React Native.
从以下指南开始:
¥Get started with the following guide:
# 更多
## 核心理念
Expo 工具、功能和服务概述。
Expo 是一款 [开源框架](https://github.com/expo/expo/),适用于在 Android、iOS 和 Web 上原生运行的应用。Expo 汇集了移动和 Web 的精华,并支持构建和扩展应用的许多重要功能。
¥Expo is an [open-source framework](https://github.com/expo/expo/) for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app.
`expo` npm 包为 React Native 应用提供了一系列令人难以置信的功能。`expo` 包几乎可以安装在任何 React Native 项目中。
¥The `expo` npm package enables a suite of incredible features for React Native apps. The `expo` package can be installed in nearly **any React Native project**.
## 工具和特性
¥Tools and features
> **info** 所有功能都是免费的、可选的,并且可以相互独立使用。未使用的功能不会给你的应用带来额外的臃肿。
>
> ¥All features are free, optional, and can be used independently of each other. Unused features add no additional bloat to your app.
| 特性 | 与 `expo` | 没有 `expo`(裸 React Native) |
| --------------------------------------------------- | -------- | ------------------------- |
| 完全使用 JavaScript 开发复杂的应用。 | | |
| 使用 Swift 和 Kotlin 编写 JSI 原生模块。 | | |
| 无需 Xcode 或 Android Studio 即可开发应用。 | | |
| 使用 [Snack](https://snack.expo.dev/) 在浏览器中创建并共享示例应用。 | | |
| 重大升级,无需原生更改。 | | |
| 一流的 TypeScript 支持。 | | |
| 从命令行安装原生兼容的库。 | | |
| 使用相同的代码库开发高性能网站。 | | |
| [隧道](/more/expo-cli/#tunneling) 将你的开发服务器连接到任何设备。 | | |
## 服务
¥Services
Expo 背后的团队还提供 Expo 应用服务 (EAS),这是一种深度集成的云服务,用于构建、提交和更新你的 React Native 应用。EAS 可以与任何 React Native 应用一起使用,无论它是否使用 `expo`。
¥The team behind Expo also provides **Expo Application Services (EAS)**, deeply integrated cloud services for building, submitting, and updating your React Native app. EAS can be used with **any React Native app**, regardless of whether it uses `expo` or not.
## 常见问题
有关 Expo 及相关服务的常见问题和限制列表。
本页列出了有关 Expo 及相关服务的一些常见问题和解答。如果此处未解答你的问题,请参阅 [论坛](https://chat.expo.dev/) 了解更多常见问题。
¥This page lists some of the common questions and answers about Expo and related services. If you have a question that is not answered here, see [Forums](https://chat.expo.dev/) for more common questions.
## Expo 是用来做什么的?
¥What is Expo used for?
Expo 是一款 [开源框架](https://github.com/expo/expo),适用于在 Android、iOS 和 Web 上原生运行的应用。Expo 汇集了移动和 Web 的优点,并支持构建和扩展应用的许多重要功能,例如实时更新、即时共享应用和网络支持。`expo` npm 包为 React Native 应用提供了一系列令人难以置信的功能。`expo` 包几乎可以安装在任何 React Native 项目中。请参阅 [Expo 提供什么](/core-concepts) 了解更多信息。
¥Expo is an [open-source framework](https://github.com/expo/expo) for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app such as live updates, instantly sharing your app, and web support. The `expo` npm package enables a suite of incredible features for React Native apps. The `expo` package can be installed in nearly any React Native project. See [what Expo offers](/core-concepts) for more information.
## 公司使用 Expo 吗?
¥Do companies use Expo?
是的,Expo 被全球顶层公司使用,为数亿终端用户提供服务。看看我们的 [showcase](https://expo.dev/customers)。
¥Yes, Expo is used by top companies worldwide, serving hundreds of millions of end users. See our [showcase](https://expo.dev/customers).
## 为什么 Expo 有自己的 SDK?
¥Why does Expo have its own SDK?
当 Expo 首次创建时,React Native 尚未公开发布。这意味着没有第三方软件包。为了使 React Native 的开发者体验合理,我们创建了 [几个库来实现通用功能](/versions/latest)。许多这样的库已经被分叉和修改以满足各种需求。我们欢迎用户混合搭配他们需要的任何 [自定义原生代码](/workflow/customizing),以使他们的应用变得更加出色。
¥When Expo was first created, React Native had yet to be publicly released. This means there were no third-party packages. To make React Native's developer experience reasonable, we created [several libraries to achieve common functionalities](/versions/latest). Many of these libraries have since been forked and modified to meet various needs. We welcome users to mix and match whichever [custom native code](/workflow/customizing) they need to make their app great.
Expo SDK 经过充分测试,用 TypeScript 编写,有文档记录,并且专为 Android、iOS 和 Web 构建。Expo SDK 中的每个模块协同工作以确保版本控制始终匹配。这创造了良好的升级体验。
¥The Expo SDK is well-tested, written in TypeScript, documented, and built for Android, iOS, and the web. Every module in the Expo SDK works together to ensure versioning always matches. This creates a nice upgrading experience.
Expo SDK 也是用 [Expo 模块 API](/modules) 编写的,以便更轻松地贡献、维护和理解。
¥The Expo SDK is also written with the [Expo Modules API](/modules) to make contributing, maintaining, and understanding easier.
## Expo 和 React Native 有什么区别?
¥What is the difference between Expo and React Native?
`expo` 包提供了一套功能,可以更轻松地开发和扩展复杂的 React Native 应用。你几乎可以在任何 React Native 应用中安装 `expo`。使用 [Expo 应用服务(EAS)](/eas) 或 React Native 不需要 `expo` 包,但是,强烈建议使用 `expo` 包。请参阅 [Expo 提供什么](/core-concepts) 了解更多信息。
¥The `expo` package provides a suite of features that make it easier to develop, and scale complex React Native applications. You can install `expo` in nearly any React Native app. The `expo` package is not required to use [Expo Application Services (EAS)](/eas) or React Native, however, it is highly recommended. See [what Expo offers](/core-concepts) for more information.
## 我需要从 React Native 切换到使用 Expo 吗?
¥Do I need to switch from React Native to use Expo?
不,`expo` npm 包和 CLI 可与任何 React Native 应用配合使用。[Expo 应用服务(EAS)](/eas) 还适用于所有 React Native 应用,对构建、更新、应用商店提交等提供一流的支持。
¥No, the `expo` npm package and CLI work with any React Native app. [Expo Application Services (EAS)](/eas) also works with all React Native apps with first-class support for builds, updates, app store submissions, and more.
## Expo 花费多少钱?
¥How much does Expo cost?
Expo 平台为 [免费和开源](https://blog.expo.dev/exponent-is-free-as-in-and-as-in-1d6d948a60dc)。这包括构成用于开发的 [Expo SDK](/versions/latest) 和 [Expo CLI](/more/expo-cli/) 的库。Expo Go 应用是最简单的入门方式,也可从应用商店免费获取。
¥The Expo platform is [free and open source](https://blog.expo.dev/exponent-is-free-as-in-and-as-in-1d6d948a60dc). This includes the libraries that make up the [Expo SDK](/versions/latest) and the [Expo CLI](/more/expo-cli/) used for development. The Expo Go app, the easiest way to get started, is also free from the app stores.
[Expo 应用服务(EAS)](/eas) 是来自 Expo 团队的 React Native 应用的可选云服务套件。EAS 使你可以更轻松地构建应用、将其提交到商店、保持更新、发送推送通知等。如果 [免费计划](https://expo.dev/pricing) 配额足够你的应用使用,你可以免费使用 EAS。更多信息请参见 [定价页面](https://expo.dev/pricing)。
¥[Expo Application Services (EAS)](/eas) is an optional suite of cloud services for React Native apps, from the Expo team. EAS makes it easier to build your app, submit it to the stores, keep it updated, send push notifications, and more. You can use EAS for free if the [Free plan](https://expo.dev/pricing) quotas are sufficient for your app. More information is available on the [pricing page](https://expo.dev/pricing).
## 如何将自定义原生代码添加到我的 Expo 项目?
¥How do I add custom native code to my Expo project?
Expo 支持添加自定义原生代码并自定义该原生代码(Android/Xcode 项目)。要使用任何自定义原生代码,你可以创建 [开发构建](/develop/development-builds/introduction/) 和 [配置插件](/config-plugins/introduction)。我们确实建议尽可能使用 [Expo SDK](/versions/latest) 中的模块,以便更轻松地升级并改善开发者体验。
¥Expo supports adding custom native code and customizing that native code (Android/Xcode projects). To use any custom native code, you can create a [development build](/develop/development-builds/introduction/) and [config plugins](/config-plugins/introduction). We do recommend using the modules in the [Expo SDK](/versions/latest) when possible for easier upgrades and improved developer experience.
## 我可以在使用 React Native CLI 创建的应用中使用 Expo 吗?
¥Can I use Expo in the app that is created with React Native CLI?
是的!所有 Expo 工具和服务在任何 React Native 应用中都能很好地工作。例如,你可以使用 [Expo SDK](/versions/latest)、[`expo-dev-client`](/develop/development-builds/installation/) 和 EAS 构建、提交和更新的任何部分 - 它们效果很好!了解有关 [在你的项目中安装 `expo`](/bare/installing-expo-modules)、[采用预构建](/guides/adopting-prebuild) 和 [设置 EAS 构建](/build/introduction) 的更多信息。
¥Yes! All Expo tools and services work great in any React Native app. For example, you can use any part of the [Expo SDK](/versions/latest), [`expo-dev-client`](/develop/development-builds/installation/) and EAS Build, Submit, and Update — they work great! Learn more about [installing `expo` in your project](/bare/installing-expo-modules), [adopting prebuild](/guides/adopting-prebuild), and [setting up EAS Build](/build/introduction).
## 如何分享我的 Expo 项目?我可以将其提交到应用商店吗?
¥How do I share my Expo project? Can I submit it to the app stores?
共享项目的最快方法是使用 [EAS 更新](/eas-update/introduction) 发布并在 [开发构建](/develop/development-builds/introduction/) 中启动。这为你的应用提供了一个 URL;你可以与任何拥有 Android 或 iOS 版 [开发构建](/develop/development-builds/introduction/) 的人分享此 URL。URL 也可以在 Android 版 Expo Go 中打开。
¥The fastest way to share your project is to publish with [EAS Update](/eas-update/introduction) and launch in a [development build](/develop/development-builds/introduction/). This gives your app a URL; you can share this URL with anybody who has the [development build](/develop/development-builds/introduction/) for Android or iOS. URLs can also be opened in Expo Go for Android.
准备就绪后,你可以创建生产版本(.aab 和 .ipa)以提交到应用商店。你可以使用 [EAS 构建](/build/introduction) 在单个命令中构建应用,然后使用 [EAS 提交](/submit/introduction) 将其提交到商店。
¥When ready, you can create a production build (**.aab** and **.ipa**) to submit to the app stores. You can build your app in a single command with [EAS Build](/build/introduction) and submit it to the stores with [EAS Submit](/submit/introduction).
你还可以使用 [内部分配](/build/internal-distribution) 在 Android 上通过 APK 共享你的应用,在 iOS 上通过临时或企业配置共享你的应用。
¥You can also use [internal distribution](/build/internal-distribution) to share your app with an APK on Android and ad-hoc or enterprise provisioning on iOS.
## 我可以在 Windows 计算机上开发 iOS 应用吗?
¥Can I develop iOS apps on a Windows computer?
传统上,你需要 macOS 来开发 iOS 应用,但是,你可以使用 [EAS 构建](/build/introduction) 在云中构建你的应用。你还可以使用 [EAS 提交](/submit/introduction) 将你的应用提交到商店。可以使用 [Expo](https://expo.dev/go) 或 [开发构建](/develop/development-builds/introduction/) 在物理 iOS 设备上完成测试。
¥Traditionally you needed a macOS to develop iOS apps, however, you can use [EAS Build](/build/introduction) to build your app in the cloud. You can also use [EAS Submit](/submit/introduction) to submit your app to the stores. Testing can be done on a physical iOS device using [Expo Go](https://expo.dev/go) or a [development build](/develop/development-builds/introduction/).
## Expo SDK 支持哪些版本的 Android 和 iOS?
¥What versions of Android and iOS are supported by the Expo SDK?
目前,Expo SDK 支持 Android {latestSdkVersionValues.android} 和 iOS {latestSdkVersionValues.ios}。欲了解更多信息,请参阅 [支持 Android 和 iOS 版本](/versions/latest/#support-for-android-and-ios-versions)。
¥Currently, Expo SDK supports Android {latestSdkVersionValues.android} and iOS {latestSdkVersionValues.ios}. For more information, see [Support for Android and iOS versions](/versions/latest/#support-for-android-and-ios-versions).
## "你好世界" expo 应用的最小大小是多少?
¥What is the minimal size of a "hello world" expo app?
使用纯 Expo 创建的最小生产应用小于 3 MB。对于 iOS,Expo 的目标是更新的最低 iOS 版本,以实现应用商店优化。
¥A bare minimum production app created using pure Expo is less than 3 MB. For iOS, Expo targets a newer minimum iOS version which enables app store optimizations.
如果你的应用中包含 `expo` 软件包,则它只会一次性为应用商店中应用的最终大小增加 1 MB。`expo` 包具有边际大小成本(例如,Android 上为 150 Kib)。其余的大小来自语言运行时(例如 Android 上的 Kotlin)。
¥If the `expo` package is included in your app, it only adds 1 MB one time to the final size of apps on app stores. The `expo` package has a marginal size cost (for example, 150 Kib on Android). The rest of the size comes from the language runtime (such as Kotlin on Android).
## 我可以将 Expo 与我的原生库一起使用吗?
¥Can I use Expo with my native library?
你可以通过使用 Swift 和 Kotlin 创建 [自定义原生模块](/modules),将原生 Android 和 iOS 库与 Expo 结合使用。许多流行的库已经有自定义的原生模块。查看我们的 [React Native 目录](https://reactnative.directory),找到适合你的用例的流行库。
¥You can use native Android and iOS libraries with Expo by creating a [custom native module](/modules) with Swift and Kotlin. Many popular libraries already have custom native modules. Check out our [React Native directory](https://reactnative.directory) to find popular libraries for your use case.
## 我可以将 Expo 与此网络库一起使用吗?
¥Can I use Expo with this web library?
许多流行的 Web 包(例如 Three.js)可与 Expo 和 React Native 配合使用。请参阅 [Expo 实例](https://github.com/expo/examples) 了解更多信息。
¥Many popular web packages such as three.js work with Expo and React Native. See [Expo examples](https://github.com/expo/examples) for more information.
## Expo 与 Web 开发的 React 类似吗?
¥Is Expo similar to React for web development?
Expo 是一款 [开源框架](https://github.com/expo/expo),适用于在 Android、iOS 和 Web 上原生运行的应用。React Native 与 Web 开发中的 `react-dom` 类似,使你能够在特定平台上运行 React,但是,它有一些关键区别:
¥Expo is an [open-source framework](https://github.com/expo/expo) for apps that run natively on Android, iOS, and the web. React Native is similar to `react-dom` from web development, enabling you to run React on a particular platform, however, it has a few key differences:
* React Native 不支持 HTML 或 CSS。
¥React Native does not support HTML or CSS.
* React Native 不使用 DOM,而是使用原生组件。例如,`` 而不是 ``。原生组件比 DOM 性能更高,并提供更好的用户体验。
¥Instead of using the DOM, React Native uses native components. For example, `` instead of ``. Native components are more performant than the DOM and provide a much nicer user experience.
* 与可以访问浏览器 API 的 React.js 不同,React Native 使用自定义原生 API。例如,你使用 `expo-location` 代替 `navigator.geolocation` 来访问用户的位置。自定义原生 API 与浏览器 API 类似,只不过你可以完全控制它们。这意味着你可以在新功能在浏览器中可用之前访问它们。
¥Unlike React.js which has access to browser APIs, React Native uses custom native APIs. For example, instead of `navigator.geolocation`, you use `expo-location` to access the user's location. Custom native APIs are similar to browser APIs except you have full control over them. This means you can access new features before they are available in the browser.
就像 React.js 框架帮助用户轻松创建更大的网站一样,Expo 也帮助用户轻松创建更大的应用。Expo 提供了一套经过充分测试的 React Native 模块,可以在 Android、iOS 和 Web 上运行。Expo 还提供了 [工具套件](/eas) 用于构建、部署和更新你的应用。
¥In the same way React.js frameworks help users create larger websites with ease, Expo helps users create larger apps with ease. Expo provides a suite of well-tested React Native modules that run on Android, iOS, and the web. Expo also provides a [suite of tools](/eas) for building, deploying, and updating your app.
## 关于解释代码的商店政策是什么?
¥What are the store policies regarding interpreted code?
React Native 使用 JavaScript 解释器(JSC、V8 或 Hermes)来运行应用代码。直接参考 [Google Play 政策中心](https://play.google/developer-content-policy/) 和 [Apple 开发者计划许可协议](https://developer.apple.com/support/terms/apple-developer-program-license-agreement) 了解最新的政策信息。
¥React Native uses a JavaScript interpreter (JSC, V8, or Hermes) to run your application code. Refer to the [Google Play Policy Center](https://play.google/developer-content-policy/) and [Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement) directly for the most up-to-date policy information.
以下是截至 2024 年 4 月 25 日的相关政策摘录。
¥*The following are excerpts of related policies, as of April 25, 2024.*
### 谷歌应用商店
¥Google Play Store
```text
...an app may not download executable code (such as dex, JAR, .so files) from a
source other than Google Play. This restriction does not apply to code that runs
in a virtual machine or an interpreter where either provides indirect access to
Android APIs (such as JavaScript in a webview or browser).
Apps or third-party code, like SDKs, with interpreted languages (JavaScript,
Python, Lua, etc.) loaded at run time (for example, not packaged with the app)
must not allow potential violations of Google Play policies.
```
来源:[Google Play 政策中心](https://support.google.com/googleplay/android-developer/answer/9888379?hl=en)。
¥Source: [Google Play Policy Center](https://support.google.com/googleplay/android-developer/answer/9888379?hl=en).
### 苹果应用商店
¥Apple App Store
```text
...Interpreted code may be downloaded to an Application but only so long as such code:
(a) does not change the primary purpose of the Application by providing features
or functionality that are inconsistent with the intended and advertised purpose
of the Application as submitted to the App Store,
(b) does not create a store or storefront for other code or applications, and
(c) does not bypass signing, sandbox, or other security features of the OS.
```
来源:[3.3.1 API 和功能 - B. 可执行代码](https://developer.apple.com/support/terms/apple-developer-program-license-agreement#b331)。
¥Source: [3.3.1 APIs and Functionality - B. Executable Code](https://developer.apple.com/support/terms/apple-developer-program-license-agreement#b331).
## 我应该使用 Expo CLI 还是 React Native Community CLI?
¥Should I use Expo CLI or React Native Community CLI?
Expo CLI 提供与 React Native Community CLI(也称为 "React Native CLI")相同的核心功能,并具有自动 [TypeScript 设置](/guides/typescript)、[网络支持](/workflow/web)、[自动安装兼容库](/more/expo-cli/#install)、[改进的原生构建命令](/more/expo-cli#compiling)、[tunneling](/more/expo-cli#tunneling)、[预建](/workflow/prebuild) 和 [more](/more/expo-cli) 等附加功能。
¥Expo CLI offers the same core functionality as React Native Community CLI (also known as "React Native CLI") with additional features such as automatic [TypeScript setup](/guides/typescript), [web support](/workflow/web), [auto installing compatible libraries](/more/expo-cli/#install), [improved native build commands](/more/expo-cli#compiling), [tunneling](/more/expo-cli#tunneling), [Prebuild](/workflow/prebuild), and [more](/more/expo-cli).
它可以与 React Native Community 同时使用。无论你使用哪种 CLI,你都可以在项目中使用 [Expo SDK](/versions/latest/) 和 [Expo 应用服务](/eas) 的任何部分。有关更多信息,请参阅:
¥It can be used simultaneously with React Native Community. Regardless of which CLI you use, you can use any part of the [Expo SDK](/versions/latest/) and [Expo Application Services](/eas) with your project. For more information, see:
* 了解如何迁移以在 [现有的 React Native 项目](/bare/using-expo-cli/) 中使用 Expo CLI。
¥Learn how you can migrate to use Expo CLI in an [existing React Native project](/bare/using-expo-cli/).
* 了解 [使用带有 配置插件的本地字体文件](https://rn.nodejs.cn/blog/2024/06/25/use-a-framework-to-build-react-native-apps) 的好处。
¥Learn about the benefits of [using a framework to build React Native apps](https://rn.nodejs.cn/blog/2024/06/25/use-a-framework-to-build-react-native-apps).
* 了解迁移到 Expo CLI 的好处,例如提高应用性能、加快发布速度以及在 [此链接](https://expo.dev/blog/from-rnc-cli-to-expo) 中促进团队之间更紧密的协作。
¥Learn about the benefits of migrating to Expo CLI such as improved app performance, expedites release, and fostering stronger collaboration across you team in [this blog post](https://expo.dev/blog/from-rnc-cli-to-expo).
> 注意:EAS Build 与现有的 React Native 项目兼容(其中原生目录已签入版本控制)。当这些目录存在时,EAS Build 不会运行预构建步骤,因为这可能会覆盖你对原生项目文件所做的任何手动自定义设置。你必须使用 Android Studio 或 Xcode 等原生工具自行配置原生目录。
>
> ¥**Note:** EAS Build is compatible with existing React Native projects (where native directories are checked into version control). When these directories are present, EAS Build does not run the prebuild step, as that could overwrite any manual customizations you have made to the native project files. You'll have to configure the native directories on your own with native tools such as Android Studio or Xcode.
## Expo Go 是开源的吗?
¥Is Expo Go open source?
是的,Expo Go 的源代码可以在 apps/expo-go 目录的 [expo/expo GitHub 存储库](https://github.com/expo/expo) 中找到。Expo Go 应用也是使用 Expo 和 React Native 构建的。
¥Yes, the source for Expo Go can be found in the [expo/expo GitHub repository](https://github.com/expo/expo) in the **apps/expo-go** directory. The Expo Go app is also built with Expo and React Native.
## Expo Go 可以做什么或不能做什么?
¥What can I do or cannot do with Expo Go?
[Expo](/get-started/set-up-your-environment/?redirected=#how-would-you-like-to-develop) 是一个沙盒,可以帮助你快速开始 React Native 开发,以便学习、制作原型或进行实验。它允许你使用 Expo SDK 中包含的库以及不需要自定义原生代码的库。
¥[Expo Go](/get-started/set-up-your-environment/?redirected=#how-would-you-like-to-develop) is a sandbox that can help you get started quickly with React Native development in order to learn, prototype, or experiment. It allows you to use libraries included in the Expo SDK and libraries that don't require custom native code.
Expo Go 无法使用需要自定义原生代码的第三方库,并且你无法在 Expo Go 中直接编辑原生代码。它不适用于生产环境的应用。
¥Expo Go cannot use third-party libraries that require custom native code and you cannot edit native code directly in Expo Go. It cannot be used for production apps.
**我们强烈建议任何需要带有原生代码的附加库的项目迁移到 [开发构建](/develop/development-builds/introduction/)。这就像创建一个专门根据你的应用需求定制的 Expo Go 版本。**
¥**We strongly recommend any projects that require additional libraries with native code to migrate to [development builds](/develop/development-builds/introduction/). It's like creating a version of Expo Go that is specifically customized to your app's needs.**
## 弹出功能是否已弃用?
¥Is ejecting deprecated?
是的,eject 是一个已弃用的术语,不再需要。Expo 首次发布时,应用的原生二进制文件较大,并且不支持没有 "ejecting" 的自定义原生代码。2020 年 12 月,随着 [EAS 构建](/build/introduction) 的发布,这种情况发生了改变,[EAS 构建](/build/introduction) 版本支持所有 React Native 应用。在 SDK 41(2021 年 4 月)中,"ejecting" 的概念已被 [`npx expo prebuild`](/workflow/prebuild) 命令取代,该命令会根据项目中的库和应用配置 (app.json) 持续生成原生项目。`expo eject` 命令已在 SDK 46(2022 年 8 月)中完全弃用。
¥Yes, eject is a deprecated term and is no longer necessary. When Expo was first released, apps had larger native binary sizes and didn't support custom native code without "ejecting". This changed in December 2020 with the release of [EAS Build](/build/introduction) which supports any React Native app. The concept of "ejecting" was replaced by the [`npx expo prebuild`](/workflow/prebuild) command in SDK 41 (April 2021), which continuously generates native projects based on the libraries in your project and the app config (**app.json**). The `expo eject` command was fully deprecated in SDK 46 (August 2022).
与之前的弹出工作流程不同,开发者可以通过创建 [配置插件](/config-plugins/introduction/) 来配置他们的库以与 Expo Prebuild 配合使用。这意味着你可以将任何库与 Expo Prebuild 一起使用。你还可以通过创建 [开发构建](/develop/development-builds/introduction/) 文件来将任何自定义原生代码与 Expo Prebuild 结合使用。在 [Expo 预建文档](/workflow/prebuild) 中了解更多信息。
¥Unlike the previous eject workflow, authors can configure their libraries to work with Expo Prebuild by creating a [config plugin](/config-plugins/introduction/). This means you can use any library with Expo Prebuild. You can also use any custom native code with Expo Prebuild by creating a [development build](/develop/development-builds/introduction/). Learn more in the [Expo Prebuild documentation](/workflow/prebuild).
## LLM 文档
可用于大型语言模型 (LLM) 和使用它们的应用的 Expo 和 EAS 文档文件列表。
在 Expo,我们支持 [llms.txt](https://llmstxt.org/) 计划,为大型语言模型 (LLM) 及其使用的应用提供文档。以下是可用文档文件的列表:
¥At Expo, we support the [llms.txt](https://llmstxt.org/) initiative to provide documentation for large language models (LLMs) and apps that use them. Below is a list of documentation files available:
* [/llms.txt](/llms.txt):所有可用文档文件的列表
¥[/llms.txt](/llms.txt): A list of all available documentation files
* [/llms-full.txt](/llms-full.txt):Expo 的完整文档,包括 Expo Router、Expo Modules API、开发过程等
¥[/llms-full.txt](/llms-full.txt): Complete documentation for Expo, including Expo Router, Expo Modules API, development process, and more
* [/llms-eas.txt](/llms-eas.txt):Expo 应用服务 (EAS) 的完整文档
¥[/llms-eas.txt](/llms-eas.txt): Complete documentation for the Expo Application Services (EAS)
* [/llms-sdk.txt](/llms-sdk.txt):最新 Expo SDK 的完整文档
¥[/llms-sdk.txt](/llms-sdk.txt): Complete documentation for the latest Expo SDK
Note: Looking for deprecated Expo SDK versions?
---
- [/llms-sdk-v52.0.0.txt](/llms-sdk-v52.0.0.txt): Documentation for the Expo SDK v52.0.0
- [/llms-sdk-v51.0.0.txt](/llms-sdk-v51.0.0.txt): Documentation for the Expo SDK v51.0.0
---
## 教程和 UI 指南概述
“学习”部分是教程和其他指南的集合,可帮助你了解 Expo 和 EAS。它涵盖以下内容:
¥The **Learn** section is a collection of tutorials and other guides that help you learn about Expo and EAS. It covers the following:
# Expo 教程
## 教程:使用 React Native 和 Expo
介绍如何使用 Expo 构建可在 Android、iOS 和 Web 上运行的通用应用的 React Native 教程。
我们即将踏上构建通用应用的旅程。在本教程中,我们将创建一个在 Android、iOS 和 Web 上运行的 Expo 应用;全部都使用一个代码库。让我们开始吧!
¥We're about to embark on a journey of building universal apps. In this tutorial, we'll create an Expo app that runs on Android, iOS, and web; all with a single codebase. Let's get started!
## 关于 React Native 和 Expo 教程
¥About React Native and Expo tutorial
本教程的目标是开始使用 Expo 并熟悉 Expo SDK。它将涵盖以下主题:
¥The objective of this tutorial is to get started with Expo and become familiar with the Expo SDK. It'll cover the following topics:
* 使用启用 TypeScript 的默认模板创建应用
¥Create an app using the default template with TypeScript enabled
* 使用 Expo Router 实现双屏底部标签布局
¥Implement a two-screen bottom tabs layout with Expo Router
* 分解应用布局并使用 Flexbox 实现
¥Break down the app layout and implement it with flexbox
* 使用每个平台的系统 UI 从媒体库中选择图片
¥Use each platform's system UI to select an image from the media library
* 使用 React Native 中的 `` 和 `` 组件创建贴纸模式
¥Create a sticker modal using the `` and `` components from React Native
* 添加触摸手势以与贴纸交互
¥Add touch gestures to interact with a sticker
* 使用第三方库截屏并保存到磁盘
¥Use third-party libraries to capture a screenshot and save it to the disk
* 处理 Android、iOS 和 Web 之间的平台差异
¥Handle platform differences between Android, iOS, and web
* 最后,完成配置状态栏、启动画面和图标的过程以完成应用
¥Finally, go through the process of configuring a status bar, a splash screen, and an icon to complete the app
这些主题为学习构建 Expo 应用的基础知识提供了基础。本教程是自定进度的,最多可能需要两个小时才能完成。
¥These topics provide a foundation to learn the fundamentals of building an Expo app. The tutorial is self-paced and can take up to two hours to complete.
为了让它对初学者友好,我们将教程分为九章,以便你可以跟着学习或放下它,稍后再回来。每章都包含完成步骤所需的代码片段,因此你可以按照步骤从头开始创建应用或复制和粘贴它。
¥To keep it beginner friendly, we divided the tutorial into nine chapters so that you can follow along or put it down and come back to it later. Each chapter contains the necessary code snippets to complete the steps, so you can follow along by creating an app from scratch or copy and paste it.
在开始之前,先看看我们将构建什么。这是一个名为 StickerSmash 的应用,可在 Android、iOS 和 Web 上运行:
¥Before we get started, take a look at what we'll build. It's an app named **StickerSmash** that runs on Android, iOS, and the web:
> **info** 本教程的完整源代码可在 [GitHub](https://github.com/expo/examples/tree/master/stickersmash) 上找到。
>
> ¥The complete source code for this tutorial is available on [GitHub](https://github.com/expo/examples/tree/master/stickersmash).
## 如何使用本教程
¥How to use this tutorial
我们相信 [边干边学](https://en.wikipedia.org/wiki/Learning-by-doing),所以本教程强调做而不是解释。你可以通过从头开始创建应用来跟踪构建应用的过程。
¥We believe in [learning by doing](https://en.wikipedia.org/wiki/Learning-by-doing), so this tutorial emphasizes doing over explaining. You can follow along the journey of building an app by creating the app from scratch.
在整个教程中,任何重要的代码或示例之间发生更改的代码都将是 highlighted in green。你可以将鼠标悬停在高亮部分上(在桌面上)或点击它们(在移动设备上)以了解有关更改的更多信息。例如,下面代码片段中高亮的代码解释了它的作用:
¥Throughout the tutorial, any important code or code that has changed between examples will be highlighted in green. You can hover over the highlights (on desktop) or tap them (on mobile) to learn more about the change. For example, the code highlighted in the snippet below explains what it does:
```tsx Hello World|collapseHeight=300
import { StyleSheet, Text, View } from 'react-native';
export default function Index() {
return (
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
```
## 下一步
¥Next step
我们已准备好开始构建我们的应用。
¥We're ready to start building our app.
## 创建你的第一个应用
在本章中,了解如何创建新的 Expo 项目。
在本章中,我们将学习如何创建一个新的 Expo 项目以及如何运行它。
¥In this chapter, let's learn how to create a new Expo project and how to get it running.
Video Tutorial: [Watch: Creating your first universal Expo app](https://www.youtube.com/watch?v=m1-bc53EGh8)
***
## 先决条件
¥Prerequisites
我们需要以下内容才能开始:
¥We'll need the following to get started:
* [Expo](https://expo.dev/go) 安装在物理设备上
¥[Expo Go](https://expo.dev/go) installed on a physical device
* [Node.js(LTS 版本)](https://nodejs.cn/en) 安装
¥[Node.js (LTS version)](https://nodejs.cn/en) installed
* [VS Code](https://code.visualstudio.com/) 或已安装的任何其他首选代码编辑器或 IDE
¥[VS Code](https://code.visualstudio.com/) or any other preferred code editor or IDE installed
* 打开终端窗口的 macOS、Linux 或 Windows(PowerShell 和 [WSL2](https://expo.fyi/wsl))
¥A macOS, Linux, or Windows (PowerShell and [WSL2](https://expo.fyi/wsl)) with a terminal window open
本教程假设你熟悉 TypeScript 和 React。如果你不熟悉它们,请查看 [TypeScript 手册](https://ts.nodejs.cn/docs/handbook/2/everyday-types.html) 和 [React 官方教程](https://react.nodejs.cn/learn)。
¥The tutorial assumes that you are familiar with TypeScript and React. If you are not familiar with them, check out the [TypeScript Handbook](https://ts.nodejs.cn/docs/handbook/2/everyday-types.html) and [React's official tutorial](https://react.nodejs.cn/learn).
Step 1:
## Initialize a new Expo app
We'll use [`create-expo-app`](/more/create-expo/) to initialize a new Expo app. It is a command line tool to create a new React Native project. Run the following command in your terminal:
```sh
$ npx create-expo-app@latest StickerSmash
$ cd StickerSmash
```
This command will create a new project directory named StickerSmash, using the [default](/more/create-expo/#--template) template. This template has essential boilerplate code and libraries needed to build our app, including Expo Router. We'll continue to add more libraries throughout this tutorial as needed.
Note: Benefits of using the default template
---
- Creates a new React Native project with `expo` package installed
- Includes recommended tools such as Expo CLI
- Includes a tab navigator from Expo Router to provide a basic navigation system
- Automatically configured to run a project on multiple platforms: Android, iOS, and web
- TypeScript configured by default
---
Step 2:
## Download assets
!!!IG5!!!
After downloading the archive:
1. Unzip the archive and replace the default assets in the **your-project-name/assets/images** directory.
2. Open the project directory in a code editor or IDE.
Step 3:
## Run reset-project script
In this tutorial, we'll build our app from scratch and understand the fundamentals of adding a file-based navigation. Let's run the `reset-project` script to remove the boilerplate code:
```sh
$ npm run reset-project
```
After running the above command, there are two files (**index.tsx** and **\_layout.tsx**) left inside the **app** directory. The previous files from **app** and other directories (**components**, **constants**, and **hooks** — containing boilerplate code) are moved inside the **app-example** directory by the script. We'll create our own directories and component files as we go along.
Note: What does the script do?
---
`reset-project` script resets the **app** directory structure in a project and copies the previous boilerplate files from the project's root directory to another sub-directory called **app-example**. We can delete it since it is not part of our main app's structure.
---
Step 4:
## Run the app on mobile and web
In the project directory, run the following command to start the [development server](/more/glossary-of-terms/#development-server) from the terminal:
```sh
$ npx expo start
```
After running the above command:
1. The development server will start, and you'll see a QR code inside the terminal window.
2. Scan that QR code to open the app on the device. On Android, use the Expo Go > **Scan QR code** option. On iOS, use the default camera app.
3. To run the web app, press w in the terminal. It will open the web app in the default web browser.
Once it is running on all platforms, the app should look like this:
Step 5:
## Edit the index screen
The **app/index.tsx** file defines the text displayed on the app's screen. It is the entry point of our app and executes when the development server starts. It uses core React Native components such as `` and `` to display background and text.
Styles applied to these components use JavaScript objects rather than CSS, which is used on web. However, a lot of the properties will look familiar if you've previously used CSS on web. Most React Native components accept a `style` prop that accepts a JavaScript object as its value. For more details, see [Styling in React Native](https://rn.nodejs.cn/docs/style).
Let's modify **app/index.tsx** screen:
1. Import `StyleSheet` from `react-native` and create a `styles` object to define our custom styles.
2. Add a `styles.container.backgroundColor` property to `` with the value of `#25292e`. This changes the background color.
3. Replace the default value of `` with "Home screen".
4. Add a `styles.text.color` property to `` with the value of `#fff` (white) to change the text color.
!!!IG4!!!
!!!IG3!!!
> React Native uses the same color format as the web. It supports hex triplets (this is what `#fff` is), `rgba`, `hsl`, and named colors, such as `red`, `green`, `blue`, `peru`, and `papayawhip`. For more information, see [Colors in React Native](https://rn.nodejs.cn/docs/colors).
This change will reflect on all platforms automatically:
## Summary
## 添加导航
在本章中,了解如何向 Expo 应用添加导航。
在本章中,我们将学习 Expo Router 的基础知识,以创建堆栈导航和带有两个选项卡的底部选项卡栏。
¥In this chapter, we'll learn Expo Router's fundamentals to create stack navigation and a bottom tab bar with two tabs.
Video Tutorial: [Watch: Adding navigation in your universal Expo app](https://www.youtube.com/watch?v=8336fcFV_T4)
## Expo 路由基础知识
¥Expo Router basics
Expo Router 是一个基于文件的路由框架,适用于 React Native 和 Web 应用。它管理屏幕之间的导航,并在多个平台上使用相同的组件。要开始,我们需要了解以下约定:
¥Expo Router is a file-based routing framework for React Native and web apps. It manages navigation between screens and uses the same components across multiple platforms. To get started, we need to know about the following conventions:
* app 目录:仅包含路由及其布局的特殊目录。添加到此目录的任何文件都将成为我们原生应用内的屏幕和 Web 上的页面。
¥**app directory**: A special directory containing only routes and their layouts. Any files added to this directory become a screen inside our native app and a page on the web.
* 根布局:app/_layout.tsx 文件。它定义共享的 UI 元素(例如标题和标签栏),以便它们在不同路由之间保持一致。
¥**Root layout**: The **app/_layout.tsx** file. It defines shared UI elements such as headers and tab bars so they are consistent between different routes.
* 文件命名约定:索引文件名(例如 index.tsx)与其父目录匹配,并且不添加路径段。例如,应用目录中的 index.tsx 文件与 `/` 路由匹配。
¥**File name conventions**: *Index* file names, such as **index.tsx**, match their parent directory and do not add a path segment. For example, the **index.tsx** file in the **app** directory matches `/` route.
* 路由文件将 React 组件导出为其默认值。它可以使用 `.js`、`.jsx`、`.ts` 或 `.tsx` 扩展。
¥A **route** file exports a React component as its default value. It can use either `.js`, `.jsx`, `.ts`, or `.tsx` extension.
* Android、iOS 和 Web 共享统一的导航结构。
¥Android, iOS, and web share a unified navigation structure.
> 上述列表足以让我们开始。有关功能的完整列表,请参阅 [Expo 路由简介](/router/introduction/)。
>
> ¥The above list is enough for us to get started. For a complete list of features, see [Introduction to Expo Router](/router/introduction/).
Step 1:
## Add a new screen to the stack
Let's create a new file named **about.tsx** inside the **app** directory. It displays the screen name when the user navigates to the `/about` route.
!!!IG4!!!
Inside **\_layout.tsx**:
1. Add a `` component and an `options` prop to update the title of the `/about` route.
2. Update the `/index` route's title to `Home` by adding `options` prop.
!!!IG14!!!
!!!IG5!!!
Note: What is a ?
---
A stack navigator is the foundation for navigating between different screens in an app. On Android, a stacked route animates on top of the current screen. On iOS, a stacked route animates from the right. Expo Router provides a `Stack` component to create a navigation stack to add new routes.
---
Step 2:
## Navigate between screens
We'll use Expo Router's `Link` component to navigate from the `/index` route to the `/about` route. It is a React component that renders a `` with a given `href` prop.
1. Import the `Link` component from `expo-router` inside **index.tsx**.
2. Add a `Link` component after `` component and pass `href` prop with the `/about` route.
3. Add a style of `fontSize`, `textDecorationLine`, and `color` to `Link` component. It takes the same props as the `` component.
!!!IG15!!!
!!!IG6!!!
Let's take a look at the changes in our app. Click on `Link` to navigate to the `/about` route:
Step 3:
## Add a not-found route
When a route doesn't exist, we can use a `+not-found` route to display a fallback screen. This is useful when we want to display a custom screen when navigating to an invalid route on mobile instead of crashing the app or display a _404_ error on web. Expo Router uses a special **+not-found.tsx** file to handle this case.
1. Create a new file named **+not-found.tsx** inside the app directory to add the `NotFoundScreen` component.
2. Add `options` prop from the `Stack.Screen` to display a custom screen title for this route.
3. Add a `Link` component to navigate to the `/` route, which is our fallback route.
!!!IG16!!!
!!!IG7!!!
To test this, navigate to `http:localhost:8081/123` URL in the web browser since it is easy to change the URL path there. The app should display the `NotFoundScreen` component:
Step 4:
## Add a bottom tab navigator
At this point, the file structure of our **app** directory looks like the following:
!!!IG12!!!
We'll add a bottom tab navigator to our app and reuse the existing Home and About screens to create a tab layout (a common navigation pattern in many social media apps like X or BlueSky). We'll also use the stack navigator in the Root layout so the `+not-found` route displays over any other nested navigators.
1. Inside the **app** directory, add a **(tabs)** subdirectory. This special directory is used to group routes together and display them in a bottom tab bar.
2. Create a **(tabs)/\_layout.tsx** file inside the directory. It will be used to define the tab layout, which is separate from Root layout.
3. Move the existing **index.tsx** and **about.tsx** files inside the **(tabs)** directory. The structure of **app** directory will look like this:
!!!IG13!!!
Update the Root layout file to add a `(tabs)` route:
!!!IG17!!!
!!!IG8!!!
Inside **(tabs)/\_layout.tsx**, add a `Tabs` component to define the bottom tab layout:
!!!IG18!!!
!!!IG9!!!
Let's take a look at our app now to see the new bottom tabs:
Step 5:
## Update bottom tab navigator appearance
Right now, the bottom tab navigator looks the same on all platforms but doesn't match the style of our app. For example, the tab bar or header doesn't display a custom icon, and the bottom tab background color doesn't match the app's background color.
Modify the **(tabs)/\_layout.tsx** file to add tab bar icons:
1. Import `Ionicons` icons set from [`@expo/vector-icons`](/guides/icons/#expovector-icons) — a library that includes popular icon sets.
2. Add the `tabBarIcon` to both the `index` and `about` routes. This function takes `focused` and `color` as params and renders the icon component. From the icon set, we can provide custom icon names.
3. Add `screenOptions.tabBarActiveTintColor` to the `Tabs` component and set its value to `#ffd33d`. This will change the color of the tab bar icon and label when active.
!!!IG19!!!
!!!IG10!!!
Let's also change the background color of the tab bar and header using `screenOptions` prop:
!!!IG11!!!
In the above code:
- The header's background is set to `#25292e` using the `headerStyle` property. We have also disabled the header's shadow using `headerShadowVisible`.
- `headerTintColor` applies `#fff` to the header label
- `tabBarStyle.backgroundColor` applies `#25292e` to the tab bar
Our app now has a custom bottom tabs navigator:
## Summary
## 构建一个屏幕
在本教程中,了解如何使用 React Native 的 Pressable 和 Expo Image 等组件来构建屏幕。
在本章中,我们将创建 StickerSmash 应用的第一个屏幕。
¥In this chapter, we'll create the first screen of the StickerSmash app.
上面的屏幕显示一个图片和两个按钮。应用用户可以使用两个按钮之一选择图片。第一个按钮允许用户从他们的设备中选择图片。第二个按钮允许用户继续使用应用提供的默认图片。
¥The screen above displays an image and two buttons. The app user can select an image using one of the two buttons. The first button allows the user to select an image from their device. The second button allows the user to continue with a default image provided by the app.
一旦用户选择图片,他们就可以向其添加贴纸。那么,让我们开始创建这个屏幕。
¥Once the user selects an image, they can add a sticker to it. So, let's start creating this screen.
Video Tutorial: [Watch: Building a screen in your universal Expo app](https://www.youtube.com/watch?v=3rcOP8xDwTQ)
***
Step 1:
## Break down the screen
Before we build this screen by writing code, let's break it down into some essential elements.
There are two essential elements:
- There is a large image displayed at the center of the screen
- There are two buttons in the bottom half of the screen
The first button contains multiple components. The parent element provides a yellow border, and contains an icon and text components inside a row.
Now that we've broken down the UI into smaller chunks, we're ready to start coding.
Step 2:
## Display the image
We'll use `expo-image` library to display the image in the app. It provides a cross-platform `` component to load and render an image.
Stop the development server by pressing Ctrl + c in the terminal. Then, install the `expo-image` library:
```sh
$ npx expo install expo-image
```
The [`npx expo install`](/more/expo-cli/#installation) command will install the library and add it to the project's dependencies in **package.json**.
The Image component takes the source of an image as its value. The source can be either a [static asset](https://rn.nodejs.cn/docs/images#static-image-resources) or a URL. For example, the source required from **assets/images** directory is static. It can also come from [Network](https://rn.nodejs.cn/docs/images#network-images) as a `uri` property.
To use the Image component in **app/(tabs)/index.tsx** file:
1. Import `Image` from the `expo-image` library.
2. Create a `PlaceholderImage` variable to use **assets/images/background-image.png** file as the `source` prop on the `Image` component.
!!!IG7!!!
!!!IG0!!!
Step 3:
## Divide components into files
Let's divide the code into multiple files as we add more components to this screen. Throughout this tutorial, we'll use the components directory to create custom components.
1. Create a top-level **components** directory, and inside it, create the **ImageViewer.tsx** file.
2. Move the code to display the image in this file along with the `image` styles.
!!!IG8!!!
!!!IG1!!!
> **info** Since **ImageViewer** is a custom component, we are placing it in a separate directory instead of the **app** directory. Every file inside **app** directory is either a layout file or a route file. For more information, see [Non-navigation components live outside of app directory](/router/basics/core-concepts/#5-non-navigation-components-live-outside-of-app-directory).
Import `ImageViewer` and use it in the **app/(tabs)/index.tsx**:
!!!IG9!!!
!!!IG2!!!
Note: What is the in import statement?
---
The `@` symbol is a custom [path alias](/guides/typescript/#path-aliases-optional) for importing custom components and other modules instead of relative paths. Expo CLI automatically configures it in **tsconfig.json**.
---
Step 4:
## Create buttons using Pressable
React Native includes a few different components for handling touch events, but [``](https://rn.nodejs.cn/docs/pressable) is recommended for its flexibility. It can detect single taps, long presses, trigger separate events when the button is pushed in and released, and more.
In the design, there are two buttons we need to create. Each has a different style and label. Let's start by creating a reusable component for these buttons. Create a **Button.tsx** file inside the **components** directory with the following code:
!!!IG10!!!
!!!IG3!!!
The app displays an alert when the user taps any of the buttons on the screen. It happens because `` calls `alert()` on its `onPress` prop. Let's import this component into **app/(tabs)/index.tsx** file and add styles for the `` that encapsulates these buttons:
!!!IG11!!!
!!!IG4!!!
Let's take a look at our app on Android, iOS and the web:
The second button with the label "Use this photo" resembles the actual button from the design. However, the first button needs more styling to match the design.
Step 5:
## Enhance the reusable button component
The "Choose a photo" button requires different styling than the "Use this photo" button, so we will add a new button theme prop that will allow us to apply a `primary` theme. This button also has an icon before the label. We will use an icon from the `@expo/vector-icons` library.
To load and display the icon on the button, let's use `FontAwesome` from the library. Modify **components/Button.tsx** to add the following code snippet:
!!!IG12!!!
!!!IG5!!!
Let's learn what the above code does:
- The primary theme button uses **inline styles**, which overrides the styles defined in `StyleSheet.create()` with an object directly passed in the `style` prop.
- The `` component in the primary theme uses a `backgroundColor` property with a value `#fff` to set the button's background to white. If we add this property to the `styles.button`, the background color value will be set for both the primary theme and the unstyled one.
- Inline styles use JavaScript and override the default styles for a specific value.
Now, modify the **app/(tabs)/index.tsx** file to use the `theme="primary"` prop on the first button.
!!!IG13!!!
!!!IG6!!!
Let's take a look at our app on Android, iOS and the web:
## Summary
## 使用图片选择器
在本教程中,学习如何使用 Expo Image Picker。
React Native 提供内置组件作为标准构建块,例如 ``、`` 和 ``。我们正在构建一个从设备的媒体库中选择图片的功能。核心组件无法做到这一点,我们需要一个库来在我们的应用中添加此功能。
¥React Native provides built-in components as standard building blocks, such as ``, ``, and ``. We are building a feature to select an image from the device's media gallery. This isn't possible with the core components and we'll need a library to add this feature in our app.
我们将使用 [`expo-image-picker`](/versions/latest/sdk/imagepicker),这是 Expo SDK 中的一个库。
¥We'll use [`expo-image-picker`](/versions/latest/sdk/imagepicker), a library from Expo SDK.
> `expo-image-picker` 提供对系统 UI 的访问,以从手机库中选择图片和视频。
>
> ¥`expo-image-picker` provides access to the system's UI to select images and videos from the phone's library.
Video Tutorial: [Watch: Using an image picker in your universal Expo app](https://www.youtube.com/watch?v=iEQZU58naS8)
***
Step 1:
## Install expo-image-picker
To install the library, run the following command:
```sh
$ npx expo install expo-image-picker
```
> **info** **Tip:** Any time we install a new library in our project, stop the development server by pressing Ctrl + c in the terminal and then run the installation command. After the installation completes, we can start the development server again by running `npx expo start` from the same terminal window.
Step 2:
## Pick an image from the device's media library
`expo-image-picker` provides `launchImageLibraryAsync()` method to display the system UI by choosing an image or a video from the device's media library. We'll use the primary themed button created in the previous chapter to select an image from the device's media library and create a function to launch the device's image library to implement this functionality.
In **app/(tabs)/index.tsx**, import `expo-image-picker` library and create a `pickImageAsync()` function inside the `Index` component:
!!!IG17!!!
!!!IG1!!!
Let's learn what the above code does:
- The `launchImageLibraryAsync()` receives an object to specify different options. This object is the [`ImagePickerOptions`](/versions/latest/sdk/imagepicker/#imagepickeroptions) object, which we are passing when invoking the method.
- When `allowsEditing` is set to `true`, the user can crop the image during the selection process on Android and iOS.
Step 3:
## Update the button component
On pressing the primary button, we'll call the `pickImageAsync()` function on the `Button` component. Update the `onPress` prop of the `Button` component in **components/Button.tsx**:
!!!IG18!!!
!!!IG2!!!
In **app/(tabs)/index.tsx**, add the `pickImageAsync()` function to the `onPress` prop on the first `