组件间不同的嵌套关系,会导致不同的通信方式。常见的有:父组件向子组件通信、子组件向父组件通信、没有嵌套关系的组件之间的通信,还有一种特殊形式:跨级组件通信。
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、子组件向父组件通信
子组件向父组件通信有两种方式
- 利用回调函数
- 利用自定义事件机制
相较而言回调函数更为简单,一般多用这种方式。其原理为:父组件将一个函数作为props传递给子组件,子组件调用这个回调函数,将想要传递的信息,作为参数,传递给父组件。示例如下:
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}>点击显示隐藏的文字</button>);
}
}
3、跨级组件通信
跨级组件通信有两种方法:(1)向层层传递props。(2)利用context。
对于第一种方式,如果组件结构较深,那么中间每一层都需要传递props,增加了复杂度且造成了冗余。而context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。使用 context 也很简单,需要满足两个条件:
- 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
- 子组件要声明自己需要使用 context
示例:
export default class GrandParent extends Component{
// 父组件声明自己支持 context
static childContextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
// 父组件提供一个函数,用来返回相应的 context 对象
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("我胡汉三又回来了!") }>点击我</button>
</div>
);
}
}
总结:如果是父组件向子组件单向通信,可以使用变量,如果子组件想向父组件通信,同样可以由父组件提供一个回调函数,供子组件调用,回传参数。
注意:如果组件中使用构造函数(constructor),还需要在构造函数中传入第二个参数 context,并在 super 调用父类构造函数是传入 context,否则会造成组件中无法使用 context。
constructor(props,context){
super(props,context);
}
Context就像全局变量一样,而全局变量正是导致应用走向混乱的罪魁祸首之一,给组件带来了外部依赖的副作用,因此,不推荐使用context。其比较好的应用场景是:真正意义上的全局信息且不会更改,如界面主题,用户信息。总体原则是:如果真的需要使用,建议写成高阶组件来实现。
补充:
1、context对象的更改。
我们不应该也不能直接改变context对象中的属性。要想改变 context 对象,只有让其和父组件的 state 或者 props 进行关联,在父组件的 state 或 props 变化时,会自动调用 getChildContext 方法,返回新的 context 对象,而后子组件进行相应的渲染。
constructor(props) {
super(props);
this.state = {
color:"red"
};
}
// 父组件声明自己支持 context
static childContextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
// 父组件提供一个函数,用来返回相应的 context 对象
getChildContext(){
return{
color:this.state.color,
callback:this.callback.bind(this)
}
}
2、context同样可以引用在无状态组件上,只需将context作为第二个参数即可。
const Child = (props,context) => {
const style = { color:context.color }
const handleConsoleLog = (msg) => {
return () => {
context.callback(msg);
}
}
return(
<div style = { style }>
Child组件
<button onClick = { handleConsoleLog("我胡汉三又回来了!") }>点击我</button>
</div>
);
}
Child.contextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
4、没有嵌套关系的组件通信
没有嵌套关系的组件通信包括兄弟组件通信和不在同一个父级中的非兄弟组件。同样有两种通信方式:
- 利用二者共同父组件的context对象进行通信
- 利用自定义事件
第一种方法利用父组件中转,会增加子组件和父组件之间的耦合度,如果组件层次较深,找到二者公共父组件不太容易。一般使用自定义事件实现。
自定义事件需要借用node.js的events模块:
安装:npm install events --save
引入:import { EventEmitter } from "events";
export default new EventEmitter(); // 初始化实例并输出给其他组件使用
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(){
// 声明一个自定义事件
this.eventEmitter = emitter.on("callMe",(msg)=>{
this.setState({
msg,
})
});
}
// 组件销毁前移除事件监听
componentWillUnmount(){
emitter.removeListener(this.eventEmitter);
}
render(){
return(
<div>
{ this.state.msg }
我是非嵌套 1 号
</div>
);
}
}
export default class Boo extends Component{
render(){
const cb = (msg) => {
return () => {
// 触发自定义事件。参一为事件名,后面为传递给事件的参数,可多个。
emitter.emit("callMe","Hello")
}
}
return(
<div>
我是非嵌套 2 号
<button onClick = { cb("blue") }>点击我</button>
</div>
);
}
}
5、总结:
几种通信情况下,最适用的方式:
- 父组件向子组件通信:使用 props
- 子组件向父组件通信:使用 props 回调
- 跨级组件间通信:使用 context 对象
- 非嵌套组件间通信:使用事件订阅