React性能優化之Context

先來講說Context的做用

react之類的mvvm框架有個痛點想必你們都遇到過,那就是祖孫組件之間的通訊html

如今組件之間的通訊規範是:前端

  • 父 -> 子:屬性綁定props
  • 子 -> 父:事件廣播(vue之類的emit)react那仍是調用父類的props[fn]

僅僅是父子兩個組件之間這沒有問題
但是祖孫組件之間呢?vue

react中有個蟲洞的概念,至關於直通車,組件【祖先】=> 組件【孫子】,而不須要中間的組件來代理propsreact

下面就先介紹下高端的蟲洞(直通車)

直接show code數組

三個組件App.js,Son.js,GrandSon.js,下面介紹的是用蟲洞從App props直通GrandSon,而不須要Son來作代理屬性bash

// App.js的代碼

import React from 'react';
import Son from './son';
import PropTypes from 'prop-types';

class App extends React.Component{
    constructor(){
		super();
		
        this.state = {
            grandSonDesc: 'i am grandSonDesc'
        };
    }

    getChildContext(){
        return {
            grandSonDesc: this.state.grandSonDesc
        }
    }
    
    render(){
        return <Son />
    }
}

App.childContextTypes = {
    grandSonDesc: PropTypes.string
}

export default App;
複製代碼
// Son.js

import React from 'react';
import GrandSon from './grandSon';

const Son = () => {
    return <GrandSon />
}

export default Son;
複製代碼
// GrandSon.js

import React from 'react';
import PropTypes from 'prop-types';

class GrandSon extends React.Component{
    render(){
        return <div>{this.context.grandSonDesc}</div>
    }
}

GrandSon.contextTypes = {
    grandSonDesc: PropTypes.string
};

export default GrandSon;
複製代碼

至此就實現了蟲洞了,但是須要注意兩點:框架

  • GrrandSon不能爲純函數組件,這樣是不能注入的
  • App組件和GrandSon組件須要定義類屬性contextTypes,App組件須要定義getChildContext

那麼問題來了,爲啥要聲明contextTypes? 緣由是,GrandSon組件除了App這個爺爺組件外,還有Son這個爸爸組件。那麼grandSonDesc是從哪一個組件繼承而來呢,contextTypes就是作這個標識做用,並且多個父組件的context會作merge,也是根據定義contextTypes來的mvvm

好的,到這裏感受很爽,蟲洞直通車傳輸props,可是我要說的是,這個實如今react17將被廢棄!!!ide

爲什麼老版本蟲洞將被廢棄?

若是一個屬性將被廢棄,多半是跟它的性能有關了,能夠試想下,蟲洞傳輸props,其實son組件沒有任何更新,那麼當grandSonDesc變化時,Son組件會不會被更新?直接show code吧函數

// App.js

import React from 'react';
import Son from './son';
import PropTypes from 'prop-types';

class App extends React.Component{
    constructor(){
		super();
		
        this.state = {
            grandSonDesc: 'i am grandSonDesc'
        };
	}
	
	componentDidMount(){
		setTimeout(() => {
			this.setState({
				grandSonDesc: 'i am grandSonDesc啊啊啊啊啊啊啊啊'
			});
		}, 3000);
	}

    getChildContext(){
        return {
            grandSonDesc: this.state.grandSonDesc
        }
    }
    
    render(){
        return <Son />
    }
}

App.childContextTypes = {
    grandSonDesc: PropTypes.string
}

export default App;
複製代碼
// Son.js

import React from 'react';
import GrandSon from './grandSon';

class Son extends React.Component{
    componentDidUpdate(){
        console.log('son updated!');
    }

    render(){
        return <GrandSon />
    }
}

export default Son;
複製代碼
// GrandSon.js

import React from 'react';
import PropTypes from 'prop-types';

class GrandSon extends React.Component{
    componentDidUpdate(){
        console.log('grandson updated!');
    }

    render(){
        return <div>{this.context.grandSonDesc}</div>
    }
}

GrandSon.contextTypes = {
    grandSonDesc: PropTypes.string
};

export default GrandSon;
複製代碼

在版本的代碼中,App.js在三秒鐘以後去更新grandSonDesc,而後在Son和GrandSon組件中加上componentDidUpdate更新以後的log,來看一下控制檯輸出

三秒以後GrandSon的顯示變成了i am grandSonDesc啊啊啊啊啊啊啊啊,而控制檯輸出son updated

這說明Son組件也被更新了,那麼蟲洞若是中間層組件比較多的話,豈不是很浪費性能嗎?

二代蟲洞

那麼既然性能很差,世界頂級前端團隊就要開始優化了,那麼就有了二代蟲洞,直接上代碼介紹下二代蟲洞吧,仍是原來的配方App+Son+GrandSon,惟一多的是一個context的配置文件

// App.js

import React from 'react';
import Son from './son';
import Context from './context';
import PropTypes from 'prop-types';

class App extends React.Component {
    constructor(){
        super();

        this.state = {
            grandSonDesc: 'i am grandSon'
        };
    }

	render(){
		return (
			<div className="App">
				<Context.Provider value={this.state}>
					<Son />
				</Context.Provider>
			</div>
		);
	}
}

App.contextType = Context;

export default App;
複製代碼
// Son.js

import React, { PureComponent } from 'react';
import GrandSon from './grandSon';
import PropTypes from 'prop-types';

class Son extends PureComponent{
    render(){
        return <GrandSon />
    }
}

export default Son;
複製代碼
// GrandSon.js

import React, { PureComponent } from 'react';
import Context from './context';

class GrandSon extends PureComponent{
    render(){
        return <div>
            <Context.Consumer>
                {value => value.grandSonDesc}
            </Context.Consumer>
        </div>
    }
}

export default GrandSon
複製代碼
// context.js

import { createContext } from 'react';

let Context = createContext();

export default Context;
複製代碼

代碼敲完了,能夠start看看效果了,能夠發現照樣跑起來了

那麼問題來了,你說一代蟲洞性能差,憑什麼嘛?

// App.js

import React from 'react';
import Son from './son';
import Context from './context';
import PropTypes from 'prop-types';

class App extends React.Component {
    constructor(){
        super();

        this.state = {
            grandSonDesc: 'i am grandSon'
        };
    }

    componentDidMount(){
        setTimeout(() => {
            this.setState({
                grandSonDesc: 'i am grandSon啊啊啊啊啊啊啊啊啊啊'
            });
        }, 3000);
    }

	render(){
		return (
			<div className="App">
				<Context.Provider value={{grandSonDesc: this.state.grandSonDesc}}>
					<Son />
				</Context.Provider>
			</div>
		);
	}
}

App.contextType = Context;

export default App;

複製代碼
// Son.js

import React, { PureComponent } from 'react';
import GrandSon from './grandSon';
import PropTypes from 'prop-types';

class Son extends PureComponent{
    componentDidUpdate(){
        console.log('son updated');
    }

    render(){
        return <GrandSon />
    }
}

export default Son;
複製代碼
// GrandSon.js

import React, { PureComponent } from 'react';
import Context from './context';

class GrandSon extends PureComponent{
    componentDidUpdate(){
        console.log('grandson updated');
    }

    render(){
        return <div>
            <Context.Consumer>
                {value => value.grandSonDesc}
            </Context.Consumer>
        </div>
    }
}

export default GrandSon
複製代碼

能夠發現,3秒以後grandSonDesc變了,兩個子組件的componentDidUpdated都沒進

這就是兩種蟲洞的實現方式,到這裏差很少講完了,關於更多新版context的用法能夠參考官網 reactjs.org/docs/contex…