Files
workspace/notes/前端开发/react-native/expo.md
T
2025-10-24 17:27:35 +08:00

20 KiB
Raw Blame History

官网:https://expo.dev/

初始化项目

创建项目命令:

pnpm create expo-app@latest --template blank-typescript

官网模板如下:

模板名称 描述
default 默认模板,用于构建多页面应用。包含推荐工具,如 Expo CLI、Expo Router 路由库,并启用 TypeScript 配置。适合大多数应用。
blank 最简模板,仅安装最基本依赖,不包含导航配置。
blank-typescript 空白模板,但启用了 TypeScript 支持。
tabs 启用基于文件的路由系统(Expo Router),并默认使用 TypeScript,适合多标签页结构的应用。
bare-minimum 带有原生目录(android 与 ios)的空白模板,创建时会自动运行 npx expo prebuild。适合需要直接修改原生代码的项目。

启动项目:

npx expo start

基础配置

集成 antfu-eslint

可以使用快捷命令:

pnpm dlx @antfu/eslint-config@latest

安装:

pnpm i -D eslint @antfu/eslint-config

新建 eslint.config.mjs 文件:

import antfu from '@antfu/eslint-config'

export default antfu({
  stylistic: {
    indent: 2, // 缩进为 2 个空格(可选 4 或 'tab')
    quotes: 'single', // 使用单引号(可选 'double')
  },
  rules: {
    'unused-imports/no-unused-imports': 'warn',
    'unused-imports/no-unused-vars': 'warn',
    '@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告
    'no-console': 'off', // 关闭 console 报错
  },
})

配置 vscode 模板,修改 .vscode/settings.json :

注意安装 eslint 插件

{
  // 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 (存在BUG,先不使用)

  1. 安装Nativewind

    pnpm install nativewind@preview react-native-css react-native-reanimated react-native-safe-area-context
    
  2. 安装Tailwind CSS

    pnpm install --save-dev tailwindcss @tailwindcss/postcss postcss
    
  3. 新建 postcss.config.mjs 文件:

    export default {
      plugins: {
        "@tailwindcss/postcss": {},
      },
    };
    
  4. 创建 global.css 并在 index.ts 文件中引入

    @import "tailwindcss/theme.css" layer(theme);
    @import "tailwindcss/preflight.css" layer(base);
    @import "tailwindcss/utilities.css";
    
    @import "nativewind/theme";
    
  5. 创建 metro.config.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 里添加:

    {
      "resolutions": {
        "lightningcss": "1.30.1"
      }
    }
    
  7. 添加 ts 支持,新建 nativewind-env.d.ts 文件(任意名称):

    /// <reference types="react-native-css/types" />
    
    // 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. 检验是否生效

    import { Text, View } from "react-native";
    
    export default function App() {
      return (
        <View className="flex-1 items-center justify-center bg-white">
          <Text className="text-xl font-bold text-blue-500">
            Welcome to Nativewind!
          </Text>
        </View>
      );
    }
    

集成@expo/vector-icons

该插件默认跟着expo安装了。可以直接使用。

图标网站地址:https://icons.expo.fyi/Index

如果需要没有想要的图标,可以用下面的方式:

安装svg依赖

npx expo install react-native-svg

然后在components下面创建一个Icons.tsx文件,用来管理所有的图标

import Svg, { Circle, Rect, Path } from 'react-native-svg';

interface IconProps {
  size?: string | number;
  color?: string;
}

// 测试图标
export const DemoIcon: React.FC<IconProps> = ({ size = 30, color = '#fff', ...rest }) => {
  return (
    <Svg height="50%" width="50%" viewBox="0 0 100 100" {...rest}>
      <Circle cx="50" cy="50" r="45" stroke="blue" strokeWidth="2.5" fill="green" />
      <Rect x="15" y="15" width={size} height={size} stroke="red" strokeWidth="2" fill="yellow" />
    </Svg>
  );
}

// 招聘
export const ZhaoPinIcon: React.FC<IconProps> = ({ size = 30, color = '#fff', ...rest }) => {
  return (
    <Svg viewBox="0 0 1024 1024" p-id="5316" width={size} height={size}  {...rest}>
      <Path d="M473.6 732.8c-9.6 0-25.6 3.2-48 6.4v112h-83.2v-102.4c-73.6 9.6-150.4 19.2-172.8 22.4L160 681.6c9.6-3.2 22.4-3.2 41.6-6.4V278.4h-19.2V188.8h272v89.6h-28.8v368c19.2-3.2 28.8-3.2 38.4-6.4l3.2 48 6.4 44.8zM288 278.4v70.4h51.2V278.4H288z m0 224h51.2v-67.2H288v67.2z m51.2 153.6v-67.2H288v73.6l51.2-6.4z m259.2-54.4l-3.2 25.6h220.8c0 9.6-9.6 112-12.8 131.2-6.4 76.8-32 89.6-121.6 99.2l-54.4 6.4-25.6-86.4 64-3.2c41.6-3.2 54.4-6.4 57.6-35.2 3.2-9.6 3.2-19.2 3.2-32h-233.6l16-102.4H448v-73.6h416v73.6l-265.6-3.2z m233.6-99.2H467.2V211.2H608V160l80 3.2v48H832v291.2zM547.2 288v32H608V288h-60.8z m0 140.8H608v-32h-60.8v32zM688 288v32h60.8V288h-60.8z m60.8 140.8v-32h-60.8v32h60.8z" p-id="5317" fill={color} />
    </Svg>
  );
}

集成expo-router

  1. 安装相关依赖

    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 文件:

    import { Slot } from 'expo-router'
    import { StyleSheet, Text } from 'react-native'
    import { SafeAreaView } from 'react-native-safe-area-context'
    
    export default function Layout() {
      return (
        <SafeAreaView style={styles.container}>
          <Text style={styles.header}>Header</Text>
    
          <Slot />
    
          <Text style={styles.footer}>Footer</Text>
        </SafeAreaView>
      )
    }
    
    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 文件下进行配置:

{
  "expo": {
    "scheme":"oneao",
  }
}

然后就可以通过这个配置 从别的应用或者浏览器跳转到你的app:

oneao://courses/react-native

定义 src 路径别名 @

修改 tsconfig.json 文件:

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    },
    "strict": true
  }
}

打包

云打包

  1. 安装 EAS CLI

    npm install -g eas-cli
    
  2. 注册并登录 Expo 账户

  3. 配置项目构建

    • 输入以下命令,选择 Android 平台即可。

      eas build:configure
      
  4. 构建 Android 包

    • 默认构建 AAB 包(上传 Google Play):

      eas build --platform android
      
    • 构建 APK(可直接安装):

      eas build -p android --profile preview
      

本地打包(EAS方式)

本地打包(原生方式)

1. 安装 Android Studio

  1. 下载安装

    • 按照官网提示依次安装 SDK 工具和必要组件。

    • 安装完成后,记住 SDK 的安装路径(例如:D:\data\sdk,默认位置是:C:\Users\<你的用户名>\AppData\Local\Android\Sdk),后续需要用到。

  2. 配置环境变量

    • 打开 系统属性 → 高级 → 环境变量。

    • 在 系统变量 中新增:

      变量名:ANDROID_HOME
      变量值:D:\data\sdk  
      
    • 在 Path 中新增:

      %ANDROID_HOME%\platform-tools
      %ANDROID_HOME%\tools
      %ANDROID_HOME%\tools\bin
      
    • 保存设置。

  3. 验证安装

    • 打开命令行,执行:

      echo %ANDROID_HOME%
      
    • 如果输出 SDK 路径,则说明配置成功。

2. 安装 jdk17

  1. 下载安装

    • 下载对应版本解压即可。
    • 安装成功后,记住 jdk 的安装路径,后续需要用到。
  2. 配置环境变量

    • 打开 系统属性 → 高级 → 环境变量。

    • 在 系统变量 中新增:

      变量名:JAVA_HOME
      变量值:D:\devtool\jdk\jdk-17.0.16+8
      
    • 在 Path 中新增:

      %JAVA_HOME%\bin
      
    • 保存设置。

  3. 验证安装

    • 打开命令行,执行:

      java --version
      
    • 如果输出 jdk 的信息,则说明配置成功。

3. Expo 项目启用 Prebuild

  1. 生成原生项目

    npx expo prebuild
    

    然后执行完毕后,会生成 android 目录。

  2. 清除并重新生成原生项目

    npx expo prebuild --clean
    

    会删除已有的 android 和 ios 目录,然后重新生成。

4. 配置 Keystore

4.1 生成密钥

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

    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 签名

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    
        debug {
            signingConfig signingConfigs.debug
        }
    }
    

4.3 脚本配置(推荐)

  1. 在 expo 项目根目录下新增 plugins\withAndroidSignature.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 的信息):

    {
      "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 中指定这个项目级插件:

    {  
      "expo": {  
        "plugins": [  
          "./plugins/withAndroidSignature"
        ]
      }  
    }
    

5. 打包

  • 编译成 Google Play 商店用的 .aab 文件

    ./gradlew bundleRelease 
    
  • 直接安装的 .apk 文件

    ./gradlew assembleRelease
    

**注意:**这个时候可能会下载 gradle 、apk 等资源,网络速度可能过慢。

**解决方法:**在 android/gradle.properties 添加网络代理:

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 ,根据需要删除即可

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