React Native 新手常见问题及体验总结

0xinhua 发布于

之前有使用 React Native 的开发经历,记录下了开发过程的常见的错误和解决方案,也算是从头趟了一遍 RN 开发过程的坑,本篇主要包括日常踩得一些坑以及我使用 React Native 开发体验总结。

#State update on an unmounted component

Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

这是一个常见的错误,当我们初始化首页 screen 屏幕时会使用 fetch 做一些异步请求,请求成功后在回调方法里使用 setState 更新组件数据,但如果在请求 pending 状态下跳转到其它屏,组件已经销毁,这时再去 setState 会报 Warning ⚠️,这个问题的解决思路有两个:

  1. 组件销毁时取消发出的 fetch 请求。
  2. 增加组件是否销毁判断字段,在 componentWillUnmount 方法里设置组件已销毁。

如何中断一个 fetch 请求?可以尝试使用 AbortController API, AbortController 接口是一个控制器对象,在 fetch 请求中增加 signal 参数,允许你在需要时中止一个或多个请求,例如:

javascript
1class HomeScreen extends Component { 2 abortController = new AbortController(); 3 constructor(props) { 4 super(props); 5 6 this.state = { 7 data: [], 8 }; 9 } 10 11 componentDidMount() { 12 13 fetch('https://my.api.com/api?query=reactnative', { signal: this.abortController.signal }) 14 .then(result => { 15 this.setState({ 16 data: result.data, 17 }); 18 }); 19 } 20 21 componentWillUnmount() { 22 this.abortController.abort() 23 } 24 25 render() { 26 ... 27 } 28} 29

我使用另一个更简单的方法,在调用 setState 方法更新数据前使用 _isMounted 字段来判断组件是否已经被销毁:

javascript
1class HomeScreen extends Component { 2 _isMounted = false; 3 4 constructor(props) { 5 super(props); 6 7 this.state = { 8 data: [], 9 }; 10 } 11 12 componentDidMount() { 13 this._isMounted = true; 14 15 fetch('https://my.api.com/api?query=reactnative') 16 .then(result => { 17 if (this._isMounted) { 18 this.setState({ 19 data: result.data, 20 }); 21 } 22 }); 23 } 24 25 componentWillUnmount() { 26 this._isMounted = false; 27 } 28 29 render() { 30 ... 31 } 32} 33

React Native 升级到 0.60 后,支持 AndroidX 和 Autolinked 自动连接 package 包算是两个比较大的更新,具体可以查看更新 blog

AndroidX is a major step forward in the Android ecosystem, and the old support library artifacts are being deprecated. For 0.60, React Native has been migrated over to AndroidX. This is a breaking change, and your native code and dependencies will need to be migrated as well.

但是坑出在有些文档没有及时更新,包括第三方库没有及时更新,导致我在使用 react navigation 、react-native-gesture-handler 第三方库时遇到的一连串的bug。包括 react-native-gesture-handler 使用了android support 的一些库,没有及时更新到 AndroidX,因为 AndroidX 是谷歌支持库的替代品,目前 Android P 处于过渡阶段,下一个版本的 Android 可能只支持AndroidX。

xxxx/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.java:3: error: package android.support.v4.util doesn't exist import android.support.v4.util.Pools; ^ xxxx/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.java:19: error: package Pools doesn't exist private static final Pools.SynchronizedPool EVENTS_POOL = ^ error: package android.support.v4.util doesn't exist import android.support.v4.util.Pools;

解决方法:

无法自动 link 的包可以手动链接依赖,例如在 iOS 平台手动 link react-native-gesture-handlerreact-native-svg等包,建议先执行unlink命令,添加 react-native.config.js 文件:

javascript
1 2module.exports = { 3 dependencies: { 4 'react-native-gesture-handler': { 5 platforms: { 6 ios: null 7 } 8 }, 9 'react-native-svg' 10 platforms: { 11 ios: null 12 } 13 } 14 } 15}; 16

使用 jetify 处理 AndroidX 导致的问题:

package.json里添加 jetify相关配置:

json
1"postinstall": "npx jetify"

使用 react navigation 开发路由导航过程中遇到的几个 bug 可以查看提的这几个 issues:

  1. react-navigation/issues/6066
  2. react-native-gesture-handler/issues/649
  3. react-native-gesture-handler/issues/494

#Flex in React Native

React Native 中同样也可以使用 flexbox 布局,和 web 上基本一致,如果不了解 flex 布局的可以查看之前总结的这篇 Flexbox布局完全指南,但也一些需要注意的地方:

  1. flex 的排列方向默认是竖直排列: flexDirection:column。
  2. flex 只能指定一个数字值,它有三种状态:正数、零与负数。
  3. alignItems 在 React Native 中默认是:alignItems: stretch。
  4. 部分属性在RN中不支持:align-content,flex-basis,order,flex-basis,flex-flow,flex-grow,flex-shrink。

具体用法这里不再介绍,官方文档上有很详细的描述。 Layout with Flexbox

#DeviceException 未连接设备

error Failed to install the app. Make sure you have an Android emulator running or a device connected. Run CLI with --verbose flag for more details. Error: Command failed: ./gradlew app:installDebug -PreactNativeDevServerPort=8081 FAILURE: Build failed with an exception. What went wrong: Execution failed for task ':app:installDebug'. com.android.builder.testing.api.DeviceException: No connected devices!

这个报错是安卓的 Virtual Devices 没有开启,可以使用 adb devices 命令查看当前设备,确保运行 react-native run-android 命令时开启调试设备,安卓平台可以在 Android Studio - Tools - AVD Manager 中打开。

#YellowBox ignore Warnings

使用 YellowBox 忽视一些警告 ⚠️,但不推荐这样做,毕竟一些警告还是比较有用的信息,例如 react native 提示componentWillMount 将会弃用,使用 componentDidMount 代替:

Warning: componentWillMount is deprecated and will be removed in the next major version. Use componentDidMount instead. As a temporary workaround, you can rename to UNSAFE_componentWillMount. Please update the following components: EventScreen Learn more about this warning here: https://fb.me/react-async-component-lifecycle-hooks

在开发环境可以使用 YellowBox 忽略这些警告:

javascript
1 2import { YellowBox } from 'react-native'; 3YellowBox.ignoreWarnings(['Warning: ...']); 4

RedBoxes and YellowBoxes are automatically disabled in release (production) builds.

不过在发布或线上打包的版本中,YellowBoxes 的提示会被自动禁掉。

#引用第三方库 build 失败

例如使用 react-native-wechat 集成微信的好友、朋友圈分享功能时,Android 环境 dev 本地开发环境一切正常运行,使用命令进行 apk 打包时提示 gradle build 失败:

error: failed linking references. FAILURE: Build failed with an exception.
What went wrong: Execution failed for task ':RCTWeChat:verifyReleaseResources'. com.android.ide.common.process.ProcessException: Failed to execute aapt Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. Get more help at https://help.gradle.org

这类问题很多情况是第三方包 gradle 版本配置与主体项目下的配置不一致导致的,例如项目使用的 28.0.3,而第三方包使用的是更低的版本,建议将引用库 gradle 配置更新成与 RN 项目 android 文件下 build.gradle 文件配置一致,例如:

java
1buildscript { 2 ext { 3 buildToolsVersion = "28.0.3" 4 minSdkVersion = 16 5 compileSdkVersion = 28 6 targetSdkVersion = 28 7 supportLibVersion = "28.0.0" 8 } 9 repositories { 10 google() 11 jcenter() 12 } 13 dependencies { 14 15 classpath("com.android.tools.build:gradle:3.4.1") 16 17 // NOTE: Do not place your application dependencies here; they belong 18 // in the individual module build.gradle files 19 } 20}

#如何进行 React Native 开发调试

模拟器开启 Developer Menu:

iOS 模拟器可以通过 Command⌘ + D 快捷键来快速打开 Developer Menu,Android 模拟器可以通过 Command⌘ + M 快捷键来快速打开 Developer Menu。

如果想查看组件的层级关系,可以全局安装 react 开发配套工具 react-devtools:

shell
1npm install -g react-devtools 2react-devtools

运行后 React Native 应用会自动连接,会出现 connectting 窗口,刷新 App 即可看到当前 screen 的组件层级情况:

如果想看控制台输出 log 日志,可以输入以下命令:

shell
1react-native log-ios 2react-native log-android

#万能的清除缓存和重启

经常碰到一些莫名其妙的错误,发现以下命令删除本地包,重装依赖重启总是很管用:

sh
1watchman watch-del-all // 清除 Watchman: 2rm -rf node_modules && yarn install // 删除 node_modules并重装 3react-native start --reset-cache // 清除 rn 缓存重启 4react-native run-ios 或 react-native run-android 5

总结

先后有 Airbnb 和 Udacity 表示将弃坑 React Native,回归到原生开发上。具体可查看公司技术博客:

As a result, moving forward, we are sunsetting React Native at Airbnb and reinvesting all of our efforts back into native. - Airbnb

1. Sunsetting React Native
2. React Native: A retrospective from the mobile-engineering team at Udacity

我简要谈谈自己的体验和看法:

“文档应当鲜活并保持更新”

当然这句话不是我说的,是 Eric Evans 在《领域驱动设计》这本书中的一句话,我引用它是想表达:如果代码能实现或体现最好,文档应该是起辅助作用的,而不是累赘;文档没及时跟上代码的更新将会给开发者带来很多麻烦。React Native 的文档已经被很多人吐槽过,RN 社区曾经有个 issue => What do you dislike about React Native? 用来收集开发者在使用RN时的痛点,排在前三的就是:升级调试崩溃。一旦需要从 RN 的旧版本升级到一个新的大版本时就变得异常困难。

第三方组件、库的支持

虽然目前 React Native 社区已经发展得足够大,社区的开发者发布了很多组件、库等方便大家开发时引用,实现各种复杂的需求,提供对业务的支持。但问题出在很多库没有及时跟上RN的大版本发布及相关改动,或者说有些库的已经不再更新维护,在没有时间自己开发或寻找替代品的时候,这也是很棘手的一个问题。

开发速度快但有一定的上手难度

作为混合的跨平台解决方案,React Native 具有比较高的可行性, 相对于大公司移动端会有比较大的团队来开发维护原生 App,RN 比较适用于(2/3个开发人员)小团队成员,或者说没有足够的人员分别去开发原生应用的团队,小团队并且直接使用 JavaScript 生态系统的库、工具就能开发应用,一套代码两端适用,开发速度上有大幅度提升,也节约了人力成本的开支。有经验的 JavaScript 和 Web 开发人员能很快得入手,但是对于新手,尤其是对 React 及相关概念不了解的同学,会有一定的上手难度。

没有使用其它混合方案框架例如 Fluter、Weex 等进行开发混合应用,这里无法进行对比,只是总结最近一段时间使用 React Native 的开发体验,从初始化项目到应用上架整个过程并不是很流畅,真机调试过程经常有应用崩溃的情况发生,代码排查错误或者找问题的过程花费了我比较多的时间,另一方面是使用第三方组件、库等是一个踩坑过程,文档等没有及时更新,在添加配置的过程出现很多问题,可能是我刚好碰上 60 这个大版本,总的来说我认为 React Native 应该还是目前比较好并且稳定的混合应用开发方案,但在开发者使用体验上社区和团队需要进一步优化改善。

附录

参考学习的一些资料:

  1. Announcing React Native 0.60
  2. React Native at Airbnb
  3. React Native: A retrospective from the mobile-engineering team at Udacity