使用stopPropagation()处理React事件委托

时间:2016-06-01 11:00:59

标签: javascript events reactjs

我在React有一个项目,应该可以放在任何网站上。我的想法是我托管一个javascript文件,人们放置一个具有特定ID的div,并且React在该div中呈现。

到目前为止,这是有效的,除了点击事件。这些均衡是handled at the top level。这一切都很好,但应该放置应用程序的其中一个站点,已经为画布外菜单实现了stopPropagation()。因此,事件无法正常运作。

我尝试在root-element处捕获所有事件,并手动调度它们:

this.refs.wrapper.addEventListener('click', (event) => {
    console.log(event);
    console.log(event.type);
    const evt = event || window.event;
    evt.preventDefault();
    evt.stopImmediatePropagation();
    if (evt.stopPropagation) evt.stopPropagation();
    if (evt.cancelBubble !== null) evt.cancelBubble = true;
    document.dispatchEvent(event);
});

这不起作用,因为事件已经被分派:

Uncaught InvalidStateError: Failed to execute 'dispatchEvent' on 'EventTarget': The event is already being dispatched.

解决此问题的正确方法是什么?不使用React的合成事件似乎不适合我..

4 个答案:

答案 0 :(得分:1)

论证'事件已经被派遣了。 您应该使用旧事件克隆新的事件对象。

var newevent = new event.constructor(event.type, event)

答案 1 :(得分:0)

Ther还没有解决方案。正如你所说的那样,React会监听DOM根目录上的事件,如果他们的event.target不在内部反应的安装节点,则过滤事件。
你可以尝试:
1.在Reract组件中重新发送新事件,但它也将在外部处理程序中停止 2.在Reract组件之外调度新事件,更高(最接近BODY)然后使用stopPropagation回调节点。但是event.target将指向节点,而不是在React的组件内部,并且您无法更改它,因为它是只读的。
也许在下一个版本中,他们会修复它。

答案 2 :(得分:0)

但是您可以在文档中侦听事件,不是吗?

假设整个应用的根组件名为app。然后,在componentDidMount内,您可以:

// when the main App component mounts - we'll add the event handlers ..
componentDidMount() {

    var appComponent = this;

    document.addEventListener('click', function(e) {

        var clickedElement = e.target;

        // Do something with the clickedElement - by ID or class .. 
        // You'll have reference to the top level component in `appComponent` .. 

    });
};

答案 3 :(得分:0)

正如您所说-React在顶级节点(文档)处理所有事件,并使用event.target属性确定哪个React组件与某个事件相关。因此,要使所有工作正常进行,您应该在文档节点上手动调度已停止的事件,并为此事件设置适当的“ target”属性。

有两个问题要解决:

  • 您无法触发已调度的事件。为了解决这个问题,您必须创建此事件的新副本。
  • 在某些节点上执行dispatchEvent()后,浏览器会自动将此事件的“ target”属性设置为触发该事件的节点。为了解决这个问题,您应该在dispatchEvent()之前设置适当的目标,并使用属性描述符将此属性设置为只读。

一般解决方案:

解决方案已在所有现代浏览器和IE9 +中经过测试

这是解决方案的源代码: https://jsbin.com/mezosac/1/edit?html,css,js,output。 (有时会挂起,因此,如果在预览区域中看不到UI元素,请单击右上角的“运行win js”按钮)

评论很好,所以在这里我不会描述所有这些东西,但是我将快速解释要点:

  1. 应该在事件停止后立即重新分配事件,以实现此目的,我扩展了事件的本机stopPropagationstopImmediatePropagation方法,以在停止传播之前调用我的redispatchEventForReact函数:< / p>

    if (event.stopPropagation) {
        const nativeStopPropagation = event.stopPropagation;
        event.stopPropagation = function fakeStopPropagation() {
            redispatchEventForReact();
            nativeStopPropagation.call(this);
        };
    }
    
    if (event.stopImmediatePropagation) {
        const nativeStopImmediatePropagation = event.stopImmediatePropagation;
        event.stopImmediatePropagation = function fakeStopImmediatePropagation() {
            redispatchEventForReact();
            nativeStopImmediatePropagation.call(this);
        };
    }
    

    还有另一种停止事件的可能性-将“ cancelBubble”属性设置为“ true”。如果您查看cancalBubble属性描述符-您将看到此属性确实是一对getter / setter,因此可以很容易地使用Object.defineProperty在setter中注入“ redispatchEventForReact”调用:

    if ('cancelBubble' in event) {
        const initialCancelBubbleDescriptor = getPropertyDescriptor(event, 'cancelBubble');
        Object.defineProperty(event, 'cancelBubble', {
            ...initialCancelBubbleDescriptor,
            set(value) {
                redispatchEventForReact();
                initialCancelBubbleDescriptor.set.call(this, value);
            }
        });
    }
    
  2. redispatchEventForReact函数:

    2.1在调度事件以进行反应之前,我们应删除自定义的stopPropagationstopImmediatePropagation方法(因为在react代码中,理论上某些组件可以调用e.stopPropagation,这将再次触发redispatchEventForReact,因此将导致无限循环):

    delete event.stopPropagation;
    delete event.stopImmediatePropagation;
    delete event.cancelBubble;
    

    2.2然后,我们应该复制此事件。在现代的浏览器中很容易做到,但是为IE11-编写了很多代码,因此我将此逻辑移到了单独的函数中(有关详细信息,请参见jsbin上随附的源代码):

    const newEvent = cloneDOMEvent(event);
    

    2.3由于浏览器在分派事件时会自动设置事件的“ target”属性,因此我们应将其设置为只读。重要的是-设置值和writeable = false在IE11中不起作用,因此我们必须使用getter和空setter:

    Object.defineProperty(newEvent, 'target', {
        enumerable: true,
        configurable: false,
        get() { return event.target; },
        set(val) {}
    });
    

    2.4最后,我们可以调度事件以进行响应:

    document.dispatchEvent(newEvent);
    
  3. 为确保在事件停止之前,事件中将注入用于响应的hack,我们应该在捕获阶段在根节点上侦听此事件并进行注入:

    const EVENTS_TO_REDISPATCH = ['click'];
    EVENTS_TO_REDISPATCH.forEach(eventToRedispatch => {
        document.addEventListener(eventToRedispatch, prepareEventToBeRedispatched, true);
    });