react-native组件避免重复渲染

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

转载http://www.tuicool.com/articles/vY7Vjym

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>组件AText>
        )
    }
}
class ComponentB extends Component {
    render() {
        console.log('渲染B');
        return (
            <View>
                <Text>组件BText>
                <ComponentC/>
            View>
        )
    }
}
class ComponentC extends Component {
    render() {
        console.log('渲染C');
        return (
            <View>
                <Text>组件CText>
                <ComponentD/>
            View>
        )
    }
}
class ComponentD extends Component {
    render() {
        console.log('渲染D');
        return (
            <View>
                <Text>组件DText>
            View>
        )
    }
}

组件关系图如下:

react-native组件避免重复渲染_第1张图片

测试现象:

若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。


你可能感兴趣的:(react-native组件避免重复渲染)