问题描述
刚开始做 RN 项目,在实现的商品列表有导航切换的布局时切换非常卡顿,总是要等待几秒钟才能渲染完效果。
即使在每个商品 item 上增加 shouldComponentUpdate 严格控制,切换也很卡顿。
截图
rn_tab_demo.png
实现代码如下
// 根据数据渲染所有导航对应的商品列表,
// 通过透明度来实现商品列表的隐藏显示,
// 辅以定位和层级来实现当前的列表是可用
<View key="goods-list-wrap">
{tab_data.map((item, idx) => {
let selected = idx === current_nav_index
return (
<View
key={`goods-list-${idx}`}
style={cn(styles, { scene: true, show: selected })}
>
<FlatList
style={{ flex: 1 }}
contentContainerStyle={[styleA, styleB]}
data={list}
renderItem={this.renderGoodsItem}
numColumns={2}
keyExtractor={(it) => it.goods_id}
/>
</View>
)
})}
</View>
// style 的部分代码
scene: {
flex: 1,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
opacity: 0,
overflow: 'hidden',
},
show: {
opacity: 1,
zIndex: 1,
},
商品 item 组件实现的 shouldComponentUpdate
shouldComponentUpdate(nextProps) {
if (nextProps.item === this.props.item) {
return false
}
return true
}
尝试优化手段
基于以上的实现思路,做了各种优化尝试,都没能很好的解决切换卡顿的问题。
实现方式:
- 商品 item 的 shouldComponentUpdate 控制更严格
- 精简更多的 dom 层级
- 包括把 FlatList 独立组件增加 shouldComponentUpdate 控制
- 严格控制父级组件的状态更新,确保在导航切换没有因父级状态变化而加深渲染耗时
解决问题
尝试多种方法后,实在没有思路,想到已经实现的类似组件有哪些,经过一番查找,
在 RN 社区中发现 react-native-tab-navigator 这个组件,研究后发现一些差异,在本地尝试后发现效果有比较大的提升
// 摘抄部分 react-native-tab-navigator 代码
<View
{...props}
pointerEvents={selected ? 'auto' : 'none'}
removeClippedSubviews={!selected}
style={[
styles.sceneContainer,
selected ? null : styles.hiddenSceneContainer,
props.style,
]}>
<StaticContainer shouldUpdate={selected}>
{this.props.children}
</StaticContainer>
</View>
// StaticContainer 中的方法
shouldComponentUpdate(nextProps) {
return !!nextProps.shouldUpdate;
}
主要差异有两点
- 使用 removeClippedSubviews,在非当前选中列表中都启用这个属性
- 它的所有内容通过统一组件实现 shouldComponentUpdate,只有选中列表上启用更新
结论
实现方式还是通过减少渲染内容来解决切换卡顿的问题,确保每次更新只渲染需要的部分。
removeClippedSubviews + shouldComponentUpdate 确保更新的只有当前选中列表。
removeClippedSubviews
这是一个特殊的性能相关的属性,由 RCTView 导出。在制作滑动控件时,如果控件有很多不在屏幕内的子视图,会非常有用。
要让此属性生效,首先要求视图有很多超出范围的子视图,并且子视图和容器视图(或它的某个祖先视图)都应该有样式 overflow: hidden。
附上实现的 SceneContainer 源码
import React from 'react'
import { View, StyleSheet, ViewPropTypes } from 'react-native'
import PropTypes from 'prop-types'
function SceneContainer({ selected, children, ...props }) {
return (
<View
{...props}
pointerEvents={selected ? 'auto' : 'none'}
removeClippedSubviews={!selected}
style={[
styles.sceneContainer,
selected ? null : styles.hiddenSceneContainer,
props.style,
]}
>
<StaticContainer shouldUpdate={selected}>
{children}
</StaticContainer>
</View>
)
}
SceneContainer.propTypes = {
...ViewPropTypes,
selected: PropTypes.bool,
}
SceneContainer.defaultProps = {
selected: false,
}
class StaticContainer extends React.Component {
shouldComponentUpdate(nextProps) {
return !!nextProps.shouldUpdate
}
render() {
let { children } = this.props
return children ? React.Children.only(children) : null
}
}
StaticContainer.propTypes = {
shouldUpdate: PropTypes.bool,
}
StaticContainer.defaultProps = {
shouldUpdate: false,
}
let styles = StyleSheet.create({
sceneContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
hiddenSceneContainer: {
overflow: 'hidden',
opacity: 0,
},
})
export default SceneContainer
网友评论