react组件通讯方式

1、父组件向子组件传值

父组件向子组件传值一般采用props属性传递

父组件:

import React from 'react'
import Child from './Child'

const dataList = [
  { id: '001', value: '张三' },
  { id: '002', value: '李四' }
]

const Parent = props => {
  return (
    
) } export default Parent

子组件:

import React from 'react'

const Child = props => {
  return (
    
      {
        props.dataList.map(item => 
  • {item.value}
  • ) }
    ) } export default Child

    image.png

    2、子组件向父组件传值

    子组件调用父组件传过来的回调函数来更改父组件的state

    父组件

    import React, { useState } from 'react'
    import Child from './Child'
    
    const Parent = props => {
      const [count, setCount] = useState(0)
    
      return (
        
      )
    }
    
    export default Parent

    子组件

    import React from 'react'
    
    const Child = props => {
      return (
        
          
          {props.count}
          
        
      )
    }
    
    export default Child

    image.png

    3、嵌套组件通讯(祖孙组件)

    context是一个全局变量,像是一个大容器,在任何地方都可以访问到,我们可以把要通信的信息放在context上,然后在其他组件中可以随意取到;
    但是React官方不建议使用大量context,尽管他可以减少逐层传递,但是当组件结构复杂的时候,我们并不知道context是从哪里传过来的;而且context是一个全局变量,全局变量正是导致应用走向混乱的罪魁祸首。

    context.js存放上下文

    import React from 'react'
    
    const MyContext = React.createContext()
    
    export default MyContext

    父组件

    import React, { useState } from 'react'
    import Child from './Child'
    import MyContext from './context'
    
    const Parent = props => {
      return (
        
          
        
      )
    }
    
    export default Parent

    子组件

    import React from 'react'
    import Son from './Son'
    
    const Child = props => {
      return (
        
      )
    }
    
    export default Child

    孙组件

    import React from 'react'
    import MyContext from './context'
    
    const Son = props => {
      return (
        
          {
            context => 
    {context}
    }
    ) } export default Son

    可以使用React.useContext钩子获取上下文对象绑定的值

    import React from 'react'
    import MyContext from './context'
    
    const Son = props => {
      const context = React.useContext(MyContext);
      return 
    {context}
    } export default Son

    image.png

    4、非嵌套组件通讯

    非嵌套组件: 就是没有任何包含关系的组件,包括兄弟组件以及不再同一个父级的非兄弟组件。
    使用事件订阅,即一个发布者,一个或多个订阅者。

    4.1、使用event模块或其他模块发布订阅

    4.1.1、event模块使用

    安装event模块

    cnpm i -S event

    新建event.js文件

    import { EventEmitter } from 'events'
    export default new EventEmitter()

    Brother组件

    import React from 'react'
    import emitter from './events'
    
    const Brother = props => {
      const handle = () => {
        emitter.emit('greet', 'sister, hello.')
      }
      return 
    }
    
    export default Brother

    Sister组件

    import React from 'react'
    import emitter from './events'
    
    const Sister = props => {
      const [msg, setMsg] = React.useState('');
      React.useEffect(() => {
        emitter.on('greet', msg => {setMsg(msg)})
        return () => {
          emitter.removeListener('greet', msg => {setMsg(msg)})
        };
      });
    
      return 
    {msg}
    } export default Sister

    image.png

    4.1.2、event模块原理

    看看event模块是怎样实现发布、订阅、注销事件的。event模块是基于观察者模式实现的,实例对象中有一个_event对象(Map结构)管理注册的事件,发布、订阅、注销也就是对该对象进行添加、删除、调用操作。

    初始化

    // 对象的构造函数
    function EventEmitter() {
      //这个就是用于调用Init函数
      // 也就是说说构造函数,仅仅是调用的下面的Init 
      EventEmitter.init.call(this);
    }
    // 导出函数,这个就是一个events模块的总体导出函数
    // 在上述的用法中,我们都是需要创建一个EventEmitter对象的
    module.exports = EventEmitter;
    
    // 用于兼容node 0.10.x
    EventEmitter.EventEmitter = EventEmitter;
    // 是否使用domain,默认用法是不使用。Domain其实是EventEmitter子类,
    // 一个单独的模块,这里不进一步分析,有兴趣可以看domain.js
    EventEmitter.usingDomains = false;
    // 同上,用于domain模块
    EventEmitter.prototype.domain = undefined;
    // 这个就是用于存储事件和回调的类map对象
    EventEmitter.prototype._events = undefined;
    // 
    EventEmitter.prototype._maxListeners = undefined;
    
    // By default EventEmitters will print a warning if more than 10 listeners // are added to it. This is a useful default which helps finding memory leaks.
    // 默认的最大的观察者的个数,默认为10, 如果超过,会有警告信息,以避免内存泄漏
    EventEmitter.defaultMaxListeners = 10;
    
    // 初始化,构造函数必须调用的部分
    EventEmitter.init = function() {
    
      // 下面是对domain的处理,不考虑
      this.domain = null;
      if (EventEmitter.usingDomains) {
        // if there is an active domain, then attach to it.
        domain = domain || require('domain');
        if (domain.active && !(this instanceof domain.Domain)) {
          this.domain = domain.active;
        }
      }
       // 创建了一个_events 的空对象,相当于创建了一个map
       if (!this._events || this._events === Object.getPrototypeOf(this)._events)
        this._events = {};
      // 用于保存当前最大监听数目,后面会用到
      this._maxListeners = this._maxListeners || undefined;
    };

    事件订阅

    EventEmitter.prototype.addListener = function addListener(type, listener) {
      var m;
       // 首先查看的是否listener为一个函数,确保是可以被执行的会掉函数
      if (!util.isFunction(listener))
        throw TypeError('listener must be a function');
    
      //确保_events对象已经被创建
      if (!this._events)
        this._events = {};
    
      // To avoid recursion in the case that type === "newListener"! Before
      // adding it to the listeners, first emit "newListener".
      // 从注释上,这个是防止递归调用的,这里只有定义了newListener后,才会发送事件
      // newListener,从本函数的代码,可以看出,newListener没有被定义,可以忽视
      if (this._events.newListener)
        this.emit('newListener', type,
                  util.isFunction(listener.listener) ?
                  listener.listener : listener);
      // 查看该type(事件)是否存在,
      if (!this._events[type])
        // Optimize the case of one listener. Don't need the extra array object.
        // 如果不存在,直接存入就可以
        this._events[type] = listener;
      else if (util.isObject(this._events[type]))
        // If we've already got an array, just append.
         // 如果存在,并且是array,直接push
        this._events[type].push(listener);
      else
        // Adding the second element, need to change to array.
        // 如果不是array,生成一个新的array
        this._events[type] = [this._events[type], listener];
    
      // Check for listener leak
      // 下面的代码就是查看是否监听者超过了最大的数目,这个是关于默认的数目的
      if (util.isObject(this._events[type]) && !this._events[type].warned) {
        var m;
        if (!util.isUndefined(this._maxListeners)) {
          m = this._maxListeners;
        } else {
          m = EventEmitter.defaultMaxListeners;
        }
         //如果数目过大,直接给出console.error
        if (m && m > 0 && this._events[type].length > m) {
          this._events[type].warned = true;
          console.error('(node) warning: possible EventEmitter memory ' +
                        'leak detected. %d %s listeners added. ' +
                        'Use emitter.setMaxListeners() to increase limit.',
                        this._events[type].length, type);
          console.trace();
        }
      }
    
      return this;
    };
    
    // 这里就是说on 和addListener是相互一样的,别名。
    EventEmitter.prototype.on = EventEmitter.prototype.addListener;
    // 查看once,事件触发一次回调函数,就删除,相当于调用了removeListener
    EventEmitter.prototype.once = function once(type, listener) {
      // 依然一样是确保参数为函数,可以回调
      if (!util.isFunction(listener))
        throw TypeError('listener must be a function');
      // 回调函数是否被fire了 
      var fired = false;
      // 帮助函数
      function g() {
        // 删除回调函数,请注意,一旦被fired掉,就删除
        this.removeListener(type, g);
        // 检查是否被fired过了,我想,这个可能是防止重复增加的case,也就是多次调用了once的  情况
        if (!fired) {
          fired = true;
          // 执行回调函数
          listener.apply(this, arguments);
        }
      }
      // 增加一个listener属性为回调函数
      g.listener = listener;
      // 增加具体的回调函数,该回调函数变成了帮助函数g,而不是listener
      this.on(type, g);
      // 返回整个对象
      return this;
    };

    事件发布

    EventEmitter.prototype.emit = function emit(type) {
      var er, handler, len, args, i, listeners;
      // 还是检查当前存储的events队列是否为空
      if (!this._events)
        this._events = {};
    
      // If there is no 'error' event listener then throw.
      // 这里需要对error进行特殊处理,如果没有error事件的监听者,直接会抛出error的错误,
      // 所有对error的时间的处理要特别注意。
      // 也就是所emit(‘error’)有抛出异常的功能,这个是文档中没有的
      if (type === 'error' && !this._events.error) {
        er = arguments[1];
        // 这个是具体domain的处理
        if (this.domain) {
          if (!er)
            er = new Error('Uncaught, unspecified "error" event.');
          er.domainEmitter = this;
          er.domain = this.domain;
          er.domainThrown = false;
          this.domain.emit('error', er);
        } else if (er instanceof Error) {
          // 直接抛出异常,如果emit一个参数Error的实例
          throw er; // Unhandled 'error' event
        } else {
           抛出生成的异常
          throw Error('Uncaught, unspecified "error" event.');
        }
        return false;
      }
      // 找到对应事件的回调函数
      handler = this._events[type];
     // 如果回调函数没有定义,说明不存在,直接返回false
      if (util.isUndefined(handler))
        return false;
      // 如果是domain的使用,直接进入domain内处理
      if (this.domain && this !== process)
        this.domain.enter();
      //处理是单个的函数的情况。
      if (util.isFunction(handler)) {
        // 检查参数的情况,注意,至少有事件这一个argument。所以先处理1,2,3
        switch (arguments.length) {
          // fast cases
          case 1:
            // 当回调函数没有参数时候
            handler.call(this);
            break;
          case 2:
            // 当回调函数有一个参数时候
            handler.call(this, arguments[1]);
            break;
          case 3:
            // 当回调函数有二个参数时候
            handler.call(this, arguments[1], arguments[2]);
            break;
          // slower
          default:
            // 当回调函数三个或者以上参数时候,就会做一个copy,然后再调用
            // 所以,这里我们可以特别注意的地方是,为了效率考虑,回调函数最好不要用3个或者3个以上的函数参数
            len = arguments.length;
            args = new Array(len - 1);
            for (i = 1; i < len; i++)
              args[i - 1] = arguments[i];
            handler.apply(this, args);
        }
      } // 这里处理回调函数为一个数组的情况
      else if (util.isObject(handler)) {
        // 这里直接生成一个参数的拷贝
        len = arguments.length;
        args = new Array(len - 1);
        for (i = 1; i < len; i++)
          args[i - 1] = arguments[i];
        // 生成一个回调函数的新队列
        listeners = handler.slice();
        len = listeners.length;
        // 逐个调用回调函数
        for (i = 0; i < len; i++)
          listeners[i].apply(this, args);
      }
      // 处理domain 的情况,直接退出
      if (this.domain && this !== process)
        this.domain.exit();
      //返回true,有回调函数处理的情况 
      return true;
    };

    事件注销

    EventEmitter.prototype.removeListener =
        function removeListener(type, listener) {
      var list, position, length, i;
    
      //依然是检查listener是否为函数
      if (!util.isFunction(listener))
        throw TypeError('listener must be a function');
    
      // 确保_events是否为空,以及事件存在在对象中
      if (!this._events || !this._events[type])
        return this;
      // 等到回调函数的value
      list = this._events[type];
      length = list.length;
      position = -1;
      // 如果当前的value和要删除的回调是相等的,包含once的内容
      if (list === listener ||
          (util.isFunction(list.listener) && list.listener === listener)) {
        // 直接删除
        delete this._events[type];
        // 发送事件
        if (this._events.removeListener)
          this.emit('removeListener', type, listener);
      } // 如果是队列,直接出来
      else if (util.isObject(list)) {
        for (i = length; i-- > 0;) {
          if (list[i] === listener ||
              (list[i].listener && list[i].listener === listener)) {
            position = i;
            break;
          }
        }
         // 如果查找不到,直接返回
        if (position < 0)
          return this;
         // 如果长度为1,说明可以删除
        if (list.length === 1) {
          list.length = 0;
          delete this._events[type];
        } else {
           // 直接在数组中删除该回调
          list.splice(position, 1);
        }
         // fire removeListener事件
        if (this._events.removeListener)
          this.emit('removeListener', type, listener);
      }
    
      return this;
    };
    
    // 删除与单一事件相关的所有回调函数
    EventEmitter.prototype.removeAllListeners =
        function removeAllListeners(type) {
      var key, listeners;
     // 确保_events不为空
      if (!this._events)
        return this;
    
      // not listening for removeListener, no need to emit
      // 查看当前是否有removeListener的监听者,从本模块看,没有赋值,所以一般情况下,都是
      // 直接删除回调然后返回
      if (!this._events.removeListener) {
        if (arguments.length === 0)
          this._events = {};
        else if (this._events[type])
          delete this._events[type];
        return this;
      }
    
      // emit removeListener for all listeners on all events
      // 处理没有参数的情况,在没有参数的情况下,就是删除所有事件的回调
      // 相当于清空。 
      if (arguments.length === 0) {
        for (key in this._events) {
          // 注意有特殊情况是,不删除removeListener的回调
          if (key === 'removeListener') continue;
          this.removeAllListeners(key);
        }
        this.removeAllListeners('removeListener');
        this._events = {};
        return this;
      }
      // 处理有具体参数的情况, 找到具体回调函数
      listeners = this._events[type];
    
      // 如果回调是单个函数,直接删除就好
      if (util.isFunction(listeners)) {
        this.removeListener(type, listeners);
      } else if (Array.isArray(listeners)) {
        // LIFO order
        // 处理回调函数是一个数组的情况,从后往前一个一个删除。
        while (listeners.length)
          this.removeListener(type, listeners[listeners.length - 1]);
      }
      // 清空事件对于的回调函数对象
      delete this._events[type];
    
      return this;
    };

    4.2、使用react-redux进行全局状态管理

    这里仅仅以redux为例进行状态管理,使用其他状态管理工具也可以,比如mobx。

    Action

    export const setMsg = msg => ({
      type: 'SET_MSG',
      msg
    })

    reducer

    const reducer = (state, action) => {
      switch (action.type) {
        case 'SET_MSG':
          return {
            ...state,
            msg: action.msg
          }
        default:
          return state
      }
    }
    
    export default reducer

    App

    import React from 'react'
    import { render } from 'react-dom'
    import { createStore } from 'redux'
    import { Provider } from 'react-redux'
    import reducer from '../reducers'
    import BrotherContaniner from '../containers/BrotherContainer'
    import SisterContainer from '../containers/SisterContainer'
    
    const store = createStore(reducer, {msg: ''})
    
    render(
      
        
        
      ,
      document.getElementById('app')
    )

    BrotherContainer

    import { connect } from 'react-redux'
    import Brother from '../module/Brother'
    import { setMsg } from '../acions'
    
    const mapDispatchToProps = dispatch => ({
      setMsg: msg => dispatch(setMsg(msg))
    })
    
    export default connect(null, mapDispatchToProps)(Brother)

    Brother

    import React from 'react'
    
    const Brother = props => {
      const handle = () => {
        props.setMsg('hello, sister.')
      }
      return 
    }
    
    export default Brother

    SisterContainer

    import { connect } from 'react-redux'
    import Sister from '../module/Sister'
    
    const mapStateToProps = state => ({
      msg: state.msg
    })
    
    export default connect(mapStateToProps, null)(Sister)

    Sister

    import React from 'react'
    
    const Sister = props => {
      return 
    {props.msg}
    } export default Sister

    image.png

    参考:
    https://blog.csdn.net/xingfuz...
    https://www.cnblogs.com/qinne...

    你可能感兴趣的:(react.js,组件通信)