(续集)ReactNative宇宙第一性能优化指导

上篇文章《ReactNative宇宙第一性能优化指导》介绍了图片性能优化,以及useMemo,useCallBack,React.memo,useEffect 这四大优化天王。

这篇文章将从 TypeScript语言本身入手、接着讲如何利用 Mobx 全局状态管理来优化ReactNative组件性能。


题外话:

如果可以,一定要空出十来天的时间整理掌握的RN技术栈,整理完后水平会提升N个档次( 过年期间就是个最佳时间段、不知道各位抓住机会了没、哈哈哈哈哈)


1. 利用TypeScript本身的特性来解决数据共享问题

先看需要优化的示例:

const HomePage = (props) => { console.log('HomePage Run'); const [text,setText] = useState(''); const [score,setScore] = useState(0); const textChangeHandler = (value:string) => { setText(value) } const scoreChangeHandler = (value:number) => { setScore(value); } return ( <Page.PageCleared navigation={props.navigation}> <View flex center> //文本输入框 <TextInput placeholder={'输入评价'} value={text} onChangeText={textChangeHandler} /> <Button label={'Done'} onPress={() => {}}/> //评分组件 <ScoreCreator onValueChange={scoreChangeHandler}/> </View> </Page.PageCleared> ) }; -- 评分组件内部 -- function ScoreCreator(props){ console.log('ScoreCreator Run') ... //评分发生改变,调用父组件传过来的回调 props.onValueChange(score); ... }

很显然,在我们没有做任何处理的情况下。无论哪个组件的值发生变化,都会导致两个组件重新‘执行’、比起'render' 我觉得' run' 更合适。

当我们在输入框输入值时,控制台输出如下:

HomePage Run ScoreCreator Run

当我们改变评分时,控制台输出如下:

HomePage Run ScoreCreator Run

代码优化分析

首先,上一篇讲的是如何通过一些hooks来优化组件,使得父组件state的变化避免子组件的重新run 、render。那么这一块要优化的是如何在避免子组件的变化引起父组件的run、render的同时又拿到子组件内部某个变化后的值

解决方案一,利用TypeScript语言本身的特性来解决问题!这个方案比使用mobx的性能开销更小!!

解决方法就是单例!这在面向对象语言中是极其常见的,而TypeScript正好是一门面向对象语言。

第一步,创建数据类, ScoreData.ts

class ScoreData { static shared = new ScoreData(); private score:number = 0; public setScore(param:number) { this.score = param; console.log('评分值被改变为',this.score) } public getScore():number { return this.score } public reset(){ this.score = 0; } } export default ScoreData;

第二步,分别在子组件和父组件中引入ScoreData类,并拿到 shared变量。从而更改或获取评分值 , score。

优化后的代码如下:

//-- HomePage.tsx import ScoreData from './ScoreData'; const shared = ScoreData.shared; const HomePage = (props) => { console.log('HomePage Run'); // state 优化前 const [text,setText] = useState(''); const [score,setScore] = useState(0); const textChangeHandler = (value:string) => { setText(value) } const scoreChangeHandler = (value:number) => { setScore(value); } //state 优化后 const [text,setText] = useState(''); const textChangeHandler = (value:string) => { setText(value) } return ( ... //评分组件优化前 <ScoreCreator onValueChange={scoreChangeHandler}/> //评分组件优化后 <ScoreCreator /> ... ) }; -- 评分组件内部 -- //-- ScoreCreator.tsx import ScoreData from './ScoreData'; const shared = ScoreData.shared; function ScoreCreator(props){ console.log('ScoreCreator Run') ... //评分发生改变,修改数据类中的变量 score。 //这里由最初的调用父组件传来的回调改为调用单例中的方法 shared.setScore(score) ... }

通过上述的改动,当评分组件发生变化时HomePage不会再run 。 这样做使得父组件不仅能拿到子组件变化后的值。而且子组件的变化也不会导致父组件重新 run 。

接下来还有一步优化没有完成!

那就是优化父组件state的变化而引起的子组件的重新run 。

这里使用到上篇讲的 React.memo 来进行优化

// HomePage.tsx const MemomizedScoreCreator = React.memo(() => <ScoreCreator /> ); const HomePage = (props) => { ... return ( ... //评分组件优化前 <ScoreCreator /> //评分组件再次优化后 <MemomizedScoreCreator /> ... )

至此,整个HomePage 优化完成。 TextInput 的输入不会导致 ScoreCreator 重新 Run, Render。 评分组件的值的改变也不会使得 HomePage 重新 Run, Render ,而且HomePage也能即使拿到更新后的评分 score

解决方案二:

使用全局状态管理库 Mobx

第一步:创建ScoreStore.ts

import { observable, action } from 'mobx'; class ScoreStore { @observable public score:number = 0; @action.bound public setScore(value:number){ this.score = value; } @action.bound public reset(){ this.score = 0; } } export default new ScoreStore();

第二步:分别在 父组件和子组件中引入 ScoreStore这个类

父组件可通过 ScoreStore.score 获取 score的值。而子组件可以通过 ScoreStore.setScore()来修改score的值。需要注意的是,父组件和子组件都最好是使用 observer 来包裹。否则值得更新可能会不及时,或者无法获取最新变化后的值

子组件中对应的修改是将方案一中的

shared.setScore(score) 修改为 ScoreStore.setScore(score)

所以说,使用单例会更加方便简单,性能开销也比使用Mobx要小。但这仅仅局限于不需要值的变化来及时更新view的情况。

2. 玩转mobx,将挂载点模式应用到mobx上,以最小的ReRender开销来解决数据获取问题。

关于这个模式的用处有多大,我也不清楚,在新架构中我有用到,但用的较少。至少目前来讲用的不多。

用处一:

将APP初始化操作、包括各种初始化网络接口查询操作都放到挂载点中执行。从而避免在跟view中数据的不断变化而导致整个view的不断Run,Render。比如静默登录操作,一般放在首页或者App.tsx 中,这样的话会使得整个应用所有组件又重新Run一遍。而且如果home页变化的话,可能会忘记将登录函数重新添加到新的home页而导致出现静默登录失败的bug。

通过将所有初始化数据获取操作都放进挂载点组件,将挂载点挂载到应用入口。这样做的话就避免了因为数据的取回而导致应用绝大部分组件重新Run的问题。

挂载点组件的一般结构如下:

import React,{ useEffect } from 'react'; import { View } from 'react-native'; import { observer } from 'mobx-react'; import Store from './Store'; const InitMountPoint = observer((props:{ param?: string }) => { function _init(){ //执行初始化 Store.setData(...更新数据) } function signIn(){ //调用登录接口 } useEffect(() => { _init(); signIn(); },[]) return ( <View /> ) }); export default InitMountPoint;

使用方法很简单,在APP入口文件, App.tsx中使用该组件(这里的代码直接放了我新架构中的现成的代码, 在我的架构中,初始化数据fetch等都在数据中心的挂载点OneForAllMountPoint中)

const ApolloApp: () => JSX.Element = () => { var MyClient = clientBuilder(DataCenter.User.user?.token ?? ""); useEffect(() => { DataCenter.App_setClient(MyClient); }, []); return ( <ApolloProvider client={MyClient}> <App/> //挂载点 <OneForAllMountPoint client={MyClient} /> </ApolloProvider> ) } export default observer(ApolloApp);

因为我们知道,子组件内部的状态改变并不会影响到父组件。挂载点模式也正是利用了这个特性。

但是挂载点技术的真正核心不在于上面讲的这些,由于篇幅问题。这里不再继续讲解挂载点的更多使用技巧。

挂载点用的好有助于优化性能,但是不能滥用。

日记本

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

赞赏支持
被以下专题收入,发现更多相似内容