react-native组件避免重复渲染

react-native若有性能问题,很可能是由于组件的重复渲染,研究下相关知识点。

export default class Home1 extends Component {
    render() {
        console.log('渲染根');
        return (
            <View style={{backgroundColor: 'white', marginTop: 100}}>
                <ComponentA/>
                <ComponentB/>
            </View>
        );
    }
}
class ComponentA extends Component {
    render() {
        console.log('渲染A');
        return (
            <Text>组件A</Text>
        )
    }
}
class ComponentB extends Component {
    render() {
        console.log('渲染B');
        return (
            <View>
                <Text>组件B</Text>
                <ComponentC/>
            </View>
        )
    }
}
class ComponentC extends Component {
    render() {
        console.log('渲染C');
        return (
            <View>
                <Text>组件C</Text>
                <ComponentD/>
            </View>
        )
    }
}
class ComponentD extends Component {
    render() {
        console.log('渲染D');
        return (
            <View>
                <Text>组件D</Text>
            </View>
        )
    }
}

组件关系图如下:


测试现象:
若A被渲染,则C、D也会跟着被渲染,其他不变。
若根被渲染,所有组件都重新渲染。
若B被渲染,其他组件不变。
结论:某一组件被render,只会导致本身和所有的子组件被重新render,其他的没有影响。

问题:例如当A被渲染时,C、D的数据并未改变,甚至C、D根本就是无状态无属性组件,但它们也被重新渲染了,浪费性能。
组件是否重新渲染的决定因素是组件的生命周期方法shouldComponentUpdate的返回值,而Component组件该方法的默认实现返回值总是true,所以会被重新渲染。可以重写该方法让其根据业务需求判断是否返回true来决定是否刷新组件。但是每个组件都重写该方法很麻烦。
PureComponent组件,继承自Component,已经实现了shouldComponentUpdate,大概实现如下

function shouldComponentUpdate(nextProps, nextState) {
  return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}

shallowEqual实现在"node_modules/fbjs/lib/shallowEqual"处,高效判断两个对象是否相等,从而决定是否渲染页面。
将上面所有组件的父类改为PureComponent,再次测试,发现当A被渲染时,C、D也不会被渲染了,性能肯定变好。

此法虽好,但也有不少副作用,比如将B组件的实现改为如下

class ComponentB extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            num: 4,
            arr: [1,2,3]
        };
    }
    componentDidMount() {
        setInterval(() => {
            this.state.arr.push(this.state.num);
            this.setState({
                num: this.state.num + 1,
                arr: this.state.arr
            })
        }, 2000)
    }
    render() {
        console.log('渲染B');
        return (
            <View>
                <ComponentC arr={this.state.arr}/>
                <Text>{`组件B: ${this.state.num}`}</Text>
            </View>
        )
    }
}

开个定时器,不断改变arr的值,组件C的属性发生了变化,但是由于C组件的shouldComponentUpdate判断方法shallowEqual只能对对象做浅比较,也就是判断对象的地址是否一致,这里数组本身地址并未发生变化,仅仅是内容有所变化,该方法鉴别不出来,返回的是false,页面不会重新被渲染,有大问题。

这里的解决方案有好些:
a、在C组件中重写shouldComponentUpdate,判断arr内容是否变化,决定重新渲染。
b、B组件给C组件传递属性前,将arr对象进行深拷贝(有性能问题),重新生成对象
c、利用不可变对象,我这里用的是轻量级的seamless-immutable

不可变对象有诸多优点就不说了,总之很好很强大,性能提升利器。使用之后B组件代码如下

class ComponentB extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            num: 0,
            arr: Immutable([1,2,3])
        };
    }
    componentDidMount() {
        setInterval(() => {
            let arr = Immutable.asMutable(this.state.arr);
            arr.push(5);
            let newArr = Immutable(arr);
            this.setState({
                num: this.state.num + 1,
                arr: newArr
            })
        }, 2000)
    }
    render() {
        console.log('渲染B');
        return (
            <View>
                <ComponentC arr={this.state.arr}/>
                <Text>{`组件B: ${this.state.num}`}</Text>
            </View>
        )
    }
}

使用就三步:
1、导入头文件,import Immutable from 'seamless-immutable'
2、数组或对象初始化时使用如Immutable([1,2,3])的方式
3、改变数组或对象时使用专门的api,比如Immutable.asMutable、Immutable.flatMap

有些组件拥有继承关系,但是顶层父类又是继承的Component,这时可以采用pure-render-decorator,给该组件添加一个装饰器扩展方法shouldComponentUpdate,这个效果跟PureComponent基本一致。

import pureRenderDecorator from 'pure-render-decorator';
@pureRenderDecorator
class ComponentA extends PureComponent {

还有个小问题,上面B组件传递到C组件的属性arr,在C组件中并没有在render方法中被使用,理论上该组件是不需要不断渲染的,但是因为shouldComponentUpdate方法返回true所以会反复渲染。当然既然B组件传递了属性arr给C,那么实际开发中arr肯定是必不可少的。我要说的是,如果在开发中C组件拿到arr不是为了渲染更新页面的目的,而是其他的比如统计之类的跟页面渲染无关的事,那么,还是需要自己重写shouldComponentUpdate,避免不必要的渲染发生。

接下来简单说下seamless-immutable中数组的实现方式:
Immutable([1,2,3]),会调用到下面这些方法

function makeImmutableArray(array) {    // 方法A
    for (var index in nonMutatingArrayMethods) {
        if (nonMutatingArrayMethods.hasOwnProperty(index)) {
            var methodName = nonMutatingArrayMethods[index];
            makeMethodReturnImmutable(array, methodName);       // 方法B
        }
    }
    // 给数组添加上flatMap、asObject等一系列自定义方法
    if (!globalConfig.use_static) {
        addPropertyTo(array, "flatMap", flatMap);
        addPropertyTo(array, "asObject", asObject);
        addPropertyTo(array, "asMutable", asMutableArray);
        addPropertyTo(array, "set", arraySet);
        addPropertyTo(array, "setIn", arraySetIn);
        addPropertyTo(array, "update", update);
        addPropertyTo(array, "updateIn", updateIn);
        addPropertyTo(array, "getIn", getIn);
    }
    // 让数组中的每一项都变为不可变对象
    for (var i = 0, length = array.length; i < length; i++) {
        array[i] = Immutable(array[i]);
    }
    return makeImmutable(array, mutatingArrayMethods);  // 方法C
}

function makeMethodReturnImmutable(obj, methodName) {    // 方法B实现
    var currentMethod = obj[methodName];
    Object.defineProperty(obj, methodName, {
        enumerable: false,
        configurable: false,
        writable: false,
        value: Immutable(currentMethod.apply(obj, arguments))
    })
}

function makeImmutable(obj, bannedMethods) {    // 方法C实现
    for (var index in bannedMethods) {
        if (bannedMethods.hasOwnProperty(index)) {
            banProperty(obj, bannedMethods[index]);
        }
    }
    Object.freeze(obj);
    return obj;
}

B方法:
参数obj就是数组对象,methodName为"map", "filter", "slice", "concat", "reduce", "reduceRight等。Object.defineProperty为数组重定义属性和方法,writable为false变为不可写,该方法的目的就是让数组的这些方法失去作用,外界调用不可变数组的map、concat等方法后不再有效。

C方法:bannedMethods为"push", "pop", "sort", "splice", "shift", "unshift", "reverse"等,banProperty方法的实现也是用Object.defineProperty实现,作用是外界调用不可变数组的push、pop等方法时直接报错。最后Object.freeze(obj)冻结整个数组对象,让其本身无法被修改,变为不可变对象。

A方法:包含B、C,并且给数组添加上自定义的很多方法如遍历flatMap、转换为普通数组asMutable等。array[i] = Immutable(array[i])使数组内部的每一个成员都变为不可变对象,在这里,若数组内部元素又是个数组,则会递归到该方法再次执行,直到数组内部所有对象都变为了不可变对象,基本数据类型不可变对象就是本身。

seamless-immutable使用Object.defineProperty扩展了JavaScript 的Array和Object对象来实现,只支持 Array和Object两种数据类型,API 基于与 Array 和 Object ,因此许多不用改变自己的使用习惯,对代码的入侵非常小。同时,它的代码库也非常小,压缩后下载只有 2K。相比于笨重的教科书级别的Immutable无疑更适用于react-native。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • React对重排和重绘的提高雅虎性能优化比较重要的点,老司机自行忽略。如下图,HTML被浏览器解析为DOM树,CS...
    HT_Jonson阅读 1,659评论 0 49
  • react 基本概念解析 react 的组件声明周期 react 高阶组件,context, redux 等高级...
    南航阅读 1,069评论 0 1
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,824评论 0 24
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,834评论 1 18
  • 昨天明明还艳阳高照 今夜却突然滂沱大雨 生活不就是这样吗 平平坦坦的却突然要绊你一脚 让你体会一下生活的跌宕起伏 ...
    长生月阅读 353评论 0 1