官网:https://expo.dev/ # 初始化项目 创建项目命令: ```bash pnpm create expo-app@latest --template blank-typescript ``` [官网模板](https://docs.expo.dev/more/create-expo/)如下: | 模板名称 | 描述 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | [`default`](https://github.com/expo/expo/tree/main/templates/expo-template-default) | 默认模板,用于构建多页面应用。包含推荐工具,如 **Expo CLI**、**Expo Router** 路由库,并启用 **TypeScript** 配置。适合大多数应用。 | | [`blank`](https://github.com/expo/expo/tree/main/templates/expo-template-blank) | 最简模板,仅安装最基本依赖,不包含导航配置。 | | [`blank-typescript`](https://github.com/expo/expo/tree/main/templates/expo-template-blank-typescript) | 空白模板,但启用了 **TypeScript** 支持。 | | [`tabs`](https://github.com/expo/expo/tree/main/templates/expo-template-tabs) | 启用基于文件的路由系统(Expo Router),并默认使用 **TypeScript**,适合多标签页结构的应用。 | | [`bare-minimum`](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum) | 带有原生目录(`android` 与 `ios`)的空白模板,创建时会自动运行 [`npx expo prebuild`](https://docs.expo.dev/workflow/prebuild)。适合需要直接修改原生代码的项目。 | **启动项目:** ```bash npx expo start ``` # 基础配置 ## 集成 [antfu-eslint](https://github.com/antfu/eslint-config) 安装: ```bash pnpm i -D eslint @antfu/eslint-config ``` 新建 `eslint.config.js` 文件: ```js // eslint.config.js import antfu from '@antfu/eslint-config' export default antfu({ stylistic: { indent: 2, // 缩进为 2 个空格(可选 4 或 'tab') quotes: 'single', // 使用单引号(可选 'double') }, rules: { 'no-unused-vars': 'off', // 关闭未使用变量的报错 '@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告 'no-console': 'off', // 关闭 console 报错 }, }) ``` 配置 vscode 模板,修改 `.vscode/settings.json` : > 注意安装 eslint 插件 ```json { // Disable the default formatter, use eslint instead "prettier.enable": false, "editor.formatOnSave": false, // Auto fix "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.organizeImports": "never" }, // Silent the stylistic rules in you IDE, but still auto fix them "eslint.rules.customizations": [ { "rule": "style/*", "severity": "off", "fixable": true }, { "rule": "format/*", "severity": "off", "fixable": true }, { "rule": "*-indent", "severity": "off", "fixable": true }, { "rule": "*-spacing", "severity": "off", "fixable": true }, { "rule": "*-spaces", "severity": "off", "fixable": true }, { "rule": "*-order", "severity": "off", "fixable": true }, { "rule": "*-dangle", "severity": "off", "fixable": true }, { "rule": "*-newline", "severity": "off", "fixable": true }, { "rule": "*quotes", "severity": "off", "fixable": true }, { "rule": "*semi", "severity": "off", "fixable": true } ], // Enable eslint for all supported languages "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", "vue", "html", "markdown", "json", "jsonc", "yaml", "toml", "xml", "gql", "graphql", "astro", "svelte", "css", "less", "scss", "pcss", "postcss" ] } ``` ## 集成 [nativewind](https://www.nativewind.dev/v5/getting-started/installation) (存在BUG,先不使用) 1. 安装[Nativewind](https://www.nativewind.dev/v5/getting-started/installation#1-install-nativewind) ```bash pnpm install nativewind@preview react-native-css react-native-reanimated react-native-safe-area-context ``` 2. 安装**Tailwind CSS** ```bash pnpm install --save-dev tailwindcss @tailwindcss/postcss postcss ``` 3. 新建 `postcss.config.mjs` 文件: ```js export default { plugins: { "@tailwindcss/postcss": {}, }, }; ``` 4. 创建 `global.css` 并在 `index.ts` 文件中引入 ```css @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css"; @import "nativewind/theme"; ``` 5. 创建 `metro.config.js` 文件: ```js const { getDefaultConfig } = require("expo/metro-config"); const { withNativewind } = require("nativewind/metro"); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); module.exports = withNativewind(config); ``` 6. 固定 lightningcss 版本,在 `package.json` 里添加: ```json { "resolutions": { "lightningcss": "1.30.1" } } ``` 7. 添加 `ts` 支持,新建 `nativewind-env.d.ts` 文件(任意名称): ```ts /// // NOTE: This file should not be edited and should be committed with your source code. It is generated by react-native-css. If you need to move or disable this file, please see the documentation. ``` 8. 检验是否生效 ```tsx import { Text, View } from "react-native"; export default function App() { return ( Welcome to Nativewind! ); } ``` ## 集成[@expo/vector-icons](https://docs.expo.dev/guides/icons/#expovector-icons) 该插件默认跟着expo安装了。可以直接使用。 图标网站地址:https://icons.expo.fyi/Index 如果需要没有想要的图标,可以用下面的方式: 安装svg依赖 ```bash npx expo install react-native-svg ``` 然后在components下面创建一个Icons.tsx文件,用来管理所有的图标 ```tsx import Svg, { Circle, Rect, Path } from 'react-native-svg'; interface IconProps { size?: string | number; color?: string; } // 测试图标 export const DemoIcon: React.FC = ({ size = 30, color = '#fff', ...rest }) => { return ( ); } // 招聘 export const ZhaoPinIcon: React.FC = ({ size = 30, color = '#fff', ...rest }) => { return ( ); } ``` ## 集成[expo-router](expo-router) 1. 安装相关依赖 ```bash npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar ``` 2. 修改 `package.json` ``` { "main": "expo-router/entry" } ``` 3. 将根目录的 `App.tsx` 和 `index.ts` 文件删除,新增 `_layout.tsx` 文件 ### 布局 1. 新建 `app/_layout.tsx` 文件: ```tsx import { Slot } from 'expo-router' import { StyleSheet, Text } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' export default function Layout() { return ( Header Footer ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', paddingHorizontal: 16, }, header: { fontSize: 20, fontWeight: 'bold', paddingVertical: 12, textAlign: 'center', backgroundColor: '#4f46e5', color: '#fff', borderRadius: 8, marginVertical: 8, }, footer: { fontSize: 16, paddingVertical: 10, textAlign: 'center', backgroundColor: '#e5e7eb', borderRadius: 8, marginVertical: 8, }, }) ``` 2. 新建 ## 配置深度链接 作用:通过链接就可以跳转到该app。 在 `app.json` 文件下进行配置: ```json { "expo": { "scheme":"oneao", } } ``` 然后就可以通过这个配置 从别的应用或者浏览器跳转到你的app: ``` oneao://courses/react-native ``` ## [定义 src 路径别名 @](https://docs.expo.dev/guides/typescript/) 修改 `tsconfig.json` 文件: ```json { "extends": "expo/tsconfig.base", "compilerOptions": { "baseUrl": "./", "paths": { "@/*": ["src/*"] }, "strict": true } } ``` # 打包 ## 云打包 1. 安装 EAS CLI ```bash npm install -g eas-cli ``` 2. 注册并登录 Expo 账户 - 访问 https://expo.dev/ 注册账户,然后命令行登录: ```bash expo login ``` 3. 配置项目构建 - 输入以下命令,选择 Android 平台即可。 ```bash eas build:configure ``` 4. 构建 Android 包 - 默认构建 AAB 包(上传 Google Play): ```bash eas build --platform android ``` - 构建 APK(可直接安装): ```bash eas build -p android --profile preview ``` ## 本地打包(EAS方式) ## 本地打包(原生方式) ### 1. 安装 [Android Studio]( https://developer.android.com/?hl=zh-cn) 1. 下载安装 - 按照官网提示依次安装 SDK 工具和必要组件。 - 安装完成后,记住 SDK 的安装路径(例如:`D:\data\sdk`,默认位置是:`C:\Users\<你的用户名>\AppData\Local\Android\Sdk`),后续需要用到。 2. 配置环境变量 - 打开 **系统属性 → 高级 → 环境变量**。 - 在 **系统变量** 中新增: ``` 变量名:ANDROID_HOME 变量值:D:\data\sdk ``` - 在 **Path** 中新增: ```bash %ANDROID_HOME%\platform-tools %ANDROID_HOME%\tools %ANDROID_HOME%\tools\bin ``` - 保存设置。 3. 验证安装 - 打开命令行,执行: ``` echo %ANDROID_HOME% ``` - 如果输出 SDK 路径,则说明配置成功。 ### 2. 安装 [jdk17](https://adoptium.net/zh-CN/temurin/releases?version=8&os=any&arch=any) 1. 下载安装 - 下载对应版本解压即可。 - 安装成功后,记住 jdk 的安装路径,后续需要用到。 2. 配置环境变量 - 打开 **系统属性 → 高级 → 环境变量**。 - 在 **系统变量** 中新增: ```bash 变量名:JAVA_HOME 变量值:D:\devtool\jdk\jdk-17.0.16+8 ``` - 在 **Path** 中新增: ```bash %JAVA_HOME%\bin ``` - 保存设置。 3. 验证安装 - 打开命令行,执行: ```bash java --version ``` - 如果输出 jdk 的信息,则说明配置成功。 ### 3. Expo 项目启用 [Prebuild](https://docs.expo.dev/workflow/prebuild/) 1. 生成原生项目 ```bash npx expo prebuild ``` 然后执行完毕后,会生成 `android` 目录。 2. 清除并重新生成原生项目 ```bash npx expo prebuild --clean ``` 会删除已有的 `android` 和 `ios` 目录,然后重新生成。 ### 4. 配置 Keystore #### 4.1 生成密钥 ```bash keytool -genkey -v -keystore android-release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 ``` 说明: - `my-release-key.keystore`:生成的密钥文件名,可自定义路径 - `my-key-alias`:密钥别名 - `RSA`:加密算法 - `2048`:密钥长度 - `10000`:有效期(天) #### 4.2 手动配置 修改 `android/app/build.gradle` 文件 1. 修改 `signingConfigs` ```bash signingConfigs { release { storeFile file('android-release.keystore') // keystore 文件路径 storePassword '你的Keystore密码' // keystore 密码 keyAlias '你的KeyAlias' // 密钥别名 keyPassword '你的Key密码' // key 密码 } debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } ``` 2. 在 `buildTypes` 中设置 release 使用 release 签名 ```bash buildTypes { release { signingConfig signingConfigs.release } debug { signingConfig signingConfigs.debug } } ``` #### 4.3 脚本配置(推荐) 1. 在 expo 项目根目录下新增 `plugins\withAndroidSignature.js` 文件,内容如下: ```js // plugins/withAndroidSignature.js const { withAppBuildGradle } = require("@expo/config-plugins"); const fs = require("fs"); const path = require("path"); module.exports = function withAndroidSignature(config) { return withAppBuildGradle(config, config => { if (config.modResults.language === "groovy") { config.modResults.contents = setAndroidSignature(config.modResults.contents); } else { throw new Error("如果不是 groovy,则无法在 app/build.gradle 中设置 signingConfigs"); } return config; }); }; function setAndroidSignature(appBuildGradle) { if (!fs.existsSync(path.resolve(__dirname, "../credentials.json"))) { console.warn("警告:没有设置正式版本的 Android Keystore 文件,因为 credentials.json 不存在。"); return appBuildGradle; } const info = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../credentials/credentials.json"), { encoding: "utf8" })); // 使用正则表达式插入签名信息 let output = appBuildGradle.replace( /(signingConfigs\s*\{)/, `$1 release { storeFile file(${JSON.stringify(path.resolve(__dirname, "../credentials/android-release.keystore"))}) storePassword ${JSON.stringify(info.android.keystore.keystorePassword)} keyAlias ${JSON.stringify(info.android.keystore.keyAlias)} keyPassword ${JSON.stringify(info.android.keystore.keyPassword)} }`, ); // 使用正则表达式替换 signingConfig output = output.replace( /(release\s*\{)[^}]*?signingConfig\s+signingConfigs\.debug/s, `$1 signingConfig signingConfigs.release `, ); return output; } ``` 2. 在 expo 项目根目录下新增 `/credentials/credentials.json` 文件(该文件主要是 Keystore 的信息): ```json { "android": { "keystore": { "keystorePath": "credentials/android-release.keystore", // Android keystore 文件路径 "keystorePassword": "123456", // keystore 密码 "keyAlias": "my-key-alias", // Key 别名 "keyPassword": "123456" // Key 密码 } }, "ios": { "provisioningProfilePath": "ios/certs/profile.mobileprovision", // iOS 描述文件路径 "distributionCertificate": { "path": "ios/certs/dist-cert.p12", // iOS 分发证书路径 "password": "password" // 证书密码 } } } ``` 3. 在 `app.json` 中指定这个项目级插件: ```json { "expo": { "plugins": [ "./plugins/withAndroidSignature" ] } } ``` ### 5. 打包 - 编译成 Google Play 商店用的 .aab 文件 ```bash ./gradlew bundleRelease ``` - 直接安装的 .apk 文件 ```bash ./gradlew assembleRelease ``` **注意:**这个时候可能会下载 `gradle` 、`apk` 等资源,网络速度可能过慢。 **解决方法:**在 `android/gradle.properties` 添加网络代理: ```bash systemProp.http.proxyHost=127.0.0.1 systemProp.http.proxyPort=7897 systemProp.https.proxyHost=127.0.0.1 systemProp.https.proxyPort=7897 ``` 打包结果在 `android/app/build/outputs/apt/release` 包下。 # 打包体积优化 默认初始项目打包就有 **80MB** 左右,是因为 expo 默认将 armeabi-v7a、arm64-v8a、x86、x86_64 这些架构的包都打包进来了。 | 架构名称 | 位数 | 设备类型 | 应用场景 | 备注 | | --------------- | ----- | ------------------------------------------ | -------------- | ------------------------------------ | | **armeabi-v7a** | 32 位 | 老旧的 ARM 芯片手机(如部分安卓 5/6 设备) | 兼容旧设备 | 已逐渐被淘汰,但仍有少数旧机型使用 | | **arm64-v8a** | 64 位 | 主流安卓手机(大多数真机) | ✅ 主力目标平台 | 目前最主流、性能最佳的架构 | | **x86** | 32 位 | 早期安卓模拟器(Intel 架构) | 开发调试 | 真机几乎不会用 | | **x86_64** | 64 位 | 现代安卓模拟器 | 开发调试 | 用于 Android Studio 模拟器或测试环境 | ## 方法1(简单) 找到 `android/gradle.properties` 文件,找到 `reactNativeArchitectures` ,根据需要删除即可 ```properties reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 ``` ## 方法2(原生方法 选择更多) 在 `android/app/build.gradle` 文件中添加或修改以下配置: ``` android { // 其他配置... splits { abi { enable true // 启用按 CPU 架构拆分 reset() // 重置默认架构列表 include 'arm64-v8a', 'armeabi-v7a' universalApk false // 是否生成包含所有架构的通用 APK(false 表示只生成分架构的独立包) // 还有一个 exclude 配置是排除指定架构的 } } // 其他配置... } ``` **说明:** - `enable true`:启用架构拆分功能。 - `reset()`:清空默认的架构配置,避免重复。 - `include`:指定要生成的架构包,比如只打 `arm64-v8a` 和 `armeabi-v7a`。 - `universalApk false`:不生成通用包(即包含所有架构的 APK)。 **举例:** 执行打包命令后,将生成多个包,例如: ``` app-arm64-v8a-release.apk app-armeabi-v7a-release.apk ``` 每个 APK 只包含对应架构的二进制文件,体积更小。 如果把 `universalApk` 设置为 `true`,则只会生成一个: ``` app-universal-release.apk ``` 这个包能在 `arm64-v8a` 和 `armeabi-v7a` 架构的设备上运行,但体积最大。 HY20241003