react 中通過ref獲取高階(HOC)子組件實例的解決方案

今天寫react項目遇到一個父子組件通信的問題慕购。這是一個非常常規(guī)的問題了躬络,隨便搜一下就能得到解決方案尖奔。總體來說可以分為兩類:

  1. 子組件需要獲取父組件的信息穷当,這通過props就可以解決提茁;
  2. 父組件需要知道子組件的信息,這可以通過ref解決馁菜。

我們這里講的屬于后者茴扁,但是又有些特殊,特殊就在于子組件是個高階組件汪疮,比如使用@connect @withRouter包裹過的組件(其實大部分組件都會被這兩個包裹)峭火,具體示例如下:

@withRouter
export default class childComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return (<div>this is childComponent</div>)
  }
}
@withRouter
export default class parentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {}; 
  }
  render () {
    return <childComponent ref={(v) => { this.childCp = v; }}/>
  }
 }

上面的childComponent被withRouter包裹過一遍后,這時候你在parentComponent中通過ref獲取到的是并不會是childComponent智嚷,而是withRouter組件卖丸。這就比較尷尬了,我們大多數(shù)情況肯定是需要獲取自己寫的組件實例的盏道。有一點需要講明白:就是通過ref獲取到的不是childComponent稍浆,在原理上是對的,如果獲取到是childComponent組件那才是有問題的猜嘱,有悖倫理知道哇衅枫。

既然通過官方提供的ref無法獲取到我們想要的ref,那我們就來仔細(xì)想下ref獲取到的是啥朗伶?我們回歸到j(luò)avascript語言層面來看为鳄,那不就是組件中的this么。

@withRouter
export default class childComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {}; // 我們需要獲取到的就是這個this而已
  }
  render() {
    return (<div>this is childComponent</div>)
  }
}

知道我們需要獲取到的是啥了腕让,那就好辦了孤钦,我給childComponent傳一個prop專門來get這個this不就好了歧斟,比如使用getInstance

@withRouter
export default class childComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    const { getInstance } = props;
    if (typeof getInstance === 'function') {
      getInstance(this); // 在這里把this暴露給`parentComponent`
    }
  }
  render() {
    return (<div>this is childComponent</div>)
  }
}
@withRouter
export default class parentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {}; 
  }
  render () {
    return (
     <childComponent 
       ref={(withRouter) => { this.childCpWrapper = withRouter; }}  // 這里獲取的是`withRouter`組件,一般沒啥用偏形,這里寫出來只是為了對比
       getInstance={(childCp) => { this.childCp = childCp; }} // 這里通過`getInstance`傳一個回調(diào)函數(shù)接收`childComponent`實例即可
    />
    );
  }
 }

perfect ! 問題解決了静袖,這樣我不管你怎么用啥高階組件、用多少個高階組件包裹我們childComponent俊扭,我們都可以通過一個getInstance队橙,穿越千山萬水直接獲取childComponent實例。

當(dāng)然完美也是相對的萨惑,比如上面的方案中捐康,我們得在每一個childComponent的構(gòu)造函數(shù)中寫那段暴露this的代碼,麻煩庸蔼、費勁解总。這時候我們可以寫一個HOC專門來做這件事情,比如withRef:

// 只做一件事姐仅,把`WrappedComponent`回傳個`getInstance`(如果有的話)
export default (WrappedComponent) => {
  return class withRef extends Component {
    static displayName = `withRef(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
    render() {
      // 這里重新定義一個props的原因是:
      // 你直接去修改this.props.ref在react開發(fā)模式下會報錯花枫,不允許你去修改
      const props = {
        ...this.props,
      };
      // 在這里把getInstance賦值給ref,
      // 傳給`WrappedComponent`掏膏,這樣就getInstance能獲取到`WrappedComponent`實例
      // 感謝評論區(qū)的[yangshenghaha]同學(xué)的完善
      props.ref = (el)=>{
          this.props.getInstance && this.props.getInstance(el);this.props.ref && this.props.ref(el);
      }
      return (
        <WrappedComponent {...props} />
      );
    }
  };
};

然后我們可以這樣使用withRef

@withRouter
@withRef  // 這樣使用是不是方便多了劳翰,注意:這句必須寫在最接近`childComponent`的地方
export default class childComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return (<div>this is childComponent</div>)
  }
}
@withRouter
export default class parentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {}; 
  }
  render () {
    return (
     <childComponent 
       // 這里獲取的是`withRouter`組件,一般沒啥用馒疹,這里寫出來只是為了對比
       ref={(withRouter) => { this.childCpWrapper = withRouter; }}  
      // 這里通過`getInstance`傳一個回調(diào)函數(shù)接收`childComponent`實例即可
       getInstance={(childCp) => { this.childCp = childCp; }} 
    />
    );
  }
 }

通過這個小問題佳簸,對高階組件的理解是不是也更深了些,問題才是最好的教材啊颖变。最后說一點吧溺蕉,通信方式有很多(暴露給全局,EventEmmiter, Props, ref...)悼做,但是我建議:遇到通信問題還是優(yōu)先考慮redux action驅(qū)動疯特,數(shù)據(jù)優(yōu)先,能通過數(shù)據(jù)驅(qū)動解決的盡量用數(shù)據(jù)驅(qū)動肛走,畢竟這才是我們用react的重要原因吶

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漓雅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朽色,更是在濱河造成了極大的恐慌邻吞,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葫男,死亡現(xiàn)場離奇詭異抱冷,居然都是意外死亡,警方通過查閱死者的電腦和手機梢褐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門旺遮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赵讯,“玉大人,你說我怎么就攤上這事耿眉”咭恚” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵鸣剪,是天一觀的道長组底。 經(jīng)常有香客問我,道長筐骇,這世上最難降的妖魔是什么债鸡? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮铛纬,結(jié)果婚禮上厌均,老公的妹妹穿的比我還像新娘。我一直安慰自己饺鹃,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布间雀。 她就那樣靜靜地躺著悔详,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惹挟。 梳的紋絲不亂的頭發(fā)上茄螃,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音连锯,去河邊找鬼归苍。 笑死,一個胖子當(dāng)著我的面吹牛运怖,可吹牛的內(nèi)容都是我干的拼弃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摇展,長吁一口氣:“原來是場噩夢啊……” “哼吻氧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咏连,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤盯孙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祟滴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體振惰,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年垄懂,在試婚紗的時候發(fā)現(xiàn)自己被綠了骑晶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痛垛。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖透罢,靈堂內(nèi)的尸體忽然破棺而出榜晦,到底是詐尸還是另有隱情,我是刑警寧澤羽圃,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布乾胶,位于F島的核電站,受9級特大地震影響朽寞,放射性物質(zhì)發(fā)生泄漏识窿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一脑融、第九天 我趴在偏房一處隱蔽的房頂上張望喻频。 院中可真熱鬧,春花似錦肘迎、人聲如沸甥温。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姻蚓。三九已至,卻和暖如春匣沼,著一層夾襖步出監(jiān)牢的瞬間狰挡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工释涛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留加叁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓唇撬,卻偏偏與公主長得像它匕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窖认,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353