React高階組件探究

React高階組件探究

在使用React構(gòu)建項(xiàng)目的過程中,經(jīng)常會(huì)碰到在不同的組件中需要用到相同功能的情況。不過我們知道,去這些組件中到處編寫同樣的代碼并不優(yōu)雅。

在以往,為了解決以上的情況,我們會(huì)用到Mixin這種方式來解決問題。

以下是一個(gè)最為簡(jiǎn)單的Mixin

var defaultMixin = {
    getDefaultProps: function() {
        return {
            name: "Allen"
        }
    }
}

var Component = React.createClass({
    mixins: [defaultMixin],
    render: function() {
        return <h1>Hello, {this.props.name}</h1>
    }
})

這個(gè)例子很好理解,在這里就不詳述了。要使用mixin屬性,我們只需要簡(jiǎn)單地在組件中加入mixins屬性即可。

不過在React ES6中,因?yàn)榉N種原因,mixin將不再被支持。

詳情可以參考以下文章:

  1. Mixin已死,Composition 萬歲

  2. React官方在ES6中不建議使用mixin

文章中提到用高階組件來替代mixin
所謂高階組件其實(shí)只是一個(gè)方法。這個(gè)方法中返回一個(gè)包裹著原組件的新組件。接下來看看它的基本用法。

高階組件基本介紹

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        componentDidMount() {
            console.log("hoc");
        }
    
        render() {
            return <ComponentClass />
        }
    }
}

以上代碼可以看作是一個(gè)最簡(jiǎn)的高階組件

使用起來也很簡(jiǎn)單,比如有一個(gè)React組件。

class ComponentClass extends React.Component {
    render() {
        return <div></div>
    }
}

export default hoc(MyComponent);

只需要將你的組件類作為參數(shù)傳入高階組件這個(gè)方法中即可。

如果采用ES7的decorator語法。則可以更簡(jiǎn)潔

@hoc
export default class ComponentClass extends React.Component {
    //...
}

簡(jiǎn)單來說,高階組件就是一個(gè)函數(shù),它讓你傳入一個(gè)組件類,然后返回給你一個(gè)更強(qiáng)大的組件類。

用更生動(dòng)一點(diǎn)的說法,高階組件就像是一個(gè)buff……

雞血(大錘)  ===>  打了雞血的大錘

@超級(jí)buff
class 賽亞人 {}   //超級(jí)賽亞人get~~~

類似以上這種感覺……

高階組件中的props、ref、state

操作props###

你可以在高階組件中對(duì)ComponentClass中的props進(jìn)行讀取、添加、編輯操作。

例子:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        render() {
            const newProps = {
                name: "cqm",
                value: "testData",
            }
            return <ComponentClass {...this.props} {...newProps} />
        }
    }
}

高階組件中能夠通過this.props直接獲取到ComponentClass中的props,假設(shè)this.props中有name,那么newProps中的name會(huì)覆蓋掉this.props中的name。

通過ref訪問組件實(shí)例###

你可以通過ref訪問到組件中的實(shí)例。可以看以下例子:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        
        componentDidMount() {
            console.log(this.refs.componentClass);
        }
    
        render() {
            return <ComponentClass ref="componentClass" />
        }
    }
}

提示:React中ref還有這種回調(diào)函數(shù)的寫法: ref={(dom) => this.dom = dom} 依稀記得好像在哪里看到過,說這種寫法更新更好

你也可以使用另一種方式:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        
        getDom(dom) {
            console.log(dom);
        }
        
        render() {
            return <ComponentClass ref={(dom) => this.getDom(dom)} />
        }
    }
}

ref回調(diào)函數(shù)在ComponentClass渲染時(shí)執(zhí)行,從而可以輕松拿到它的引用。

獲取state###

在高階組件中獲取state有若干種方式,下面將一一闡述。

和React父子組件交互的方式類似,你可以往ComponentClass中傳入一個(gè)函數(shù),之后ComponentClass中通過props拿到這個(gè)函數(shù),往里面?zhèn)魅肽阆胍膕tate參數(shù)。

示例如下:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        
        getState(state) {
            console.log(state);
        }
        
        render() {
            const newProps = {
                getState: (state) => this.getState(state)
            };
            return <ComponentClass {...newProps} />
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    state = {
        value = ""
    };
    
    render() {
        return (
            <div>
                <input onChange={(z) => this.props.getState(z.target.value)} />
            </div>
        );
    }
}

理論上你還可以通過this.refs.componentClass.state這種方式來獲取全部的state(不過我一般不這么干)。


接下來用到的獲取state的方式應(yīng)該算是高階組件的另一種運(yùn)用方式

我在文章開篇提到過這句話

所謂高階組件其實(shí)只是一個(gè)方法。這個(gè)方法中返回一個(gè)包裹著原組件的新組件。接下來看看它的基本用法。

高階組件中所謂的包裹方式主要有以下兩種:

  1. Props Proxy: 高階組件通過ComponentClass中的props來進(jìn)行相關(guān)的操作。

  2. Inheritance Inversion: 高階組件繼承自ComponentClass。

之前我們并未用到第二種包裹方式,大部分采用的Props Proxy的方式來進(jìn)行操作。接下來我們來詳細(xì)講講第二種方式。

Inheritance Inversion

Inheritance Inversion的最簡(jiǎn)實(shí)現(xiàn)大概如下:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            return super.render();
        }
    }
}

高階組件hoc中,返回了一個(gè)繼承了原先組件類的組件。頗有一種反轉(zhuǎn)的味道。

顯而易見,你可以在這個(gè)組件類中拿到一切ComponentClass的props、state。

代碼如下:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        
        componentDidMount() {
            console.log(this.state);
        }
    
        render() {
            return super.render();
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    
    state = {
        id: 0,
        value: "hahaha"
    };
    
    render() {
        return <div>ComponentClass</div>
    }
}

你也可以通過super.[lifecycle]來調(diào)取ComponentClass的聲明周期或是render函數(shù)

接下來我們可以來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的需求。比如說一個(gè)組件中存在網(wǎng)絡(luò)請(qǐng)求,你希望在請(qǐng)求完成之前組件顯示loading,完成后再顯示具體內(nèi)容。我們就可以運(yùn)用高階組件來實(shí)現(xiàn)。

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            if (this.state.success) {
                return super.render()
            }
            return <div>Loading...</div>
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    state = {
        success: false,
        data: null
    };
    
    async componentDidMount() {
        const result = await fetch(...請(qǐng)求);          this.setState({
            success: true,
            data: result.data
        });
    }
    
    render() {
        return <div>主要內(nèi)容</div>
    }
}

這個(gè)例子應(yīng)該很好理解。這邊就不再詳細(xì)解釋了。

Inheritance Inversion渲染劫持

渲染劫持這句話的意思很好理解。因?yàn)樵诟唠A組件中你可以控制ComponentClass的渲染輸出,因此你可以在渲染的時(shí)候做出各種各樣的事情。比如修改props,修改render的組件樹等等。

以下示例用以演示修改render方法輸出的組件樹

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            const elementTree = super.render();
            elementTree.props.children = elementTree.props.children.filter((z) => {
                return z.type !== "ul" && z;
            }
            const newTree = React.cloneElement(elementTree);
            return newTree;
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    render() {
        const divStyle = {
            width: '100px',
            height: '100px',
            backgroundColor: 'red'
        };
        
        return (
            <div>
                <p style={{color: 'brown'}}>啦啦啦</p>
                <ul>
                    <li>1</li>
                    <li>2</li>
                </ul>
                <h1>哈哈哈</h1>
            </div>
        )
    }
}

以上就是一個(gè)渲染劫持的示例,我將ComponentClass中的ul標(biāo)簽給移除了。你可以使用渲染劫持完成類似的需求。如果你對(duì)于React中的組件元素操作不是很了解的話,推薦您去看看官方文檔中的這一節(jié):

React官方關(guān)于component-elements的文檔

實(shí)際案例

mobx-react

/**MyStore文件**/
import { observable } from 'mobx';

export class MyStore() {
    @observable value = 1;
    
    @action add = () => {
        this.value++;
    }
}

/**另一個(gè)文件**/
import MyStore from './MyStore';

const store = new MyStore();

@observer
export default class MyComponent extends React.Component {
    render() {
        return (
            <div>
                <p>這只青蛙的壽命{store.value}</p>
                <button onClick={store.add}>+1s</button>
            </div>
        );
    }
}

可以把observer當(dāng)成高階組件。傳入MyComponent這個(gè)類,之后mobx-react會(huì)對(duì)其生命周期進(jìn)行各種處理,并通過調(diào)用實(shí)例的forceUpdate來進(jìn)行刷新實(shí)現(xiàn)最小粒度渲染。

算是小小安利一下mobx這個(gè)框架吧。

順便一提, mobx中提倡一份數(shù)據(jù)引用,而在redux中則更提倡immutable思想,返回新對(duì)象。

pure-render-decorator

@pureRender
export default class MyComponent extends React.Component {
    
    static propTypes = {
        name: React.PropTypes.string,
    };
    
    render() {
        return (
            <div>{this.props.name}</div>
        );
    }
}

pureRender中重新定義了shouldComponentUpdate這一生命周期,當(dāng)傳來的props未發(fā)生改變時(shí),不重新render,從而達(dá)到性能優(yōu)化的目的。


以上。。。

References

  1. Mixin的使用
  2. 深入理解React高階組件
  3. React進(jìn)階——使用高階組件(Higher-order Components)優(yōu)化你的代碼
  4. Mixin 已死,Composition 萬歲
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容