与父节点通信的react.js自定义事件


84

我正在制作并监听正常的DOMCustomEvent以便与父节点通信:

在儿童中:

  var moveEvent = new CustomEvent('the-graph-group-move', { 
    detail: {
      nodes: this.props.nodes,
      x: deltaX,
      y: deltaY
    },
    bubbles: true
  });
  this.getDOMNode().dispatchEvent(moveEvent);

在父母中:

componentDidMount: function () {
  this.getDOMNode().addEventListener("the-graph-group-move", this.moveGroup);
},

这可行,但是有一个特定于React的方法会更好吗?


6
React的方式是通过props将回调显式传递给子级<Child onCustomEvent={this.handleCustomEvent} />。在React中不支持带有冒泡的自定义事件。
andreypopp 2014年

14
那么,将回调回调降低而不是使事件上升?似乎合理。
forresto 2014年

22
@forresto爱讽刺,+ 1
Dimitar Christoff

12
我不是在讽刺。
forresto 2014年

5
设置最佳实践是一回事,而另一种则是阻止可行的模式。这样形成鲜明对比的说,twitter.github.io/flight -它使用DOMEvents到气泡和繁殖合成事件。
Dimitar Christoff

Answers:


47

如上所述:

React的方式是通过props将回调显式传递给子级。在React中不支持带有冒泡的自定义事件。

反应式编程抽象是正交的:

利用观察者模式对交互式系统进行编程是困难且容易出错的,但仍然是许多生产环境中的实现标准。我们提出一种逐渐弃用观察者的方法,以支持反应式编程抽象。几个库层可帮助程序员将现有代码从回调平稳地迁移到更具声明性的编程模型。

React哲学基于Command模式:

在此处输入图片说明

参考文献


8
我对为什么React中不支持自定义合成事件感兴趣。仅仅是因为它们从未实现过,还是有一个有效的设计决定为什么没有实现?是否像goto语句一样认为事件有害?
Michael Bylstra 2015年

4
@MichaelBylstra自定义事件后,由于对皱起眉头一个经典的问题a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events。具有单向数据流命令模式是React的理念。
保罗·斯威特

2
考虑到组件通过React上下文与祖先注入的数据存储进行通信的事实(Redux,React Router等都使用上下文),说孩子通过上下文上的任何函数回调给祖先是完全假的惯用的React。使用“ action”对象调用Redux“ dispatch”与使用“ event”对象在上下文中调用“ event handler”之间没有实际区别。
安迪(Andy)

1
触发其他事件的长事件链在某些人使用Redux的方式中非常普遍(例如redux-saga疯狂)
Andy

我明白了,但我有一个问题。我们假设将构建一个UI-Kit库,该库旨在跨多种架构无缝地工作。某些组件可能需要调度自定义事件,因为我们无法假设未知的体系结构。React做出的严格假设导致了一个大问题。您可以在此处检查问题(custom-elements-everywhere.com):React是唯一无法正确处理自定义事件的库。也许,我错过了一些事情,任何建议将不胜感激。
7uc4,9

7

您可以编写一个简单的服务然后使用它

/** eventsService */
module.exports = {
  callbacks: {},

  /**
   * @param {string} eventName
   * @param {*} data
   */
  triggerEvent(eventName, data = null) {
    if (this.callbacks[eventName]) {
      Object.keys(this.callbacks[eventName]).forEach((id) => {
        this.callbacks[eventName][id](data);
      });
    }
  },

  /**
   * @param {string} eventName name of event
   * @param {string} id callback identifier
   * @param {Function} callback
   */
  listenEvent(eventName, id, callback) {
    this.callbacks[eventName][id] = callback;
  },

  /**
   * @param {string} eventName name of event
   * @param {string} id callback identifier
   */
  unlistenEvent(eventName, id) {
    delete this.callbacks[eventName][id];
  },
};

示例(与触发相同)

import eventsService from '../../../../services/events';
export default class FooterMenu extends Component {
  componentWillMount() {
    eventsService
      .listenEvent('cart', 'footer', this.cartUpdatedListener.bind(this));
  }

  componentWillUnmount() {
    eventsService
      .unlistenEvent('cart', 'footer');
  }

  cartUpdatedListener() {
    console.log('cart updated');
  }
}

4

您可以通过上下文传递的回调使事件冒泡:[CodePen]

import * as React from 'react';

const MyEventContext = React.createContext(() => {});

const MyEventBubbleContext = ({children, onMyEvent}) => {
  const bubbleEvent = React.useContext(MyEventContext);
  const handleMyEvent = React.useCallback((...args) => {
    // stop propagation if handler returns false
    if (onMyEvent(...args) !== false) {
      // bubble the event
      bubbleEvent(...args);
    }
  }, [onMyEvent]);
  return (
    <MyEventContext.Provider value={handleMyEvent}>
      {children}
    </MyEventContext.Provider>
  );
};

const MyComponent = () => (
  <MyEventBubbleContext onMyEvent={e => console.log('grandparent got event: ', e)}>
    <MyEventBubbleContext onMyEvent={e => console.log('parent got event: ', e)}>
      <MyEventContext.Consumer>
        {onMyEvent => <button onClick={onMyEvent}>Click me</button>}
      </MyEventContext.Consumer>
    </MyEventBubbleContext>
  </MyEventBubbleContext>
);

export default MyComponent;

3

我发现还有另外一个非常合理的方法,特别是如果从父母到孩子到孩子的钻洞已经很麻烦。他称其为简单沟通。这是链接:

https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/04-less-simple-communication.md


基本上是说“实现观察者模式”。我同意迈克尔·贝斯特拉(Michael Bylstra)关于OP的评论,他将“不太简单的交流”中提到的问题描述为“钻孔”……没有冒泡,传递回调不能处理1级以上的深层结构。
ericsoco 2015年

3

一个可能的解决方案,如果您绝对必须使用ReactJs应用程序中的Observer模式,则可以劫持一个正常事件。例如,如果您希望Delete键导致<div>标记为删除的a键,则可以<div>监听一个keydown事件,该事件将由customEvent调用。将keydown捕获到主体上,并customEvent在选定的上调度keydown事件<div>。分享以防万一。


3

中央存储[Redux]将状态分配给客户端,然后将状态“分派”回存储,这也类似于观察者模式。由于连接道具/事件路径的显式(脆弱的?)开销,使得发布/订阅的方法只会变得更糟。为了破解层次结构,React提供了上下文(提供者模式)或可观察到的令人讨厌的库。就像MobX引入了新的装饰器@observable或Vue引入了新的模板语法“ v-if”。无论如何,事件是DOM和javascript事件循环工作的主要方式,所以为什么不呢?我认为撒旦主义者做到了。大声笑


1

我知道这个问题到现在为止已经很老了,但是这个答案可能仍然会对某人有所帮助。我为React写了一个JSX杂注,其中添加了声明式自定义事件:jsx-native-events

基本上,您只是使用该onEvent<EventName>模式来监视事件。

<some-custom-element onEventSomeEvent={ callback }></some-custom-element>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.