今天寫react項目遇到一個父子組件通信的問題慕购。這是一個非常常規(guī)的問題了躬络,隨便搜一下就能得到解決方案尖奔。總體來說可以分為兩類:
- 子組件需要獲取父組件的信息穷当,這通過
props
就可以解決提茁;- 父組件需要知道子組件的信息,這可以通過
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的重要原因吶