react native 比 java_React Native 小结

React Native 实现模式

Bridge 通信

Bridge(c++ 层)

JS Bridge 是封装 JSCore 作为中间适配层桥接,实现 js 层和 native 层双端通信。React Native 就运行在 JSCore 上,也不会存在 ES6 兼容性之类的问题。

在原生端提供的 Native Module 模块(如网络请求,ViewGroup 控件),和 JS 端提供的 JS Module,都会在 C++实现的 so 中保存起来,双方的通讯通过 C++ 中的保存的映射,最终实现两端的交互。通信的数据和指令,在中间层会被转为 String 字符串传输。

JS 层

在 RN 代码里写的前端组件都是 Virtual DOM 这个概念,然后交给 Native 去解析

Java / Objective-C

作为逻辑入口,启动 c++ 层的 JSCore,执行 js 通过 c++ 传递来的渲染指令,从而构建 NativeUI

最终的 Bundle

JS 会被打包压缩成 bundle 文件,添加到 App 的资源目录下

React Native 在优化的路上

RN 的优化大致是在两个方向:首屏渲染优化 + UI 更新优化

JSBundle 体积

RN 中最终 pack 出来的 JS Bundle 文件不仅仅包含了业务的页面 JS 逻辑,还包含了 RN 组件和其框架的 JS

生成 JSBundle

react-native bundle --entry-file index.js --bundle-output index.ios.bundle --platform ios

var __DEV__ = true,

__BUNDLE_START_TIME__ = this.nativePerformanceNow

? nativePerformanceNow()

: Date.now(),

process = this.process || {};

process.env = process.env || {};

process.env.NODE_ENV = 'development';

...

缩小 Bundle 体积

Lazy Require

这个点其实在前端优化性能中也比较常见(著名的雅虎 36 条军规也有说明)

import React, { Component } from 'react';

import { TouchableOpacity, View, Text } from 'react-native';

let VeryExpensive = null;

export default class Optimized extends Component {

state = { needsExpensive: false };

didPress = () => {

if (VeryExpensive == null) {

VeryExpensive = require('./VeryExpensive').default;

}

this.setState(() => ({

needsExpensive: true

}));

};

render() {

return (

Load

{this.state.needsExpensive ? : null}

);

}

}

渲染更新方面shouldComponentUpdate

PureComponent

改变了 SCU,自动检查组件是否需要重新渲染

公共的问题就是只会做浅比对,如果是引用对象的话,考虑上 immutable 吧 233..

从 ListView 无法支撑数据量大的列表到 FlatList

在 FlatList 之前,社区中有种种思路封装成高性能的 ListView 的方案可以参考 FlatList 的源码​github.com

从源码中可见是包装了 VirtualizedList,继承于 ScrollView

ScrollView -- getItemLayout

如果预先知道列表中的每一项的高度(ITEM_HEIGHT)和其在父组件中的偏移量(offset)和位置(index),就能减少一次渲染。如果不做 getItemLayout 的优化,每个列表都需要事先渲染一次,动态地取得其渲染尺寸,然后再真正地渲染到页面中。

getItemLayout={(data, index) => (

{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}

)}

VirtualizedList 中的优化

FlatList 之所以节约内存、渲染快,是因为它只将用户看到的(和即将看到的)部分真正渲染出来了。而用户看不到的地方,渲染的只是空白元素。渲染空白元素相比渲染真正的列表元素需要内存和计算量会大大减少,这就是性能好的原因。

FlatList 将页面分为 4 部分。初始化部分/上方空白部分/展现部分/下方空白部分。初始化部分,在每次都会渲染;当用户滚动时,根据需求动态的调整(上下)空白部分的高度,并将视窗中的列表元素正确渲染出来。

if (itemCount > 0) {

_usedIndexForKey = false;

_keylessItemComponentName = '';

const spacerKey = !horizontal ? 'height' : 'width';

const lastInitialIndex = this.props.initialScrollIndex

? -1

: this.props.initialNumToRender - 1;

const { first, last } = this.state;

this._pushCells(

cells,

stickyHeaderIndices,

stickyIndicesFromProps,

0,

lastInitialIndex,

inversionStyle

);

const firstAfterInitial = Math.max(lastInitialIndex + 1, first);

// first 就是 在视图中(包括要即将在视图)的第一个 item if (!isVirtualizationDisabled && first > lastInitialIndex + 1) {

let insertedStickySpacer = false;

if (stickyIndicesFromProps.size > 0) {

const stickyOffset = ListHeaderComponent ? 1 : 0;

// See if there are any sticky headers in the virtualized space that we need to render. for (let ii = firstAfterInitial - 1; ii > lastInitialIndex; ii--) {

if (stickyIndicesFromProps.has(ii + stickyOffset)) {

const initBlock = this._getFrameMetricsApprox(lastInitialIndex);

const stickyBlock = this._getFrameMetricsApprox(ii);

const leadSpace =

stickyBlock.offset -

initBlock.offset -

(this.props.initialScrollIndex ? 0 : initBlock.length);

cells.push(

);

this._pushCells(

cells,

stickyHeaderIndices,

stickyIndicesFromProps,

ii,

ii,

inversionStyle

);

const trailSpace =

this._getFrameMetricsApprox(first).offset -

(stickyBlock.offset + stickyBlock.length);

// 从第 11 个 items (除去初始化的 10个 items) 到 first 渲染空白元素 cells.push(

);

insertedStickySpacer = true;

break;

}

}

}

if (!insertedStickySpacer) {

const initBlock = this._getFrameMetricsApprox(lastInitialIndex);

const firstSpace =

this._getFrameMetricsApprox(first).offset -

(initBlock.offset + initBlock.length);

cells.push(

);

}

}

// last 是最后一个在视图(包括要即将在视图)中的元素。 // 从 first 到 last ,即用户看到的界面渲染真正的 item this._pushCells(

cells,

stickyHeaderIndices,

stickyIndicesFromProps,

firstAfterInitial,

last,

inversionStyle

);

if (!this._hasWarned.keys && _usedIndexForKey) {

console.warn(

'VirtualizedList: missing keys for items, make sure to specify a key property on each ' +

'item or provide a custom keyExtractor.',

_keylessItemComponentName

);

this._hasWarned.keys = true;

}

if (!isVirtualizationDisabled && last < itemCount - 1) {

const lastFrame = this._getFrameMetricsApprox(last);

const end = this.props.getItemLayout

? itemCount - 1

: Math.min(itemCount - 1, this._highestMeasuredFrameIndex);

const endFrame = this._getFrameMetricsApprox(end);

const tailSpacerLength =

endFrame.offset + endFrame.length - (lastFrame.offset + lastFrame.length);

// last 之后的元素,渲染空白 cells.push(

);

}

}

既然要使用空白元素去代替实际的列表元素,就需要预先知道实际展现元素的高度(或宽度)和相对位置。如果不知道,就需要先渲染出实际展现元素,在获取完展现元素的高度和相对位置后,再用相同(累计)高度空白元素去代替实际的列表元素。_onCellLayout 就是用于动态计算元素高度的方法,如果事先知道元素的高度和位置,就可以使用上面提到的 getItemLayout 方法,就能跳过 _onCellLayout 这一步,获得更好的性能。

_onCellLayout(e, cellKey, index) {

const layout = e.nativeEvent.layout;

// 计算相关尺寸 const next = {

offset: this._selectOffset(layout),

length: this._selectLength(layout),

index,

inLayout: true,

};

const curr = this._frames[cellKey];

if (

!curr ||

next.offset !== curr.offset ||

next.length !== curr.length ||

index !== curr.index

) {

this._totalCellLength += next.length - (curr ? curr.length : 0);

this._totalCellsMeasured += curr ? 0 : 1;

this._averageCellLength =

this._totalCellLength / this._totalCellsMeasured;

this._frames[cellKey] = next;

this._highestMeasuredFrameIndex = Math.max(

this._highestMeasuredFrameIndex,

index,

);

this._scheduleCellsToRenderUpdate();

} else {

this._frames[cellKey].inLayout = true;

}

FlatList 优化小结

基于 ScrollView 的优化,至于为什么不直接使用 IOS/Android 的列表组件In UITableView, when an element comes on screen, you have to synchronously render it. This means that you've got less than 16ms to do it. If you don't, then you drop one or multiple frames.

UITableView 的渲染要求是视窗中的元素需要同步渲染,超过 16ms 会掉帧The problem is in the RN render -> shadow -> yoga -> native loop. You have at least three runloop jumps (dispatch_async(dispatch_get_main_queue(), ...) as well as background thread work, which all work against the required goal.

RN render 的过程到调用 native 组件,不可以保证在 16ms 内We are actually starting to experiment more and more with synchronous method calls for other modules, which would allow us to build a native list component that could call renderItem on demand and choose whether to make the call synchronously on the UI thread if it's hi-pri (including the JS, react, and yoga/layout calcs), or on a background thread if it's a low-pri pre-render further off-screen. This native list component might also be able to do proper recycling and other optimizations.

终极方案大概就是在高性能场景下可以选择去直接使用 native 组件

总结

很多的优化方式,不仅是 Native,在做 Web 应用/ React 程序时是共通的地方。

官方文档中的 Performance 章节还有很多地方可以去继续看看。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值