2017-09-12 11:33:21

React Native的异步

RN的JSBridge是一个完成度相当高的东西。它带来了开发速度的巨大提升,相应地也导致了运行原生代码时总是异步的这一特征,本文讨论一下RN中异步的一些场景和异步带来的一些问题。

60帧

RN的目标是一秒能够渲染到60帧,这个很多人都知道。往细节上看,一秒60帧即一帧持续16.6毫秒,只要在16.6内能够运行完代码,RN就能够保证它的渲染速度。

基于这个16.6毫秒,RN的异步产生了很多东西

为什么是FlatList是纯JS实现的

早期(在v0.42之前)RN的长列表是ListView,它完全是ScrollView的一层包装,没有任何内存复用机制,这在移动端是不可接受的。一两年前就有不少团队分享过RN的自制长列表方案,在v0.42,RN终于介绍了自己的实现: FlatList

翻阅FlatList的实现,其实有一点非常明显:FlatList是纯JS实现的。为什么不原生实现呢?博主认为就是16.6毫秒的限制,内存复用在滑动中是随时随地都被触发的,使用原生代码无法再16.6毫秒内完成,因此只能选择纯JS。

好在React.PureComponent的配合下,纯JS实现的FlatList也够用了(相较于之前完全没有复用机制的ListView)

AsyncStorage

AsyncStorage的所有方法都是异步的,这带来了一个问题:app启动时不能加载用户设置的参数。这是因为,RN自身的实现是不允许异步启动App的,也就是说下面的代码会报错:

AsyncStorage.get().then(() => {
  AppRegistrin.runApplication()
})

一个典型的例子是夜间模式,应用默认的参数是日间模式,用户设置后,由于无法先加载设置后运行app,会导致应用启动时先显示日间模式,加载设置完毕后才切换为黑色,这简直是不能忍的用户体验。

目前有两种方式解决这个问题

隐藏切换

使用splash-screen的方式,在加载完设置后将splash-screen隐藏掉即可

换一种启动方式

这里强推荐RN中最良心的第三方库react-native-navigation,它直接支持异步启动app

Alert无法唤出

使用安卓一些第三方库,例如拾取图片唤出Intent时,在回调中调用Alert或者ToastAndroid这种必须在主线程中调用的方法,不会有任何反应。这同样是异步导致的问题,原生代码根本不会出现这个问题

Animated.Value与useNativeDriver

在Aniamted方法中使用useNativeDriver参数,可以将Animated运算过程全部交由原生代码,达到轻易满足60帧的动画效果。使用useNativeDriver参数时,首先在JS层异步组织触发计算过程,之后在原生代码中进行计算。

虽然看起来挺美好,但是实际上坑也蛮多的。因为触发时也是一个异步操作,所以很轻易地能够在react-navigation的TabNavigation中发现Tab下的indicator在滑动时会有大概一秒的延迟。另外,在原生代码计算时虽然帧率是满足了,但是如果此时父组件被重新渲染,则Animated.Value会被强制置为zero-value,比如opacity会被设置为0,这会导致用户看到一次闪烁,并且之后的计算全部停止,意思是opacity已经不会重新变为1了(除非再次重新渲染)。

上面第二个问题,目前RN有一个解决思路,即是将Aniamted.Value在初始化时就给他useNativeDriver参数,这样在重新渲染时原生代码可以通过这个flag继续运算。然而它的实现目前还远呢。

两个异步如果在同时间被触发,很容易会导致UI线程被严重拖慢,也就是锁。这在安卓上经常会遇到,典型的例子是TouchableNativeFeeedback触发onPress时间时涟漪会卡一下,典型的解决办法是使用requestAnimationFrame调整一下onPress的运行时机。

本文链接:https://smallpath.me/post/react-native-async

-- EOF --