組件間不同的嵌套關(guān)系,會(huì)導(dǎo)致不同的通信方式。常見的有:父組件向子組件通信、子組件向父組件通信、沒有嵌套關(guān)系的組件之間的通信,還有一種特殊形式:跨級(jí)組件通信。
1、父組件向子組件通信
這是React中最為常見的一種通信方式,父組件通過props向子組件傳遞需要的信息。示例如下:
class Child extends Component{
render(){
const { name } = this.props;
return <p>hello, { name }</p>;
}
}
class Parent extends Component{
render(){
return (
<div>
<Child name='Bob' />
</div>
);
}
}
2、子組件向父組件通信
子組件向父組件通信有兩種方式
- 利用回調(diào)函數(shù)
- 利用自定義事件機(jī)制
相較而言回調(diào)函數(shù)更為簡單,一般多用這種方式。其原理為:父組件將一個(gè)函數(shù)作為props傳遞給子組件,子組件調(diào)用這個(gè)回調(diào)函數(shù),將想要傳遞的信息,作為參數(shù),傳遞給父組件。示例如下:
class Parent extends Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
this.state={
visible: false。
}
}
handleClick(){
this.setState({
visible: true,
});
}
render(){
return (
<React.Fragment>
<div style={{display: this.state.visible ? 'block' : 'none'}}>
我是被隱藏的文字
</div>
<Child name='Bob' handleClick={this.handleClick} />
</React.Fragment>
);
}
}
class Child extends Component{
render(){
const { handleClick } = this.props;
return (<button onClick={handleClick}>點(diǎn)擊顯示隱藏的文字</button>);
}
}
3、跨級(jí)組件通信
跨級(jí)組件通信有兩種方法:(1)向?qū)訉觽鬟fprops。(2)利用context。
對(duì)于第一種方式,如果組件結(jié)構(gòu)較深,那么中間每一層都需要傳遞props,增加了復(fù)雜度且造成了冗余。而context 相當(dāng)于一個(gè)全局變量,是一個(gè)大容器,我們可以把要通信的內(nèi)容放在這個(gè)容器中,這樣一來,不管嵌套有多深,都可以隨意取用。使用 context 也很簡單,需要滿足兩個(gè)條件:
- 上級(jí)組件要聲明自己支持 context,并提供一個(gè)函數(shù)來返回相應(yīng)的 context 對(duì)象
- 子組件要聲明自己需要使用 context
示例:
export default class GrandParent extends Component{
// 父組件聲明自己支持 context
static childContextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
// 父組件提供一個(gè)函數(shù),用來返回相應(yīng)的 context 對(duì)象
getChildContext(){
return{
color:"red",
callback:this.callback.bind(this)
}
}
callback(msg){
console.log(msg)
}
render(){
return(
<div>
<Parent></Parent>
</div>
);
}
}
const Parent = (props) =>{
return(
<div>
<Child />
</div>
);
}
export default class Child extends Component{
// 子組件聲明自己需要使用 context
static contextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
render(){
const style = { color:this.context.color }
const handleConsoleLog = (msg) => {
return () => {
this.context.callback(msg);
}
}
return(
<div style = { style }>
Child組件
<button onClick = { handleConsoleLog("我胡漢三又回來了!") }>點(diǎn)擊我</button>
</div>
);
}
}
總結(jié):如果是父組件向子組件單向通信,可以使用變量,如果子組件想向父組件通信,同樣可以由父組件提供一個(gè)回調(diào)函數(shù),供子組件調(diào)用,回傳參數(shù)。
注意:如果組件中使用構(gòu)造函數(shù)(constructor),還需要在構(gòu)造函數(shù)中傳入第二個(gè)參數(shù) context,并在 super 調(diào)用父類構(gòu)造函數(shù)是傳入 context,否則會(huì)造成組件中無法使用 context。
constructor(props,context){
super(props,context);
}
Context就像全局變量一樣,而全局變量正是導(dǎo)致應(yīng)用走向混亂的罪魁禍?zhǔn)字?,給組件帶來了外部依賴的副作用,因此,不推薦使用context。其比較好的應(yīng)用場(chǎng)景是:真正意義上的全局信息且不會(huì)更改,如界面主題,用戶信息??傮w原則是:如果真的需要使用,建議寫成高階組件來實(shí)現(xiàn)。
補(bǔ)充:
1、context對(duì)象的更改。
我們不應(yīng)該也不能直接改變context對(duì)象中的屬性。要想改變 context 對(duì)象,只有讓其和父組件的 state 或者 props 進(jìn)行關(guān)聯(lián),在父組件的 state 或 props 變化時(shí),會(huì)自動(dòng)調(diào)用 getChildContext 方法,返回新的 context 對(duì)象,而后子組件進(jìn)行相應(yīng)的渲染。
constructor(props) {
super(props);
this.state = {
color:"red"
};
}
// 父組件聲明自己支持 context
static childContextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
// 父組件提供一個(gè)函數(shù),用來返回相應(yīng)的 context 對(duì)象
getChildContext(){
return{
color:this.state.color,
callback:this.callback.bind(this)
}
}
2、context同樣可以引用在無狀態(tài)組件上,只需將context作為第二個(gè)參數(shù)即可。
const Child = (props,context) => {
const style = { color:context.color }
const handleConsoleLog = (msg) => {
return () => {
context.callback(msg);
}
}
return(
<div style = { style }>
Child組件
<button onClick = { handleConsoleLog("我胡漢三又回來了!") }>點(diǎn)擊我</button>
</div>
);
}
Child.contextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
4、沒有嵌套關(guān)系的組件通信
沒有嵌套關(guān)系的組件通信包括兄弟組件通信和不在同一個(gè)父級(jí)中的非兄弟組件。同樣有兩種通信方式:
- 利用二者共同父組件的context對(duì)象進(jìn)行通信
- 利用自定義事件
第一種方法利用父組件中轉(zhuǎn),會(huì)增加子組件和父組件之間的耦合度,如果組件層次較深,找到二者公共父組件不太容易。一般使用自定義事件實(shí)現(xiàn)。
自定義事件需要借用node.js的events模塊:
安裝:npm install events --save
引入:import { EventEmitter } from "events";
export default new EventEmitter(); // 初始化實(shí)例并輸出給其他組件使用
export default class App extends Component{
render(){
return(
<div>
<Foo />
<Boo />
</div>
);
}
}
export default class Foo extends Component{
constructor(props) {
super(props);
this.state = {
msg:null,
};
}
componentDidMount(){
// 聲明一個(gè)自定義事件
this.eventEmitter = emitter.on("callMe",(msg)=>{
this.setState({
msg,
})
});
}
// 組件銷毀前移除事件監(jiān)聽
componentWillUnmount(){
emitter.removeListener(this.eventEmitter);
}
render(){
return(
<div>
{ this.state.msg }
我是非嵌套 1 號(hào)
</div>
);
}
}
export default class Boo extends Component{
render(){
const cb = (msg) => {
return () => {
// 觸發(fā)自定義事件。參一為事件名,后面為傳遞給事件的參數(shù),可多個(gè)。
emitter.emit("callMe","Hello")
}
}
return(
<div>
我是非嵌套 2 號(hào)
<button onClick = { cb("blue") }>點(diǎn)擊我</button>
</div>
);
}
}
5、總結(jié):
幾種通信情況下,最適用的方式:
- 父組件向子組件通信:使用 props
- 子組件向父組件通信:使用 props 回調(diào)
- 跨級(jí)組件間通信:使用 context 對(duì)象
- 非嵌套組件間通信:使用事件訂閱