• React 组件间的通讯


      组件化开发应该是React核心功能之一,组件之间的通讯也是我们做React开发必要掌握的技能。接下来我们将从组件之间的关系来分解组件间如何传递数据。

    1.父组件向子组件传递数据

      通讯是单向的,数据必须是由一方传到另一方。在 React 中,父组件可以向子组件通过传 props 的方式,向子组件进行通讯。

    // 父组件 Parent
    class Parent extends Component{
      state = {
        msg: 'start'
      };
      componentDidMount() {
        setTimeout(() => {
          this.setState({
            msg: 'end'
          });
        }, 3000);
      }
      render() {
        return (<Child1 msg={this.state.msg}/>);
      }
    }
    // 子组件 Child1
    class Child1 extends Component{
      render() {
        return (<p>{this.props.msg}</p>);
      }
    }

      如果父组件与子组件之间不止一层嵌套,如 "Parent>Child1>Child1_1" 这样的关系,可通过 ... 运算符(Object 剩余和展开属性),将父组件的信息,以更简洁的方式传递给更深层级的子组件。通过这种方式,不用考虑性能的问题,通过 babel 转义后的 ... 运算符 性能和原生的一致,且上级组件 props 与 state 的改变,会导致组件本身及其子组件的生命周期改变。

    // 向 Child1_1 传递 Parent 组件的信息
    // Parent 的子组件 Child1
    class Child1 extends Component{
      render() {
        return (
        <div>   <p>{this.props.msg}</p>   <Child1_1 {...this.props}/>   </div>
       ); } } // Child1 的子组件 Child1_1 class Child1_1 extends Component{ render() { return (<p>{this.props.msg}</p>); } }

    2.子组件向父组件传递数据

      在上一个例子中,父组件可以通过传递 props 的方式,自顶而下向子组件进行通讯。而子组件向父组件传递数据,同样也需要父组件向子组件传递 props 进行通讯,只是父组件传递的是作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中。

    // 父组件 Parent
    class Parent extends Component{
      state = {
        msg: 'start'
      };
      transferMsg(msg) {
        this.setState({
          msg
        });
      }
      render() {
        return (
        <div> <p>child msg: {this.state.msg}</p> <Child1 transferMsg = {msg => this.transferMsg(msg)}/> </div>
       ); } } // 子组件 Child1 class Child1 extends Component{ componentDidMount() { setTimeout(() => { this.props.transferMsg('end'); }, 3000); } render() { return (<p>child1 component</p>); } }

      在上面的例子中,我们使用了 箭头函数,将父组件的 transferMsg 函数通过 props 传递给子组件,得益于箭头函数,保证子组件在调用 transferMsg 函数时,其内部 this 仍指向父组件。

      当然,对于嵌套比较深的子组件与父组件之间的数据传递,仍可使用 ... 运算符,将父组件的调用函数传递给子组件,具体方法和上面的例子类似。

    3.兄弟组件间传递数据

      对于没有直接关联关系的两个节点,就如 Child1 与 Child2 之间的关系,他们唯一的关联点,就是拥有相同的父组件(即 Parent > Chid1, Parent > Child2, Child1 向 Child2 传递数据)。参考之前介绍的两种关系的通讯方式,如果我们向由 Child1 向 Child2 传递数据,我们可以先通过 Child1 向 Parent 组件进行通讯,再由 Parent 向 Child2 组件进行通讯,所以有以下代码:

    (1)父组件Parent

    import React, { Component } from 'react';
    import Child1 from './components/Child1';
    import Child2 from './components/Child2';
    // 父组件 Parent
    class Parent extends Component {
      state = {
        msg: 'start'
      }
      componentDidUpdate() {
        console.log('Parent Update');
      }
      transferMsg(msg) {
        this.setState({
          msg
        });
      }
      render() {
        return (
          <div style={{textAlign:'center'}}>
            <Child1 transferMsg={msg=>this.transferMsg(msg)}/>
            <hr/>
            <Child2 msg={this.state.msg}/>
          </div>
        );
      }
    }
    export default Parent;

    (2)子组件Child1

    import React,{Component} from 'react';
    // 子组件 Child1
    class Child1 extends Component{
        state={
          child1_data:'end'
        }
        componentDidMount() {
          setTimeout(() => {
            this.props.transferMsg(this.state.child1_data);
          }, 3000);
        }
        componentDidUpdate(){
          console.log('Child1 Update');
        }
        render() {
          return (
            <div>
                <h3>Child1 component</h3>
                <h4>Child1组件数据:{this.state.child1_data}</h4>
            </div>
          );
        }
      }
    export default Child1;

    (3)子组件Child2

    import React,{Component} from 'react';
    // 子组件 Child2
    class Child2 extends Component{
        componentDidUpdate() {
            console.log('Child2 Update');
        }
        render(){
            return (
                <div>
                    <h3>Child2 component</h3>
                    <h4 style={{color:'red'}}>Child2组件数据:{this.props.msg}</h4>
                </div>
            );
        }
    }
    export default Child2;

    (4)运行结果

      从运行结果来看是达到了我们想要的目的。然而这个方法有至少两个问题:

      第一,一旦我们嵌套很多层的组件,难道我们真的要一层一层的通过this.props来完成数据传递吗?;

      第二,由于 Parent 的 state 发生变化,会触发 Parent 及从属于 Parent 的子组件的生命周期,所以我们在控制台中可以看到,在各个组件中的 componentDidUpdate 方法均被触发(有没有注意代码中的console.log呢?):

      有没有更好的解决方式来进行兄弟组件间的通讯,甚至是父子组件层级较深的通讯的呢?

    4.观察者模式

      观察者模式也叫 发布者-订阅者模式,发布者发布事件,订阅者监听事件并做出反应,对于上面的代码,我们引入一个小模块,使用观察者模式进行改造。在传统的前端解耦方面,观察者模式作为比较常见一种设计模式,大量使用在各种框架类库的设计当中。

      我们假定,存在一个"信号中心" (SignalCenter),某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。

    (1)兄弟组件之间的通讯

      ( Parent > Chid1, Parent > Child2, Child1 向 Child2 传递数据。)

    (a)父组件Parent

    import React, { Component } from 'react';
    import Child1 from './components/Child1';
    import Child2 from './components/Child2';
    // 父组件 Parent
    class Parent extends Component {
      componentDidUpdate(){
        console.log('Parent Update');
      }
      render() {
        return (
          <div style={{textAlign:'center'}}>
            <Child1 />
            <hr/>
            <Child2 />
          </div>
        );
      }
    }
    export default Parent;

    (b)子组件Child1

    import React,{Component} from 'react';
    // SignalCenter 类似于信号中心
    import SignalCenter from '../SignalCenter';
    // 子组件 Child1
    class Child1 extends Component{
        state={
          child1_data:'哎哟,不错哦!'
        }
        componentDidMount() {
          setTimeout(() => {
            // 发布msg事件
            SignalCenter.publish('msg',this.state.child1_data);
          }, 3000);
        }
        componentDidUpdate(){
          console.log('Child1 Update');
        }
        render() {
          return (
            <div>
                <h3>Child1 component</h3>
                <h4>Child1组件数据:{this.state.child1_data}</h4>
            </div>
          );
        }
      }
    export default Child1;

    (c)子组件Child2

    import React,{Component} from 'react';
    // SignalCenter 类似于信号中心
    import SignalCenter from '../SignalCenter';
    // 子组件 Child2
    class Child2 extends Component{
        state={
            msg:'start'
        }
        componentDidMount(){
            // 监听(订阅)msg事件
            SignalCenter.subscribe('msg',(msg)=>{
                this.setState({
                    msg
                })
            });
        }
        componentDidUpdate() {
            console.log('Child2 Update');
        }
        render(){
            return (
                <div>
                    <h3>Child2 component</h3>
                    <h4 style={{color:'red'}}>Child2组件数据:{this.state.msg}</h4>
                </div>
            );
        }
    }
    export default Child2;

    (d)运行结果

      我们在 Child2 组件的 componentDidMount 中订阅了 msg 事件,并在 Child1 componentDidMount 中,在 3s 后发布了 msg 事件,Child2 组件对 msg 事件做出相应,更新了自身的 state ,我们可以看到,由于在整个通讯过程中,只改变了 Child2 的 state,因而只有 Child2 触发了一次更新的生命周期。

    (2)SignalCenter(信号中心)

      神奇的 SignalCenter 究竟是怎样的一回事呢?

    const SignalCenter = {
        onObj: {},
        onceObj: {},
        subscribe(key, fn) {
            (!this.onObj[key]) && (this.onObj[key] = []);
            this.onObj[key].push(fn);
        },
        one(key, fn) {
            (!this.onceObj[key]) && (this.onceObj[key] = []);
            this.onceObj[key].push(fn);
        },
        off(key) {
            this.onObj[key] = [];
            this.onceObj[key] = [];
        },
        publish(...args) {
            if (!args.length) return;
            const [e, ...a] = args;
    
            const onCallbacks = this.onObj[e] || [];
            for (const fn of onCallbacks) {
                fn.apply(this, a);
            }
    
            const onceCallbacks = this.onceObj[e] || [];
            for (const [i, fn] of Object.entries(onceCallbacks)) {
                fn.apply(this, a);
                onceCallbacks[i] = undefined;
            }
            this.onceObj[e] = [];
        }
    }
    
    export default SignalCenter;
      在 SignalCenter 对象中,总共有 subscribe、one、off、publish这 4 个函数:
    • subscribe、one:subscribe 与 one 函数用于订阅者监听相应的事件,并将事件响应时的函数作为参数,subscribe 与 one 的唯一区别就是,使用 one 进行订阅的函数,只会触发一次,而 使用 subscribe 进行订阅的函数,每次事件发生相应时都会被触发。
    • publish:publish 用于发布者发布事件,将除第一参数(事件名)的其他参数,作为新的参数,触发使用 one 与 subscribe 进行订阅的函数。
    • off:用于解除所有订阅了某个事件的所有函数。

    (3)嵌套较深的组件间通讯

      如 `Parent > Child1 > Child1_1` 这种关系,现在我们来完成 Child1_1 向 Parent 传递数据。

    (a)父组件 Parent

    import React,{Component} from 'react';
    import Child1 from './components/Child1';
    // 导入`SignalCenter`文件
    import SignalCenter from './components/SignalCenter';
    // 父组件 Parent
    class Parent extends Component{
        state={
            msg:'等待Child1_1组件的数据...'
        }
        componentDidMount(){
            // 监听(订阅)msg事件
            SignalCenter.subscribe('msg',(msg)=>{
                this.setState({
                    msg
                })
            });
        }
        render(){
            return (
                <div style={{textAlign:'center'}}>
                    <h3>Parent component</h3>
                    <h4 style={{color:'red'}}>Child1_1 传递给 Parent 的数据:{this.state.msg}</h4>
                    <hr/>
                    <Child1 />
                </div>
            );
        }
    }
    
    export default Parent;

    (b)Parent 的子组件 Child1

    import React,{Component} from 'react';
    import Child1_1 from './Child1_1';
    // Parent 组件的子组件 Child1
    class Child1 extends Component{
        componentDidUpdate(){
          console.log('Child1 Update');
        }
        render() {
          return (
            <div>
              <h3>Child1 component</h3>
              <hr/>
              <Child1_1 />
            </div>
          );
        }
      }
    export
    default Child1;

    (c)Child1 的子组件 Child1_1

    import React,{Component} from 'react';
    // SignalCenter 类似于信号中心
    import SignalCenter from '../../SignalCenter';
    // Child1 组件的子组件 Child1_1
    class Child1_1 extends Component{
        state={
            child1_1_data:'哎哟,不错哦!'
        }
        componentDidMount() {
          setTimeout(() => {
            // 发布msg事件
            SignalCenter.publish('msg',this.state.child1_1_data);
          }, 3000);
        }
        componentDidUpdate(){
          console.log('Child1_1 Update');
        }
        render() {
          return (
            <div>
                <h3>Child1_1 component</h3>
                <h4 style={{color:'red'}}>Child1_1 组件数据:{this.state.child1_1_data}</h4>
            </div>
          );
        }
      }
      
    export default Child1_1;

    (d)运行结果

      我们在 Parent 组件的 componentDidMount 中订阅了 msg 事件,并在 Child1_1 componentDidMount 中,在 3s 后发布了 msg 事件,Parent 组件对 msg 事件做出相应,更新了自身的 state 。我们可以看到,由于在整个通讯过程中,不管组件嵌套有多深,我们通过这种方式只需要关心谁向谁传递数据(即哪个组件向哪个组件发布信息),省掉了由于嵌套太深导致不断通过this.props传递信息的麻烦。

      当然,在这里大家要注意一个问题就是在组件传递数据的过程中一定要”先订阅,再发布“,不然你的代码并不会达到你想要的效果。比如案例(1)兄弟组件之间的通讯,如果你把 Child1 组件当中的 setTimeout 给去掉,你会发现并没有将数据传递过去。因为违背了”先订阅,再发布“。

  • 相关阅读:
    萨卡斯指法
    香港保险
    数据分析,了解行业
    数据分析师
    黑盒测试方法
    web系统的常用功能测试
    linux-磁盘问题
    mysql连表查询
    mysql模糊查询
    MySQL 数据类型
  • 原文地址:https://www.cnblogs.com/onebox/p/9599234.html
Copyright © 2020-2023  润新知