這周重點學習React的組件間通信邮丰。
1. 組件間通信
React的組件間通信可分為四種:
- 父組件向子組件通信
- 子組件向父組件通信
- 跨級組件間通信
- 無嵌套關系組件間通信
1.1.父組件向子組件通信
因為React的數(shù)據(jù)流是單向流動的走搁,所以父組件向子組件通信也是最常見的通信方式焕檬。父組件通過props向子組件傳遞數(shù)據(jù)。
舉一個簡單的例子:
子組件中:
import React from 'react';
export default function Child({ name }) {
return <h1>Hello, {name}</h1>;
}
父組件中:
import React, { Component } from 'react';
import Child from './Child';
class Parent extends Component {
render() {
return (
<div>
<Child name="Sara" />
</div>
);
}
}
export default Parent;
上述例子中父組件向子組件傳遞了name屬性,在子組件中就可以用this.props.name的方式獲取從父組件傳來的name值焊傅。
1.2.子組件向父組件通信
子組件向父組件通信主要有兩種方法:
1.利用回調函數(shù):子組件更新組件狀態(tài)策幼,通過回調函數(shù)的方式傳遞給父組件。
2.利用自定義事件機制:這種方法更加通用廣泛左医,加入事件機制可以簡化組件API授帕。
舉個例子:
父組件中:定義回調函數(shù)
import React, { Component } from 'react';
import Child from './child.js';
class App extends Component {
constructor(props){
super(props);
this.state = {
msg: '父組件初始msg'
}
}
//父組件回調函數(shù)同木,更新state,進而更新父組件跛十。
callback=(msg)=>{
this.setState({msg});
}
render() {
return (
<div className="App">
<p>子組件傳值實驗: {this.state.msg}</p>
<Child callback={this.callback} ></Child>
</div>
);
}
}
export default App;
子組件中:調用父組件傳來的回調函數(shù)彤路,并把值通過回調函數(shù)傳遞給父組件。
import React from "react";
class Child extends React.Component{
constructor(props){
super(props);
this.state={
msg: '子組件msg傳值'
}
}
//通過props調用回調函數(shù)傳值
trans=()=>{
this.props.callback(this.state.msg);
}
render(){
return(
<div>
<button onClick={this.trans}>激發(fā)trans事件芥映,傳值給父組件</button>
</div>
)
}
}
export default Child;
上述例子中洲尊,在父組件中定義了一個回調函數(shù),可以實時更新傳入的參數(shù)為this.state中的值奈偏,然后在子組件中通過this.props.回調函數(shù)調用它坞嘀,并把自己的this.state中的值傳給這個回調函數(shù),從而達到子組件向父組件傳值的作用惊来。
1.3.跨級組件間通信
在React中丽涩,可以使用context來實現(xiàn)跨級父子組件間的通信。context提供了一種組件之間共享數(shù)據(jù)的方式裁蚁,可以避免數(shù)據(jù)在組件樹上逐層傳遞矢渊,但大部分情況下,不推薦使用context,因為它屬于全局變量枉证,容易引起結構混亂昆淡。如果真的需要使用,建議寫成高階組件來實現(xiàn)刽严。
注:
Context API的使用基于生產者消費者模式昂灵。
生產者一方,通過組件靜態(tài)屬性childContextTypes聲明舞萄,然后通過實例方法getChildContext()創(chuàng)建Context對象眨补。
消費者一方,通過組件靜態(tài)屬性contextTypes申請要用到的Context屬性倒脓,然后通過實例的context訪問Context的屬性撑螺。
舉個例子:
生產者:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import CppComponent from './Cpp.js';
class App extends Component {
constructor(props){
super(props);
}
//Context生產者,通過靜態(tài)屬性childContextTypes聲明提供給子組件的Context對象的屬性崎弃,
static childContextTypes = {
propA: PropTypes.string
}
//實例getChildContext方法甘晤,返回Context對象
getChildContext () {
return {
propA: 'propA'
}
}
render() {
return <BppComponent />
}
}
class BppComponent extends React.Component {
render () {
return <CppComponent />
}
}
export default App;
消費者:
import React, { Component } from 'react';
import PropTypes from 'prop-types'
/**
* 第三層有A(生產者)層直接傳遞數(shù)據(jù)到此層C(消費者)
*/
class CppComponent extends React.Component {
//子組件需要通過一個靜態(tài)屬性contextTypes聲明后,才可以訪問父組件Context對象的屬性
static contextTypes = {
propA: PropTypes.string
}
render () {
return(
<div>
<p>從生產者傳遞過來的屬性A:{this.context.propA}</p>
</div>
)
}
}
export default CppComponent;
上面的例子其實就運用了高階組件饲做,高階組件說通俗點就是一個函數(shù)线婚,它接受一個React組件作為參數(shù)輸入,然后輸出一個新的React組件盆均。
1.4.無嵌套關系組件間通信
非嵌套組件: 就是沒有任何包含關系的組件,包括兄弟組件以及不再同一個父級的非兄弟組件塞弊。
使用事件訂閱,即一個發(fā)布者,一個或多個訂閱者游沿。
這里我們借用Node.js Events 模塊的瀏覽器版實現(xiàn)饰抒。
- 安裝event
npm install event -save
- 新建Evt.js, 創(chuàng)建EventEmitter 實例
import { EventEmitter } from 'events';
export default new EventEmitter();
- 發(fā)布者通過emit事件觸發(fā)方法,發(fā)布訂閱消息給訂閱者,把 EventEmitter 實例輸出到各組件中使用诀黍;
訂閱者通過emitter.addListener(事件名稱,函數(shù)名)方法袋坑,進行事件監(jiān)聽(訂閱);通過emitter.removeListener(事件名稱,函數(shù)名)方法 眯勾,進行事件銷毀(取消訂閱)
舉例:
發(fā)布者:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Custom1 from './Custom1.js';
import Custom2 from './Custom2.js';
import emitter from './Evt.js';
class App extends Component {
constructor(){
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
//emit事件觸發(fā)方法,通過事件名稱找對應的事件處理函callCustom咒彤,將事件處理函數(shù)作為參數(shù)傳入
emitter.emit('callCustom', 'Hello 我來發(fā)消息了');
}
render() {
return(
<div>
<br/>
<button onClick = {this.handleClick}>點擊發(fā)布事件</button>
<Custom1 />
<Custom2 />
</div>
)
}
}
export default App;
訂閱者1 Custom1:
import React from 'react';
import ReactDOM from 'react-dom';
import emitter from './Evt.js';
class Custom1 extends React.Component {
constructor(){
super();
this.state= {
msg:''
}
}
componentDidMount () { //在組件掛載完成后聲明一個自定義事件
emitter.addListener('callCustom', (msg) => {
this.setState({
msg: 'Custom1收到消息--'+msg
});
})
}
componentWillUnmount () { //組件銷毀前移除事件監(jiān)聽
emitter.removeListener('callCustom', (msg) => {
this.setState({
msg: 'Custom1即將銷毀此消息--'+ msg
});
})
}
//訂閱者1消息顯示
render () {
return(<p style={{color:'red'}}>
{this.state.msg}
</p>)
}
}
export default Custom1;
訂閱者2 Custom2:
import React from 'react';
import ReactDOM from 'react-dom';
import emitter from './Evt.js';
class Custom2 extends React.Component {
constructor(){
super();
this.state= {
msg:''
}
}
componentDidMount () { //在組件掛載完成后聲明一個自定義事件
emitter.addListener('callCustom', (msg) => {
this.setState({
msg: 'Custom2收到消息--'+msg
})
})
}
componentWillUnmount () { //組件銷毀前移除事件監(jiān)聽
emitter.removeListener('callCustom', (msg) => {
this.setState({
msg: 'Custom2即將銷毀此消息--'+ msg
})
})
}
//訂閱者2消息顯示
render () {
return(<p style={{color:'blue'}}>{this.state.msg}</p>)
}
}
export default Custom2;
最終預覽效果如下圖: