Expo 最佳实践

一、创建项目

expo init MyProject 创建项目,选择托管工作流的TypeScript的空白模板,然后用 expo eject 退出到裸漏工作流。

之所以选择这种方式创建项目,是因为灵活性很大,可以在托管和裸漏之间做开发(android&ios/android)。

因为很多库是依赖裸漏工作流开发的且打包android apk的时候可以减少size,但是同时也可以使用Expo开发的库而节省了手动配置的时间。

特别注意:在退出之前,必须先expo login登录,否则在构建的时候会报错;在app.json中将基础的配置配置好,比如屏幕方向设置为 ‘default’,背景色设置为 ‘#00000088’ 等。免去之后手动配置的步骤。

android package name 的命名方式:https://developer.android.com/studio/build/application-id

二、从托管managed工作流中退出到裸漏bare工作流

Step1. 退出

expo eject

Step2. 在项目根目录执行下载密钥

expo fetch:android:keystore

Step3.将命令行窗口中的密钥复制保存到 temporary_dev_files/build_apk_key 临时文件夹中,便于之后打包apk使用

三、查看项目环境信息

Expo/RN项目根目录下可执行以下命令

react-native info

Expo项目根目录下也可执行

expo diagnostics

四、项目根目录最好新建一个 temporary_dev_files 临时开发文件夹,便于管理许多开发阶段临时存放的文件

五、关于修改node_modules软件包的事宜

有时候安装的软件包不符合自己的要求或者存在bug,需要自己修改软件包源代码,进入到node_modules中找到相关的软件包源码进行修改。

在不影响开发的情况下,最省事的做法是,将修改好的代码复制一份保存到 temporary_dev_files/modules (注意不要命名为node_modules,因为github不会提交) 临时文件夹,项目会自动读取modules的修改文件。

六、.d.ts 文件

很多时候,很多库都没有直接安装的TypeScript版本,但是往往带有 index.d.ts 文件。

可以将它放置到 no_modules/对应的软件包根目录中,就可以生效了,当然,也可在项目根目录新建一个d.ts文件夹,里面可以放置各种库的d.ts文件。

也可以更省事在项目根目录新建type.d.ts

或者

或者(推荐)

项目根目录新建 type.d.ts

declare module 'react-native-static-server'
declare module 'react-native-fs'

七、关于expo启动图片不显示和裸漏工作流打包失败的问题

expo sdk40 启动图片不显示,连官方都给不到答案。github issues

同时,裸漏工作流打包失败。裸漏工作流和托管工作流打包出来的apk都不显示图像。

问题分析,本地java sdk和托管服务器java sdk没有升级。

解决办法:

升级java8到最新版本就可以解决了。

从8u271 升级到 8u281 。

裸漏工作流中打包没有问题了,同时apk启动图像也可以显示了。

至于托管工作流,由于服务器在Expo,要等到他们升级了估计才能正常显示图片。

八、打包apk的尺寸和性能提升

用裸漏工作流打包要比托管工作流尺寸要小不少,这对于对体积有要求的项目是个选择。

压缩及优化apk包 –启用Hermes爱马仕 + 构建不同架构的apk

图片会对性能有很大的影响(cpu和内存等峰值),建议使用webp格式的图像

// Step1. android/app/build.gradle
  project.ext.react = [
      entryFile: "index.js",
-     enableHermes: false  // clean and rebuild if changing
+     enableHermes: true  // clean and rebuild if changing
  ]


- def enableHermes = project.ext.react.get("enableHermes", false);
+ def enableHermes = project.ext.react.get("enableHermes", true);

- def enableSeparateBuildPerCPUArchitecture = false
+ def enableSeparateBuildPerCPUArchitecture = true

// Step2. 清理构建
cd android && ./gradlew clean

// Step3. 重新构建
gradlew assembleRelease

九、裸漏工作流打包apk后,github上传文件过大

由于打包后的android代码增加了一个.hprof和一个 BasePackageList.java 大文件,所以在上传到github的时候文件超大了,由于expo用expo eject退出到裸漏工作流的时候没有自动生成对.hprof和BasePackageList.java的限制新.gitignore文件,所以要手动自己添加进去。最省事就是去到Expo的github根目录拿现成的来用 https://github.com/expo/expo/blob/master/.gitignore

十、gradlew clean

yarn android 过程中,报Task :app:processDebugResources FAILED 等等错误。

Step1. android 文件夹中 执行清理构建

gradlew clean

Step2. 然后再返回项目根目录 执行

yarn android

十一、Could not download groovy-all-2.4.15.jar

由于中国共产党邪教的原因

* What went wrong:
Could not determine the dependencies of task ':app:lintVitalRelease'.
> Could not resolve all artifacts for configuration ':app:debugCompileClasspath'.
   > Could not download groovy-all-2.4.15.jar (org.codehaus.groovy:groovy-all:2.4.15)
      > Could not get resource 'https://jcenter.bintray.com/org/codehaus/groovy/groovy-all/2.4.15/groovy-all-2.4.15.jar'.
         > Could not GET 'https://jcenter.bintray.com/org/codehaus/groovy/groovy-all/2.4.15/groovy-all-2.4.15.jar'.
            > Remote host terminated the handshake

解决办法

Step1. android/build.gradle

repositories {
            - google()
            - jcenter()
  //用阿里镜像替换
            + maven { url 'https://maven.aliyun.com/repository/google'}
            + maven { url 'https://maven.aliyun.com/repository/jcenter'}
            + maven { url 'http://maven.aliyun.com/nexus/content/groups/public'}
    }

Step2. 然后再打包apk

gradlew assembleRelease

/android/app/src/main/AndroidManifest.xml 中配置 scheme 访问前缀

<intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="myapp"/>
        <data android:scheme="com.notbrent.mofengfs"/>
        <data android:scheme="a08"/>
</intent-filter>

配置深层链接

import * as Linking from 'expo-linking';

export default {
  prefixes: ['a08://'],
  config: {
    screens: {
        Root: {
          screens: {
            TabOne: {
              screens: {
                TabOneScreen: 'one',
              },
            },
            TabTwo: {
              screens: {
                TabTwoScreen: 'two',
              },
            },
          },
        },
      //   NotFound: '*',
      List: {
        path: 'list/:id',
        parse: {
          id: (id: any) => `${id}`
        }
      },
      Detail: {
        path: 'detail/:id',
        parse: {
          id: (id: any) => `${id}`
        }
      }
    }
  },
};

NavigationContainer 中调用深层链接

<NavigationContainer
      linking={LinkingConfiguration}
      theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <RootNavigator />
</NavigationContainer>

android backbutton 不能返回上一页的解决办法
每个非首页屏幕都要添加goBack代码

import { BackHandler } from 'react-native';

export const listScreen = ({ navigation: { goBack } }) => {

  // 返回上一屏  start
  function handleBackButtonClick() {
    goBack();
    return true;
  }

  useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', handleBackButtonClick);
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', handleBackButtonClick);
    };
  }, []);
  // 返回上一屏  end

 ...
 ...

唤醒深层链接,执行例如:

npx uri-scheme open a08://detail/123 --android

完整例子:https://github.com/mofengfs/DeepLink-BackHandler-Example

十三、启动屏幕白闪解决

使用 react navigation v5和 eva 主题, 在 NavigationContainer 中配置深色主题即可

Step1.

import { NavigationContainer, DarkTheme, DefaultTheme } from '@react-navigation/native'; 
import * as eva from '@eva-design/eva';

Step2.

const navigationTheme = eva.dark ? DarkTheme : DefaultTheme;

Step3.

<NavigationContainer theme={navigationTheme} linking={deepLinking}>
        <MainStack />
</NavigationContainer>

十四、worker cmd 假死状态

有时候打印没有即时显示,可以尝试敲回车键

十五、error: Android project not found. Are you sure this is a React Native project?

很可能是丢失了./android/app/src/main/AndroidManifest.xml文件

十六、Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0

./android/build.gradle

- classpath("com.android.tools.build:gradle:3.5.3")
+ classpath("com.android.tools.build:gradle:3.5.4")

十七、文件更新,标记

任意能持久保存数据的地方/方式都可以做版本标记,总之就是让程序自己知道哪些逻辑执行过了,哪些需要执行,执行完了做标记,不管啥迁移都是这么个流程

通过拉去服务器返回的版本号与本地数据库保存的版本号做对比,判断是否应该执行文件的更新操作。

注意:压缩文件必须是能够直接解压不带根文件夹名称,压缩的时候需要注意,操作如下: 全选添加到 www.zip 操作

十八、UI方向

默认UI横向。 只要进入了项目,就可以随意更改方向了。

有关如何控制应用程序方向的信息,请参见app.json中的方向键

有效值:自响应default,纵向portrait,横向landscape

App.json中配置

{
  "expo": {
    "name": "my-app",
    "slug": "my-app",
    "version": "1.0.0",
   -  "orientation": "portrait",//纵向
   + "orientation": "default",//自响应
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

十九、Expo&UI-Kitten(web端报错的问题)

解决办法:

//1.安装
expo install @expo/webpack-config

//2.根目录新建`webpack.config.js`添加

const createExpoWebpackConfigAsync = require('@expo/webpack-config');

module.exports = async function(env, argv) {
    const config = await createExpoWebpackConfigAsync({
        ...env,
        babel: {
            dangerouslyAddModulePathsToTranspile: ['@ui-kitten/components']
        }
    }, argv);
    return config;
};

二十、Expo Web 项目抽屉

  1. 轮播图适合用的不多,react-native-banner-carousel 是其中一个

  2. react-native-webview 不支持web端渲染html,替代方案是使用 react-native-web-webview

  3. 在线客服

function Link(props: any) {
    return (
        <Text
            {...props}
            accessibilityRole="link"
            style={StyleSheet.compose(styles.link, props.style)}
        />
    );
}

<Link href="http://wpa.qq.com/msgrd?v=3&uin=QQ号&site=qq&menu=yes" style={styles.item}>
    <TouchableOpacity style={styles.link}>
        <FontAwesome5 name="comments" size={24} color="#1296db" />
        <Text style={styles.item_text}>在线客服</Text>
    </TouchableOpacity>
</Link>
  1. 模态框使用 react-native-modalize

  2. 由于expo build:web 构建的是单页面应用,所以,在刷新页面的时候会找不到路由对应的文件,所以,最便捷的方式是通过npx serve –single启动服务,通过云服务配置对应的端口号,比如配置5000, 那么npx serve –single启动的服务也应该是5000, IIS配置的端口号随便了,只要不与5000或者其他端口冲突就行了。

除了5000端口号,还可通过以下命令自定义端口号(基于Vercel服务库)

npx serve --single -l 1337
//or
npx serve --single --listen 1337