硅基俊宁.
Skills · 技能体系 ↗
← 返回文章

React Native 架构面面观

俊宁 · 2020-02-23
迁自历史博客,保留原文、署名、引用与原始日期;文中的版本、成果与性能结论不代表重新完成了当前验收。
查看原始出处 ↗ · 查看迁移清单
React Native 架构面面观封面

@sigmayun/react-native-template-typescript

@sigmayun/react-native-template-typescript是基于 react-native-community/react-native-template-typescript 二次开发的模板脚手架。本文的架构方案融进了该模板。你甚至不需要阅读本文的内容即可使用本文中的最佳实践!!!

全局变量

既然都引入 TypeScript 了,就不用再担心给全局对象添加属性不小心污染它了,但是需要我们进行一些配置。

1、在项目根目录新建 types,并新建 global.d.ts 文件名,添加一下内容:

declare const global: {
  HermesInternal: null | {}
  ...
}

2、为了防止和 @types/node 的 Global 声明冲突,需要把 tsconfig.json 中的 skipLibCheck 的值设置为 true

该部分示例代码涉及 global.ts、types/global.d.ts、tsconfig.json,请到 https://github.com/sigmayun/react-native-template-typescript 查看。

编码规范

该部分示例代码:https://bre.is/3GD7cHKn

Npm Scripts

善用 NPM Scripts 能帮助改善工作流程!!!

{
  "scripts": {
    "postinstall": "npx jetifier",
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "pod": "cd ios && pod install",
    "gradle:clean": "cd android && ./gradlew clean",
    "an:release": "yarn gradle:clean && cd android && ./gradlew app:assembleRelease",
    "an:installRelease": "yarn gradle:clean && cd android && ./gradlew app:installRelease",
    "an:releaseStaging": "yarn gradle:clean && cd android && ./gradlew app:assembleReleaseStaging",
    "an:installReleaseStaging": "yarn gradle:clean && cd android && ./gradlew app:installReleaseStaging",
    "an:genkeypair": "keytool -genkeypair -v -keystore release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000",
    "an:key-debug": "keytool -list -v -keystore ./android/app/debug.keystore",
    "an:key-release": "keytool -v -list -keystore ./android/app/release.keystore",
    "icon": "npx iconfont-rn"
  }
}

该部分示例代码:https://bre.is/LC6uu8kc

网络编程

该部分示例代码:https://bre.is/39khhJtn

路由管理

该部分示例代码:https://bre.is/pzA7JArY

相关博客:https://bre.is/pzA7JArY

状态管理

本地存储

启动屏

启动屏的配置涉及到不少原生知识,请阅读 https://bre.is/CRC2SkqP 进行配置。

热更新

新版热更新部分我还没写好博客,可以先参考我之前写的: http://techblog.sishuxuefu.com/atricle.html?5beaa7e59f5454007039e01c,我会在看完新文档之后把这部分配置也内置到脚手架中,毕竟这个服务也是 React Native 的标配。

UI框架

其他组件收藏请查看:https://bre.is/jWUGvPrK

列表

图标

字体

工具

React Hooks

这个新语法是2019年的明星,React Navigation、Mobx这些常用库都内置支持了,还没有学习的同学快去补课吧!!!

推荐阅读

建议先读官方文档!!!

如何兼容类组件

考虑到对于不适应 Hooks 的但是业务又很紧急的场景,我们可以在类组件之上封装一层来支持 React Navigation 的 Hooks 组件,之所以这么做,起因是因为 React Navigation 5 中我们只能通过 useHeaderHeight() 方法获取标题栏高度。

class Albums extends React.Component {
  render() {
    return <ScrollView ref={this.props.scrollRef}><Text>{this.props.headerHeight}</Text></ScrollView>;
  }
}
// 封装并导出
export default function(props) {
  const ref = React.useRef(null);
  const headerHeight = useHeaderHeight();
  useScrollToTop(ref);
  return <Albums {...props} scrollRef={ref} headerHeight={headerHeight}/>;
}

Metro

第三放服务

这里尽量为大家推荐了免费的服务,当然选择自行搭建服务也是一个很棒的选择!!

Sentry

Sentry 胜在可以把服务搭建到自己的服务器上,并且官方提供了sentry-react-native插件来帮助 React Native 工程师快速集成。如果有条件,选择 Sentry 恐怕是目前行业的最佳选择。

Sentry提供自托管和基于云的错误监控,可帮助所有软件团队实时发现分类和确定错误的优先级。已经有超过五万家公司的100万名开发人员使用,Sentry可以更快地提供更好的软件。你不加入他们吗?

腾讯 Bugly

腾讯Bugly,为移动开发者提供专业的异常上报和运营统计,帮助开发者快速发现并解决异常,同时掌握产品运营动态,及时跟进用户反馈。免费提供异常上报、运营统计、应用升级

推送

信鸽 | 腾讯移动推送

为开发者提供免费、快速、简单的推送服务。QQ登录即可快速注册,为APP接入SDK后马上获得无限量应用推送能力, 有效提升用户留存率、活跃度,开发者的不二选择!

小米消息推送服务

极光推送

极光推送服务,可以免费使用,但限制较多。官方SDK, React Native 集成容易

U-Push

和极光属于一类,限制级免费,友盟的产品胜在生态,公司产品考虑,后期用户上来之后方便扩展。

社会化分享

ShareSDK

为开发者提供40+主流平台的分享与授权等社会化功能,效果稳定,完整清晰统计分享数据

jshare

U-Share

其他

测试

这部分作者的理解仅限于 Jest,有大佬可以联系我补充!!!

自动化运维

这部分作者接触不深,只知道 Jekens + GitLab 的方案

shell 文件的坑

为了安全性 shell 文件默认都是不可执行的,当然也包括 android/gradlew 这个用来打包的脚本文件,这会给持续集成带来麻烦:运维同学默认是执行不了我们的打包命令的。解决办法很简单:

$ git update-index --add --chmod=+x android/gradlew

动态设置 package.json 的版本

set-version.sh:

#!/bin/bash

# current_git_branch_latest_id=`git rev-parse HEAD`
current_git_branch_latest_short_id=`git rev-parse --short HEAD`
current_os=`uname -s`

# echo current git branch latest commit id=$current_git_branch_latest_id
echo current git branch latest commit short id=$current_git_branch_latest_short_id
echo current os=$current_os

if [ "$current_os" == "Darwin" ]
  then
  sed -i '' 's/"version".*/"version": "1.0.0-'$current_git_branch_latest_short_id'",/g' package.json
else
  echo windows
  sed -i 's/"version".*/"version": "'$current_git_branch_latest_short_id'",/g' package.json
fi

package.json:

{
  // 依赖 husky
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "post-commit": [
        "./scripts/set-version.sh",
        "git add .",
        "git commit -m bump version",
        "git push"
      ]
    }
  },
}

官方组件缺陷处理

VsCode 插件推荐

性能优化

自动 remove console 语句

准确地说,是在正式环境下删除 console 语句,配置方式请参考 自动 remove console 语句

推荐阅读

应用内测

安卓必知必会

配置应用名

很简单,我们直接打开 android/app/src/main/res/values/strings.xml,即可看到配置中的 app_name,修改为你想要的即可。

你可以在初始化项目的时候指定应用的名字,像这样:npx react-native init MyApp --title 掘金

配置图标

1、使用图标工厂、react-native-svg-app-icon 或者让设计师给图片

2、在 android\app\src\main\res\mipmap-xxxxxx 中直接覆盖图标就可以,注意图标的大小。

打包 APK

1、在项目根目录执行 keytool -genkeypair -v -keystore release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 生成密钥文件 release.keystore

2、把 release.keystore 文件放到你工程中的 android/app 文件夹下。

3、配置 android/app/build.gradle

android{
    ...
    signingConfigs {
        release {
            storeFile file("release.keystore")
            storePassword "****"
            keyAlias "my-key-alias"
            keyPassword "****"
        }
    }
    ...
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
            ...
        }
    }
}

打包优化

去除无用的语言资源

通过配置 android/defaultConfig/resConfigs 可以选择只打包哪几种语言,进而去掉各种 aar 包中全世界的语言,尤其是 support 包中的。

选择保留什么语言要根据产品的用户和市场来定,如果只选择默认英语和中文语言,配置如下:

defaultConfig {
	resConfigs "en","zh"
}

配置 PackagingOptions

打开 android/app/build.gradle 文件,添加如下配置:

packagingOptions {
    exclude 'META-INF/android_release.kotlin_module'
    exclude 'META-INF/DEPENDENCIES'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/LICENSE.txt'
    exclude 'META-INF/license.txt'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/notice.txt'
    exclude 'META-INF/ASL2.0'
}

配置 splits

查看手机 CPU 信息:adb shell -> cd /proc -> cat cpuinfo

默认情况下,生成的 APK 会同时包含针对于 x86 和 ARMv7a 两种 CPU 架构的原生代码。这样可以让我们更方便的向其他人分享这个 APK,因为它几乎可以运行在所有的 Android 设备上。但是,这会导致所有设备上都有一些根本不会运行的代码,白白占据了空间。目前安卓设备绝大多数是 ARM 架构,因此对于大部分应用来说可以考虑去掉 x86 架构的支持。

你可以在 android/app/build.gradle 中修改如下代码:

- include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
+ include "armeabi-v7a", "arm64-v8a"
- def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
+ def versionCodes = ["armeabi-v7a":1, "arm64-v8a": 2]

gradle 编译速度优化配置

在 android\gradle.properties 中加入以下配置:

# 让gradle使用单独的守护进程
org.gradle.daemon=true
# 让gradle并行编译
org.gradle.parallel=true
# 让gradle在需要的时候才配置
org.gradle.configureondemand=true
# 增加gradle运行的java虚拟机的大小
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

BuildConfig

在 react-native 中,我们可以借助 react-native-config-reader 来方便地读取这些属性

BuildConfig 是程序编译后,根据 buildType 生成在 app\build\generated\source\buildConfig\debug(release)\ 包名下的一个 java 文件。默认有一下属性:

自定义 BuildConfig

defaultConfig {
  // 三个参数: 1.要定义的常量的类型 2.该常量的命名 3.该常量的值
  // APP_NAME,对应 ios 的 CFBundleDisplayName
  buildConfigField "String", "APP_NAME", '"我是谁"'
  // BUILD_TIME
  buildConfigField "String", "BUILD_TIME", '"' + new Date().format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("Asia/Shanghai")) + '"'
}

在子模块中取主项目的 BuildConfig

...
public static Object getBuildConfigValue(Context context, String fieldName) {
  try {
    Class<?> clazz = Class.forName(context.getPackageName() + ".BuildConfig");
    Field field = clazz.getField(fieldName);
    return field.get(null);
  } catch (ClassNotFoundException e) {
    e.printStackTrace();
  } catch (NoSuchFieldException e) {
    e.printStackTrace();
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  }
  return null;
}
...
String versionName = (String)getBuildConfigValue(activity, "VERSION_NAME"))

解决在 Android P 上的提醒弹窗 (Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

在 MainActivity.java 中添加 closeAndroidPDialog 方法并在 onCreate 方法中调用

import android.os.Bundle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
  closeAndroidPDialog(); // here
  super.onCreate(savedInstanceState);
}
...
private void closeAndroidPDialog(){
  try {
    Class aClass = Class.forName("android.content.pm.PackageParser$Package");
    Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
    declaredConstructor.setAccessible(true);
  } catch (Exception e) {
    e.printStackTrace();
  }
  try {
    Class cls = Class.forName("android.app.ActivityThread");
    Method declaredMethod = cls.getDeclaredMethod("currentActivityThread");
    declaredMethod.setAccessible(true);
    Object activityThread = declaredMethod.invoke(null);
    Field mHiddenApiWarningShown = cls.getDeclaredField("mHiddenApiWarningShown");
    mHiddenApiWarningShown.setAccessible(true);
    mHiddenApiWarningShown.setBoolean(activityThread, true);
  } catch (Exception e) {
    e.printStackTrace();
  }
}

自定义安卓打包的后缀

配置 android/app/build.gradle:

...
def releaseTime() {
    return new Date().format("yyyyMMdd-HHmmss", TimeZone.getTimeZone("GMT+08:00"))
}
...
android: {
    applicationVariants.all { variant ->
        ...
    		variant.outputs.all {
            // the apk name is e.g. galaxy_v1.0.1_2018-11-1_debug.apk
           outputFileName = "galaxy_v${defaultConfig.versionName}_${releaseTime()}_${variant.buildType.name}.apk"
        }
        ...
    }
}

常见报错及解决办法

Failed to read PNG signature: file does not start with PNG signature

有时从网上下载的 Demo 资源文件不规范,会出现直接将 jpg 文件改为 png 后缀名的情况,gradle 打包检查时报错编译通不过的。我们通过 aaptOptions.cruncherEnabled=false 来禁止 Gradle 检查 png 的合法性:

android {
  aaptOptions {
    cruncherEnabled=false
  }
}

com.android.build.api.transform.TransformException

在 android\gradle.properties 中加入以下配置:

dexOptions.javaMaxHeapSize = 2g

The number of method references in a .dex file cannot exceed 64K.

随着 Android 平台的持续成长,Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时,您会遇到构建错误,指明您的应用已达到 Android 应用构建架构的极限。

解决办法是配置您的应用进行 Dalvik 可执行文件分包,在 android/app/build.gradle 中做下面的配置:

defaultConfig {
+    multiDexEnabled true
}

ios必知必会

这部分目前作者接触不深,有大佬可以联系我补充!!!

镜像

pod install 会从 GitHub 拉代码,所以在国内会很慢。年轻人要爱国,自觉不翻墙!!!

配置应用名

  1. 选中工程名称
  2. 找到右侧菜单 Info 选项
  3. 添加 Bundle display name 并把 value 设置成 "应用名称"
  4. 最后重新再 Run一次,即可看到最新的效果

你可以在初始化项目的时候指定应用的名字,像这样:npx react-native init MyApp --title 掘金

配置图标

1、使用图标工厂、react-native-svg-app-icon 或者让设计师给图片 2、把准备好的图标拖到图中箭头指向的位置:

原文配图

获取BUILD_TYPE

在 Info.plist 中添加 BUILD_TYPE,取值为 $(CONFIGURATION)

获取构建时间

在 Info.plist 中添加 BUILD_TIME,取值为空,并通过脚本在每次编译的时候对其更新,脚本添加步骤 Target-> Build Phases -> + -> New Run Script Phase, Shell 代码如下:

#!/bin/bash
infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH"
builddate=`date +%Y-%m-%d_%H:%M`
if [[ -n "$builddate" ]]; then
/usr/libexec/PlistBuddy -c "Set :BUILD_TIME $builddate" ${infoplist}
fi

权限申请

Lean Core

COMPONENTDEPRECATED?NEW HOME
AsyncStorage0.59@react-native-community/react-native-async-storage
ImageStore0.59expo-file-system or react-native-fs
MaskedViewIOS0.59@react-native-community/react-native-masked-view
NetInfo0.59@react-native-community/react-native-netinfo
Slider0.59@react-native-community/react-native-slider
ViewPagerAndroid0.59@react-native-community/react-native-viewpager
WebView0.60react-native-webview
NetInfo0.60@react-native-community/netinfo
Geolocation0.60@react-native-community/geolocation
Apple TV Support0.62react-native-community/react-native-tvos

联系作者

作者微信知识星球赞赏作者
原图暂不可用:查看原始图片链接原图暂不可用:查看原始图片链接原图暂不可用:查看原始图片链接