react-native组件避免重复渲染

转载于:https://blog.csdn.net/sinat_17775997/article/details/77971016

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>组件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组件的实现改为如下

 
  1. class ComponentB extends PureComponent {

  2. constructor(props) {

  3. super(props);

  4. this.state = {

  5. num: 4,

  6. arr: [1,2,3]

  7. };

  8. }

  9. componentDidMount() {

  10. setInterval(() => {

  11. this.state.arr.push(this.state.num);

  12. this.setState({

  13. num: this.state.num + 1,

  14. arr: this.state.arr

  15. })

  16. }, 2000)

  17. }

  18. render() {

  19. console.log('渲染B');

  20. return (

  21. <View>

  22. <ComponentC arr={this.state.arr}/>

  23. <Text>{`组件B: ${this.state.num}`}</Text>

  24. </View>

  25. )

  26. }

  27. }

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

这里的解决方案有好些:

a、在C组件中重写shouldComponentUpdate,判断arr内容是否变化,决定重新渲染。

b、B组件给C组件传递属性前,将arr对象进行深拷贝(有性能问题),重新生成对象

c、利用不可变对象,我这里用的是轻量级的seamless-immutable

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

 
  1. class ComponentB extends PureComponent {

  2. constructor(props) {

  3. super(props);

  4. this.state = {

  5. num: 0,

  6. arr: Immutable([1,2,3])

  7. };

  8. }

  9. componentDidMount() {

  10. setInterval(() => {

  11. let arr = Immutable.asMutable(this.state.arr);

  12. arr.push(5);

  13. let newArr = Immutable(arr);

  14. this.setState({

  15. num: this.state.num + 1,

  16. arr: newArr

  17. })

  18. }, 2000)

  19. }

  20. render() {

  21. console.log('渲染B');

  22. return (

  23. <View>

  24. <ComponentC arr={this.state.arr}/>

  25. <Text>{`组件B: ${this.state.num}`}</Text>

  26. </View>

  27. )

  28. }

  29. }

使用就三步:

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]),会调用到下面这些方法

 
  1. function makeImmutableArray(array) { // 方法A

  2. for (var index in nonMutatingArrayMethods) {

  3. if (nonMutatingArrayMethods.hasOwnProperty(index)) {

  4. var methodName = nonMutatingArrayMethods[index];

  5. makeMethodReturnImmutable(array, methodName); // 方法B

  6. }

  7. }

  8. // 给数组添加上flatMap、asObject等一系列自定义方法

  9. if (!globalConfig.use_static) {

  10. addPropertyTo(array, "flatMap", flatMap);

  11. addPropertyTo(array, "asObject", asObject);

  12. addPropertyTo(array, "asMutable", asMutableArray);

  13. addPropertyTo(array, "set", arraySet);

  14. addPropertyTo(array, "setIn", arraySetIn);

  15. addPropertyTo(array, "update", update);

  16. addPropertyTo(array, "updateIn", updateIn);

  17. addPropertyTo(array, "getIn", getIn);

  18. }

  19. // 让数组中的每一项都变为不可变对象

  20. for (var i = 0, length = array.length; i < length; i++) {

  21. array[i] = Immutable(array[i]);

  22. }

  23. return makeImmutable(array, mutatingArrayMethods); // 方法C

  24. }

  25.  
  26. function makeMethodReturnImmutable(obj, methodName) { // 方法B实现

  27. var currentMethod = obj[methodName];

  28. Object.defineProperty(obj, methodName, {

  29. enumerable: false,

  30. configurable: false,

  31. writable: false,

  32. value: Immutable(currentMethod.apply(obj, arguments))

  33. })

  34. }

  35.  
  36. function makeImmutable(obj, bannedMethods) { // 方法C实现

  37. for (var index in bannedMethods) {

  38. if (bannedMethods.hasOwnProperty(index)) {

  39. banProperty(obj, bannedMethods[index]);

  40. }

  41. }

  42. Object.freeze(obj);

  43. return obj;

  44. }

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值