1. 程式人生 > >react-native元件避免重複渲染

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元件的實現改為如下

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。