React高階組件與mixin使用

轉(zhuǎn):http://www.reibang.com/p/9ab8a2e0403a
在多個(gè)不同的組件中需要用到相同的功能荧降,其解決辦法有兩種:mixin和高階組件慕购。

1牺丙、mixin
mixin一直被廣泛用于各種面向?qū)ο笳Z言中伪节,其作用是為單繼承語言創(chuàng)造一種類似多重繼承的效果。

廣義的mixin方法,就是用賦值的方式將mixin對(duì)象中的方法都掛載到原對(duì)象上,來實(shí)現(xiàn)對(duì)象的混入酒繁,類似ES6中的Object.assign()的作用。原理如下:

const mixin = function(obj, mixins){
const newObj = obj;
newObj.prototype = Object.create(obj.prototype);

for(let prop in mixins){ // 遍歷mixins的屬性
    if(mixins.hasOwnPrototype(prop)){ // 判斷是否為mixin的自身屬性
        newObj.prototype[prop] = mixins[prop]; // 賦值
    }
}

return newObj;

}
實(shí)質(zhì)上就是把任意多個(gè)源對(duì)象擁有的自身可枚舉屬性復(fù)制給目標(biāo)對(duì)象控妻,然后返回目標(biāo)對(duì)象欲逃。那么React中的mixin是這樣的么?

在React中使用mixin
React在使用createClass構(gòu)建組件時(shí)提供了mixin屬性饼暑,比如官方封裝的PureRenderMixin:

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

React.createClass({
mixins: [PureRenderMixin],

render(){
    return <div>foo</div>;
}

});
可以看出稳析,mixins是一個(gè)數(shù)組,封裝了我們需要的模塊弓叛。不同mixin的方法或許會(huì)有重合彰居,如何處理視重合部分是普通方法還是生命周期方法而定。

在不同mixin里實(shí)現(xiàn)兩個(gè)名字一樣的普通方法:并不會(huì)覆蓋撰筷,且控制臺(tái)會(huì)報(bào)錯(cuò)陈惰。
重合的是生命周期方法:將各個(gè)mixin的生命周期方法疊加在一起順序執(zhí)行。
可以看到毕籽,使用createClass實(shí)現(xiàn)的mixin為組件做了兩件事:

工具方法:mixin的基本功能抬闯。用來定義共享的工具類方法,以便在各個(gè)組件中使用关筒。
生命周期繼承溶握,props與state合并:mixin可以合并生命周期方法。如果有多個(gè)mixin定義了componentDidMount()蒸播,React會(huì)自動(dòng)將它們合并處理睡榆。同樣萍肆,mixin也可以作用在getInitialState的結(jié)果上,作state的合并胀屿,而props的合并也是這樣的塘揣。
然而,使用ES6 classes構(gòu)建組件時(shí)宿崭,并不支持mixin亲铡。這就不得不說到decorator語法糖。

ES6 classes 與 decorator
decorator是運(yùn)用在運(yùn)行時(shí)的方法葡兑,用以對(duì)組件進(jìn)行“修飾”〗甭現(xiàn)在,使用decorator來實(shí)現(xiàn)mixin:

function handleClass(target, mixins){
if(mixins.length){
for(let i=0, l=mixins.length; i<l; i++){
// 獲取mixins的attribute對(duì)象
const decs = getOwnPropertyDescriptors(mixins[i]);
}

    // 定義mixins的attribute對(duì)象
    for(const key in decs){
        if(!(key in target.prototype)){
            defineProperty(target.prototype, key, decs[key]);
        }
    }
}

}

function mixin(...mixins){
if(typeof mixins[0] === 'function'){
return handleClass(mixins[0], []);
}else{
return target=>{
return handleClass(target,mixins);
}
}
}
不難看出铁孵,這個(gè)mixin與本文開頭createClass的mixin的實(shí)現(xiàn)是不一樣的:createClass的mixin是直接給對(duì)象的prototype屬性賦值,而這里是使用getOwnPropertyDescriptors和defineProperty進(jìn)行定義房资。賦值與定義的區(qū)別在于賦值會(huì)覆蓋已有的定義蜕劝,而后者不會(huì)。兩者在本質(zhì)上都與官方的mixin方法存在區(qū)別轰异,除了定義方法級(jí)別不能覆蓋之外岖沛,還得加上對(duì)生命周期方法的繼承以及對(duì)state的合并。

當(dāng)然搭独,decorator除作用在類上婴削,還可以作用在方法上,但不在此處討論牙肝。

minxin的缺陷
破壞了原有組件的封裝:可能會(huì)帶來新的state和props,意味著會(huì)有些“不可見”的狀態(tài)需維護(hù)唉俗。
命名沖突:不同mixin中的命名不可知,故非常容易發(fā)生沖突配椭,需要花一定成本解決虫溜。
增加了復(fù)雜性,難以維護(hù)股缸。
2衡楞、高階組件
由于mixin存在上述缺陷,故React剝離了mixin敦姻。改用高階組件來取代它瘾境。
高階組件其實(shí)是一個(gè)函數(shù),接收一個(gè)組件作為參數(shù)镰惦,返回一個(gè)新的組件作為返回值迷守,類似于高階函數(shù)。高階組件和decorator是同一模式旺入,因此盒犹,因此高階組件可以作為decorator來使用。高階組件基本形式:

const EnhancedComponent = higherOrderComponent(WrappedComponent);
decorator形式:

@higherOrderComponent
WrappedComponent
高階組件有以下好處:

適用范圍廣,它不需要es6或者其它需要編譯的特性急膀,有函數(shù)的地方沮协,就有HOC。
Debug友好卓嫂,它能夠被React組件樹顯示慷暂,所以可以很清楚地知道有多少層,每層做了什么晨雳。
高階組件實(shí)現(xiàn)的方法有兩種:

屬性代理:通過被包裹組件的props來進(jìn)行相關(guān)操作行瑞。主要進(jìn)行組件的復(fù)用。
反向繼承:繼承被包裹的組件餐禁。主要進(jìn)行渲染的劫持血久。
1、屬性代理
屬性代理主要是四個(gè)作用:操作props帮非、通過refs訪問組件實(shí)例氧吐、抽象state、使用其他元素包裹WrappedComponent末盔。

(1)操作props
包括對(duì)props的讀取筑舅、增加、刪除陨舱、修改翠拣。刪除和修改要注意不能影響原組件。
示例:增加一個(gè)props

function compHOC(WrappedComponent) {
return class Comp extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
(2)通過refs訪問組件實(shí)例

可以通過ref回調(diào)函數(shù)的形式來訪問傳入組件的實(shí)例游盲,進(jìn)而調(diào)用組件相關(guān)方法或其他操作(如實(shí)例的props操作)误墓。

//WrappedComponent初始渲染時(shí)候會(huì)調(diào)用ref回調(diào),傳入組件實(shí)例益缎,在proc方法中优烧,就可以調(diào)用組件方法
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}

render() {
  const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
  return <WrappedComponent {...props}/>
}

}
}
(3)抽象state
通過傳入 props 和回調(diào)函數(shù)抽象state。高階組件可以通過原組件抽象為展示型組件链峭,分離內(nèi)部狀態(tài)畦娄。
示例:抽象 <Input />的 value 和 onChange 方法。

function compHOC(WrappedComponent) {
return class Comp extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}

  this.onNameChange = this.onNameChange.bind(this)
}

// 將對(duì)name屬性的onChange方法提取到此處=>提取到高階組件弊仪,有效的抽象了同樣的state操作
onNameChange(event) {
  this.setState({
    name: event.target.value
  })
}
render() {
  const newProps = {
    name: {
      value: this.state.name,
      onChange: this.onNameChange
    }
  }
   return <WrappedComponent {...this.props} {...newProps}/>
}

}
}

//使用方式如下
@compHOC
class Example extends React.Component {
render() {
//使用ppHOC裝飾器之后熙卡,組件的props被添加了name屬性,可以通過下面的方法励饵,將 value 和 onChange方法 添加到input上面

return <input name="name" {...this.props.name}/>

// 變成<input name="name" value={this.state.name} onChange={this.onNameChange} />驳癌,這樣我們就得到了一個(gè)受控組件政基。

}
}

(4)使用其他元素包裹組件
用于加樣式惠猿、布局等。

function compHOC(WrappedComponent) {
return class Comp extends React.Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
2陕贮、反向繼承
高階組件繼承了WrappedComponent,意味著可以訪問并使用WrappedComponent的state甜滨,props乐严,生命周期和render方法,但它不能保證完整的子組件樹被解析衣摩。如果在高階組件中定義了與WrappedComponent中同名的方法昂验,將會(huì)發(fā)生覆蓋,就必須手動(dòng)通過super進(jìn)行調(diào)用艾扮。反向繼承有兩個(gè)比較大的特點(diǎn):渲染劫持和控制state既琴。

(1)渲染劫持
渲染劫持指的就是高階組件可以控制 WrappedComponent 的渲染過程,并渲染各種各樣的結(jié)果泡嘴。我們可以在這個(gè)過程中在任何React元素輸出的結(jié)果中讀取甫恩、增加、修改酌予、刪除props磺箕,或讀取或修改React元素樹,或條件顯示元素樹霎终,又或者是用元素包裹元素樹滞磺。
大致形式如下:

function compHOC(WrappedComponent) {
return class ExampleEnhance extends WrappedComponent {
...
componentDidMount() {
super.componentDidMount();
}
componentWillUnmount() {
super.componentWillUnmount();
}
render() {
...
return super.render();
}
}
}
例如升薯,實(shí)現(xiàn)一個(gè)顯示loading的請(qǐng)求莱褒。組件中存在網(wǎng)絡(luò)請(qǐng)求,完成請(qǐng)求前顯示loading涎劈,完成后再顯示具體內(nèi)容广凸。(條件渲染)
可以用高階組件實(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>
}
}
正如前面所說,反向繼承不能保證完整的子組件樹被解析蛛枚,這意味著會(huì)限制渲染劫持功能谅海。渲染劫持的經(jīng)驗(yàn)法則是:我們可以操控 WrappedComponent 的元素樹,并輸出正確的結(jié)果蹦浦。但如果元素樹中包括了函數(shù)類型的React組件扭吁,就不能操作組件的子組件。

(2)控制state
高階組件可以讀取盲镶,編輯和刪除WrappedComponent實(shí)例的state侥袜,可以添加state。不過這個(gè)可能會(huì)破壞WrappedComponent的state溉贿,所以枫吧,要限制高階組件讀取或添加state,添加的state應(yīng)該放在單獨(dú)的命名空間里宇色,而不是和WrappedComponent的state混在一起九杂。
例如:通過訪問WrappedComponent的props和state來做調(diào)試

export function IIHOCDEBUGGER(WrappedComponent) {
return class II extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
)
}
}
}
3颁湖、組件命名
用HOC包裹的組件會(huì)丟失原先的名字,影響開發(fā)和調(diào)試例隆∩啵可以通過在WrappedComponent的名字上加一些前綴來作為HOC的名字,以方便調(diào)試裳擎。
參考react-redux實(shí)現(xiàn):

HOC.displayName = HOC(${getDisplayName(WrappedComponent)});
//或
class HOC extends ... {
static displayName = HOC(${getDisplayName(WrappedComponent)});
...
}

//getDisplayName
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
‘Component’
}
4涎永、組件參數(shù)
有時(shí)候,在調(diào)用高階組件時(shí)鹿响,需要傳入一些參數(shù)羡微。可以這樣實(shí)現(xiàn):

function HocFactoryFactory(...params){
// 可以做一些改變params的事
return function HocFactory(WrappedCompinent){
return class Hoc extends Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
}
使用方式如下:

HocFactoryFactory(params)(WrappedComponent);
或者:

@HocFactoryFactory(params)
class WrappedComponent extends Component{
...
}

作者:南風(fēng)知我意ZD
鏈接:http://www.reibang.com/p/9ab8a2e0403a
來源:簡(jiǎn)書
著作權(quán)歸作者所有惶我。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)妈倔,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绸贡,一起剝皮案震驚了整個(gè)濱河市盯蝴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌听怕,老刑警劉巖捧挺,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尿瞭,居然都是意外死亡闽烙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門声搁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黑竞,“玉大人,你說我怎么就攤上這事疏旨『芑辏” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵檐涝,是天一觀的道長(zhǎng)遏匆。 經(jīng)常有香客問我,道長(zhǎng)谁榜,這世上最難降的妖魔是什么幅聘? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮惰爬,結(jié)果婚禮上喊暖,老公的妹妹穿的比我還像新娘。我一直安慰自己撕瞧,他們只是感情好陵叽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布狞尔。 她就那樣靜靜地躺著,像睡著了一般巩掺。 火紅的嫁衣襯著肌膚如雪偏序。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天胖替,我揣著相機(jī)與錄音研儒,去河邊找鬼。 笑死独令,一個(gè)胖子當(dāng)著我的面吹牛端朵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播燃箭,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼冲呢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了招狸?” 一聲冷哼從身側(cè)響起敬拓,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裙戏,沒想到半個(gè)月后乘凸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡累榜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年营勤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片信柿。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冀偶,死狀恐怖醒第,靈堂內(nèi)的尸體忽然破棺而出渔嚷,到底是詐尸還是另有隱情,我是刑警寧澤稠曼,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布形病,位于F島的核電站,受9級(jí)特大地震影響霞幅,放射性物質(zhì)發(fā)生泄漏漠吻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一司恳、第九天 我趴在偏房一處隱蔽的房頂上張望途乃。 院中可真熱鬧,春花似錦扔傅、人聲如沸耍共。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)试读。三九已至杠纵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钩骇,已是汗流浹背比藻。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倘屹,地道東北人银亲。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纽匙,于是被迫代替她去往敵國(guó)和親群凶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 在多個(gè)不同的組件中需要用到相同的功能哄辣,其解決辦法有兩種:mixin和高階組件请梢。 1、mixin mixin一直被廣...
    南風(fēng)知我意ZD閱讀 8,131評(píng)論 1 5
  • 一力穗、mixin 什么是mixin:創(chuàng)造一種類似多重繼承的效果毅弧。事實(shí)上,說它是組合更為貼切当窗。 1.封裝mixin方法...
    南慕瑤閱讀 2,089評(píng)論 0 0
  • 什么是HOC? HOC(Higher-order component)是一種React 的進(jìn)階使用方法够坐,只要還是為...
    一瓣山河閱讀 22,955評(píng)論 0 6
  • 高階函數(shù) 高階函數(shù)是指滿足下列條件之一的函數(shù)1、接受一個(gè)或多個(gè)函數(shù)作為輸入崖面;2元咙、返回一個(gè)函數(shù); Mixins的缺點(diǎn)...
    LTA閱讀 675評(píng)論 0 1
  • 在多個(gè)不同的組件中需要用到相同的功能巫员,這個(gè)解決方法庶香,通常有Mixin和高階組件。Mixin方法例如: 但是由于Mi...
    小魚小蝦小海洋閱讀 815評(píng)論 0 3