React组件间通信


这周重点学习React的组件间通信。

1. 组件间通信

React的组件间通信可分为四种:

  • 父组件向子组件通信
  • 子组件向父组件通信
  • 跨级组件间通信
  • 无嵌套关系组件间通信

1.1.父组件向子组件通信

因为React的数据流是单向流动的,所以父组件向子组件通信也是最常见的通信方式。父组件通过props向子组件传递数据。
举一个简单的例子:

子组件中:
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.利用回调函数:子组件更新组件状态,通过回调函数的方式传递给父组件。
2.利用自定义事件机制:这种方法更加通用广泛,加入事件机制可以简化组件API。
举个例子:

父组件中:定义回调函数
import React, { Component } from 'react';
import Child from './child.js'; 

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      msg: '父组件初始msg'
    }
  }

  //父组件回调函数,更新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;

子组件中:调用父组件传来的回调函数,并把值通过回调函数传递给父组件。
import React from "react";

class Child extends React.Component{
    constructor(props){
        super(props);
        this.state={
            msg: '子组件msg传值'
        }
    }
    //通过props调用回调函数传值
    trans=()=>{
        this.props.callback(this.state.msg);
    }
    render(){
        return(
            <div>
                <button onClick={this.trans}>激发trans事件,传值给父组件</button>
            </div>
        )
    }
}

export default Child;

上述例子中,在父组件中定义了一个回调函数,可以实时更新传入的参数为this.state中的值,然后在子组件中通过this.props.回调函数调用它,并把自己的this.state中的值传给这个回调函数,从而达到子组件向父组件传值的作用。

1.3.跨级组件间通信

在React中,可以使用context来实现跨级父子组件间的通信。context提供了一种组件之间共享数据的方式,可以避免数据在组件树上逐层传递,但大部分情况下,不推荐使用context,因为它属于全局变量,容易引起结构混乱。如果真的需要使用,建议写成高阶组件来实现。
注:


Context API的使用基于生产者消费者模式。
生产者一方,通过组件静态属性childContextTypes声明,然后通过实例方法getChildContext()创建Context对象。
消费者一方,通过组件静态属性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生产者,通过静态属性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(生产者)层直接传递数据到此层C(消费者)
 */
class CppComponent extends React.Component {
   //子组件需要通过一个静态属性contextTypes声明后,才可以访问父组件Context对象的属性
  static contextTypes = {
    propA: PropTypes.string
  }

  render () {
    return(
      <div>
        <p>从生产者传递过来的属性A:{this.context.propA}</p>
      </div>
    ) 
  }
}
export default CppComponent;


上面的例子其实就运用了高阶组件,高阶组件说通俗点就是一个函数,它接受一个React组件作为参数输入,然后输出一个新的React组件。

1.4.无嵌套关系组件间通信

非嵌套组件: 就是没有任何包含关系的组件,包括兄弟组件以及不再同一个父级的非兄弟组件。
使用事件订阅,即一个发布者,一个或多个订阅者。
这里我们借用Node.js Events 模块的浏览器版实现。

  1. 安装event
   npm install event -save
  1. 新建Evt.js, 创建EventEmitter 实例
   import { EventEmitter } from 'events'; 
   export default new EventEmitter();
  1. 发布者通过emit事件触发方法,发布订阅消息给订阅者,把 EventEmitter 实例输出到各组件中使用;
    订阅者通过emitter.addListener(事件名称,函数名)方法,进行事件监听(订阅);通过emitter.removeListener(事件名称,函数名)方法 ,进行事件销毁(取消订阅)
    举例:
  发布者:
  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事件触发方法,通过事件名称找对应的事件处理函callCustom,将事件处理函数作为参数传入
      emitter.emit('callCustom', 'Hello 我来发消息了');
    }

    render() {    
      return(
        <div>
          <br/>
          <button onClick = {this.handleClick}>点击发布事件</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 () { //组件销毁前移除事件监听
      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 () { //组件销毁前移除事件监听
    emitter.removeListener('callCustom', (msg) => {
      this.setState({
        msg: 'Custom2即将销毁此消息--'+ msg
      })
    })
  }

  //订阅者2消息显示
  render () {
    return(<p style={{color:'blue'}}>{this.state.msg}</p>) 
  }
}
export default Custom2;

最终预览效果如下图:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,321评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,559评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,442评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,835评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,581评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,922评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,931评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,096评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,639评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,374评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,591评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,104评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,789评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,196评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,524评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,322评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,554评论 2 379