6

1、父组件向子组件传值

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

父组件:

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

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

const Parent = props => {
  return (
    <ul>
      <Child dataList={dataList}></Child>
    </ul>
  )
}

export default Parent

子组件:

import React from 'react'

const Child = props => {
  return (
    <React.Fragment>
      {
        props.dataList.map(item => <li key={item.id}>{item.value}</li>)
      }
    </React.Fragment>
  )
}

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 (
    <Child count={count} setCount={setCount}></Child>
  )
}

export default Parent

子组件

import React from 'react'

const Child = props => {
  return (
    <React.Fragment>
      <button onClick={() => {props.setCount(props.count-1)}}>-</button>
      <span>{props.count}</span>
      <button onClick={() => {props.setCount(props.count+1)}}>+</button>
    </React.Fragment>
  )
}

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 (
    <MyContext.Provider value="world">
      <Child />
    </MyContext.Provider>
  )
}

export default Parent

子组件

import React from 'react'
import Son from './Son'

const Child = props => {
  return (
    <Son />
  )
}

export default Child

孙组件

import React from 'react'
import MyContext from './context'

const Son = props => {
  return (
    <MyContext.Consumer>
      {
        context => <div>{context}</div>
      }
    </MyContext.Consumer>
  )
}

export default Son

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

import React from 'react'
import MyContext from './context'

const Son = props => {
  const context = React.useContext(MyContext);
  return <div>{context}</div>
}

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 <button onClick={handle}>向sister问候</button>
}

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 <div>{msg}</div>
}

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(
  <Provider store={store}>
    <BrotherContaniner />
    <SisterContainer />
  </Provider>,
  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 <button onClick={handle}>向sister问候</button>
}

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 <div>{props.msg}</div>
}

export default Sister

image.png

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


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。