原本我是沒想過總結這些東西的,會感受比較入門。可是以前同窗去騰訊面試問到了這個問題(react或vue的組件通訊),我幫他整理,順便寫demo的過程當中,會有一些新的體會,多總結仍是有利於進步的呀。javascript
另外本次的代碼都放在 github.com/sunyongjian… , 能夠 done 下來加深理解。html
parent組件傳給child組件,符合react的單向數據流理念,自上到下傳遞props。vue
// 父組件
class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
this.setState({
value: this.value,
})
}
render() {
return (
<div>
我是parent
<input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
<div>
<Child value={this.state.value} />
</div>
</div>
);
}
}複製代碼
// 子組件
class Child extends Component {
render() {
const { value } = this.props;
return (
<div> 我是Child,獲得傳下來的值:{value} </div>
);
}
}複製代碼
父組件作的就是定義好 state ,定義好事件函數,input onChange 的時候,去緩存 value 值,而後點擊 button 的時候,改變 state , 子組件只負責展現 value 。java
child 組件通知 parent 組件, 主要是依靠 parent 傳下來的 callback 函數執行,改變 parent 組件的狀態,或者把 child 本身的 state 通知 parent 。分兩種狀況:node
// parent
class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
setValue = value => {
this.setState({
value,
})
}
render() {
return (
<div> <div>我是parent, Value是:{this.state.value}</div> <Child setValue={this.setValue} /> </div> ); } }複製代碼
class Child extends Component {
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}
render() {
return (
<div> 我是Child <div className="card"> state 定義在 parent <input onChange={this.handleChange} /> <div className="button" onClick={this.handleClick}>通知</div> </div> </div> ); } }複製代碼
parent 組件把改變 state 的 setValue 函數傳給 child ,child 組件本身處理內部的狀態(這裏是表單的value值),當 child 組件分發消息的時候, 執行 parent 的 setValue 函數,從而改變了 parent 的 state,state發生變化, parent 組件執行 re-render 。react
// parent
class Parent extends Component {
onChange = value => {
console.log(value, '來自 child 的 value 變化');
}
render() {
return (
<div>
<div>我是parent
<Child onChange={this.onChange} />
</div>
);
}
}複製代碼
class Child extends Component {
constructor() {
super();
this.state = {
childValue: ''
}
}
childValChange = e => {
this.childVal = e.target.value;
}
childValDispatch = () => {
const { onChange } = this.props;
this.setState({
childValue: this.childVal,
}, () => { onChange(this.state.childValue) })
}
render() {
return (
<div> 我是Child <div className="card"> state 定義在 child <input onChange={this.childValChange} /> <div className="button" onClick={this.childValDispatch}>通知</div> </div> </div> ); } }複製代碼
有時候 state 是須要定義在 child 組件的,好比彈窗, CheckBox 這種開關性質的,邏輯是重複的,state 定義在組件內部更好維護, 複用性更好。可是 child 的 state 是須要告知個人 parent 組件的, 一樣仍是執行 parent 傳下來的 change 函數。git
有時候可能出現頁面中的某兩部分通訊,好比省市的級聯選擇,點擊 button 改變顏色等等,組件並非父子級,沒有嵌套關係的時候。這種時候一般是依賴共有的頂級 Container 處理或者第三方的狀態管理器。其實原理都是相通的,兄弟 A 的 value 發生變化,分發的時候把 value 值告訴一箇中間者 C ,C 會自動告知 B,實現 B 的自動render 。github
// container
class Container extends Component {
constructor() {
super();
this.state = {
value: '',
}
}
setValue = value => {
this.setState({
value,
})
}
render() {
return (
<div>
<A setValue={this.setValue}/>
<B value={this.state.value} />
</div>
);
}
}複製代碼
// 兄弟A
class A extends Component {
handleChange = (e) => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}
render() {
return (
<div className="card"> 我是Brother A, <input onChange={this.handleChange} /> <div className="button" onClick={this.handleClick}>通知</div> </div> ) } }複製代碼
// 兄弟B
const B = props => (
<div className="card"> 我是Brother B, value是: {props.value} </div>
);
export default B;複製代碼
組件 A 中的表單 value 值,告知了父級 Container 組件(經過 setValue 函數改變 state),組件 B 依賴於 Container 傳下來的 state,會作出同步更新。這裏的中間者是 Container。面試
上面的方式,若是嵌套少還能夠,若是嵌套特別多,好比一級導航欄下的二級導航欄下的某個按鈕,要改變頁面中 content 區域的 table 裏的某個列的值...他們同屬於一個 page 。這樣傳遞 props 就會很痛苦,每一層組件都要傳遞一次。redux
// 頂級公共組件
class Context extends Component {
constructor() {
super();
this.state = {
value: '',
};
}
setValue = value => {
this.setState({
value,
})
}
getChildContext() { // 必需
return {
value: this.state.value,
setValue: this.setValue,
};
}
render() {
return (
<div> <AParent /> <BParent /> </div>
);
}
}
// 必需
Context.childContextTypes = {
value: PropTypes.string,
setValue: PropTypes.func,
};複製代碼
// A 的 parent
class AParent extends Component {
render() {
return (
<div className="card"> <A /> </div>
);
}
}
// A
class A extends Component {
handleChange = (e) => {
this.value = e.target.value;
}
handleClick = () => {
const { setValue } = this.context;
setValue(this.value);
}
render() {
return (
<div> 我是parentA 下的 A, <input onChange={this.handleChange} /> <div className="button" onClick={this.handleClick}>通知</div> </div> ); } } // 必需 A.contextTypes = { setValue: PropTypes.func, };複製代碼
// B 的 parent
class BParent extends Component {
render() {
return (
<div className="card"> <B /> </div>
);
}
}
// B
class B extends Component {
render() {
return (
<div> 我是parentB 下的 B, value是: {this.context.value} </div>
);
}
}
B.contextTypes = {
value: PropTypes.string,
};複製代碼
組件 A 還是 消息的發送者,組件 B 是接收者, 中間者是 Context 公有 Container 組件。context是官方文檔的一個 API ,經過 getChildContext 函數定義 context 中的值,而且還要求 childContextTypes 是必需的。這樣屬於這個 Container 組件的子組件,經過 this.context
就能夠取到定義的值,而且起到跟 state 一樣的效果。中間者其實仍是 Container,只不過利用了上下文這樣的 API ,省去了 props 的傳遞。另外:這個功能是實驗性的,將來可能會有所改動。
這種一個地方發送消息,另外一個地方接收作出變化的需求,很容易想到的就是觀察者模式了。具體的實現會有不少種,這裏咱們本身寫了一個 EventEmitter 的類(其實就是仿照 node 中的 EventEmitter 類),若是不瞭解觀察者,能夠看個人另外一篇文章觀察者模式。
// 發佈訂閱類
class EventEmitter {
_event = {}
// on 函數用於綁定
on(eventName, handle) {
let listeners = this._event[eventName];
if(!listeners || !listeners.length) {
this._event[eventName] = [handle];
return;
}
listeners.push(handle);
}
// off 用於移除
off(eventName, handle) {
let listeners = this._event[eventName];
this._event[eventName] = listeners.filter(l => l !== handle);
}
// emit 用於分發消息
emit(eventName, ...args) {
const listeners = this._event[eventName];
if(listeners && listeners.length) {
for(const l of listeners) {
l(...args);
}
}
}
}
const event = new EventEmitter;
export { event };複製代碼
// Container
import A from './a';
import B from './b';
const Listener = () => {
return (
<div> <A /> <B /> </div>
);
};
export default Listener;複製代碼
// 兄弟組件 A
import { event } from './eventEmitter';
class A extends Component {
handleChange = e => {
this.value = e.target.value;
}
handleClick = () => {
event.emit('dispatch', this.value);
}
render() {
return (
<div className="card"> 我是Brother A, <input onChange={this.handleChange} /> <div className="button" onClick={this.handleClick}>通知</div> </div> ) } }複製代碼
// 兄弟組件 B
import { event } from './eventEmitter';
class B extends Component {
state = {
value: ''
}
componentDidMount() {
event.on('dispatch', this.valueChange);
}
componentWillUnmount() {
event.off('dispatch', this.valueChange);
}
valueChange = value => {
this.setState({
value,
})
}
render() {
return (
<div className="card"> 我是Brother B, value是: {this.state.value} </div>
);
}
}複製代碼
仍然是組件 A 用於分發消息,組件 B 去接收消息。這裏的中間者其實就是 event 對象。須要接收消息的 B 去訂閱 dispatch 事件,並把回調函數 valueChange 傳入,另外 B 定義了本身的 state,方便獲得 value 值的時候自動渲染。組件 A 其實就是把內部的表單 value 在點擊的時候分發,發佈事件,從而 B 中的 valueChange 執行,改變 state。這種方式比較方便,也更直觀,不須要藉助 Container 組件去實現,省去了不少邏輯。
Redux 或者 Mobx 是第三方的狀態管理器,是這裏咱們通訊的中間者。大型項目最直接的就是上庫... 更方便,更不容易出錯。 但其實小項目就沒什麼必要了。東西比較多,這裏再也不闡述它們的實現和作了什麼。
react 特殊的自上而下的單向數據流,和 state 的特性,造就以這樣的思想實現組件通訊。除去發佈訂閱和 Redux 等,其餘的都是 props 自上而下傳遞的理念,子組件須要的老是經過父組件傳遞下來的,關於 state 的定義,仍是看具體的應用場景了。
PS: 我的blog github.com/sunyongjian…歡迎Star