1. 目的:
- 优化渲染速度
- 减少渲染次数
2. 优化流程:
2.1 梳理组件结构
优化后的结构图
–略
2.2 需要优化的内容
针对渲染时间和渲染次数进行优化
减少渲染时间:
- 避免内联函数定义 → 每次重新渲染都重新创建函数实例,VDom属性改变,dom节点重新渲染。
//bad: onClick方法中,使用了内联匿名函数
<Comp onClick={(e)=>{this.setState({isClick:ture})}} key={item.id}/>
//good: 声明函数
const handleClick = (e) => this.setState({isClick:ture})
<Comp onClick={handleClick} key={item.id}/>
- 避免内联组件定义 → 每次都重复创建匿名组件实例,引起Comp VDom变更,dom节点重新渲染
//bad: onClick方法中,使用了内联匿名组件
<Comp prop={<View props={this.state.name}> ... </View>} key={item.id}/>
//good: 声明组件
const propComp = (props: {name}) => (<View props={name}> ... </View>)
<Comp prop={propComp} key={item.id}/>
- 避免内敛style定义 → 更新vdom,重新渲染
//bad :
<Comp style={{flex: 1}} />
//good:
flex: {
flex: 1
}
<Comp style={flex} />
-
减少render函数内声明 → 每次重新渲染都会执行render函数,在render函数中定义子组件或方法,会增加每次重新渲染耗时。若是较大的组件或等待方法,耗时更明显
//bad: 每次重新渲染都会重新创建navBarLeftRender与handlePageBack class ScanList extends React.Component<ScanListProps, ScanListState> { ... ... render() { const navBarLeftRender = ( <TouchableOpacity onPress={hadnlePageBack} ... /> ) const handlePageBack = async() => { await this.props?.onBack?.(); } return ( <View> <NavBar leftProps={navBarLeftRender} /> ... </View> ) } } //good: 将render中声明的函数与组件都拿到render外 class ScanList extends React.Component<ScanListProps, ScanListState> { ... const navBarLeftRender = ( <TouchableOpacity onPress={hadnlePageBack} ... /> ) const handlePageBack = async() => { await this.props?.onBack?.(); } ... render() { ... return ( <View> <NavBar leftProps={navBarLeftRender} /> ... </View> ) } }
-
记忆子组件 → 在引用的props不变更的情况下,不因父组件重新渲染而引起子重新渲染。
//bad: 无论leftProps是否更新,NavBar总是随着ScanList组件重新渲染。 render() { ... return ( <View> <NavBar leftProps={navBarLeftRender} /> ... </View> ) } //good: 只在leftProps更新时渲染 const MemoNavBar = memo(NavBar) render() { ... return ( <View> <MemoNavBar leftProps={navBarLeftRender} /> ... </View> ) }
-
长列表优化 → 目前组件用到的flatList没有使用官方推荐的优化属性, 如: maxToRenderPerBatch, initialNumToRender。
减少渲染次数:
引起重新渲染主要场景:
-
组件状态变更[略]
-
父组件渲染导致子组件重新渲染
-
梳理出每个子组件使用到的state,状态下放。
//bad: 若点击button,会触发整个Component的更新,导致veryslowComponent也重新渲染 const Component = () => { const [open, setOpen] = useState(false); return ( <Something> <Button onClick={()=> setOpen(true)} {isOpen && <ModalDialog />} <VerySlowComponent /> </Something> ) } //good: 将只作用在Dialog的open state集中到ButtonWithDialog中 //open更新只引起ButtonWithDialog的重新渲染, VerySlowComponent不受影响 const ButtonWithDialog = () => { const [open, setOpen] = useState(false); return ( <> <Button onCLick={() => setOpen(true)} /> {isOpen && <ModalDialog />} </> ) } const Component = () => { return ( <Something> <ButtonWithDialog /> <VerySlowComponent /> </Something> ) }
-
记忆组件
通过PureComponent,或者memo,或者shouldComponentUpdate 控制class组件是否有必要重新渲染。
-
基于b,使用组件的页面,若每次都传入重新定义的pure function可以使用usecallback , usememo,减少引起scanList重新渲染次数
-
-
context变更导致引用的组件重新渲染[略]
-
hooks内状态变更触发重新渲染[略]
3. 具体优化内容
-略
4 前后对比
通过flipper performace记录优化后的各项值,前后对比
最终优化性能对比:
直观的火焰图: 可以看到渲染次数和渲染时间都比初始大大减少
页面操作耗时对比:
5. 遗留:
emulator随网络波动,cpu运行情况 影响大,同一修改节点,刷新前后渲染时长有较大差异,用真机更可靠,但目前真机无法使用filpper的profilling。