React和jQuery事件处理程序以错误的顺序触发

Pio*_*rak 13 javascript-events reactjs

我正在创建出现在屏幕顶部的菜单.当用户点击其中一个菜单项div时会出现很多链接.我不能通过点击另一个菜单项(没问题,已经实现它)来隐藏这个div,而且还可以点击其他任何地方然后点击这个div和menuitems.

我听说过两个解决方案:

  • 显示菜单后面的隐形div并覆盖整个屏幕并为其添加单击处理程序.
  • 将事件处理程序附加到document.body.

第一个对我不好,因为该div将覆盖页面上的链接,我希望它们即使在点击menuitem后也可以点击,并且它出现了相应的div.所以我尝试了第二次灵魂.但问题是在我的组件的处理程序之前触发了body上的jquery click处理程序.我不知道如何让它首先调用我的组件处理程序,然后阻止事件传播.

这是代码和js小提琴:

/** @jsx React.DOM */
var Menu = React.createClass({
    click: function(e) {
        console.log('component handled click - should be called before jquery one and prevent jquery handler from running at all');
        e.stopPropagation();
    },
    render: function(){
    console.log("component renders");
        return (
            <div>
                <div>| MenuItem1 | MenuItem2 | MenuItem 3 |</div>
                <br />
                <div className="drop">
                    MenuItem 1 (This div appears when you click menu item)
                    <ul>
                        <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li>
                        <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li>
                    </ul>
                </div>
            </div>
        );
    }
});

React.renderComponent(<Menu />, document.getElementById("menu"));

$(document).ready(function() {
    console.log('document is ready');
    $("body").click(function() {
        console.log('jquery handled click - should not happen before component handled click');
    });
});
Run Code Online (Sandbox Code Playgroud)

http://jsfiddle.net/psonsx6j/

在运行它之前启动控制台以解释问题

Pio*_*rak 16

所以我解决了.使用jQuery向文档添加事件处理程序时,将首先触发此事件处理程序.所以你无法捕捉事件并阻止它的传播.但是当你使用document.addEventlistener附加处理程序时,这个处理程序最后被调用,你有机会反应:)点击React组件.

我修改了Mike的代码以使其工作(http://jsfiddle.net/s8hmstzz/):

/** @jsx React.DOM */
var Menu = React.createClass({
    getInitialState: function() {
        return {
            show: true
        }
    },
    componentDidMount: function() {
        // when attaching with jquery this event handler is run before other handlers
        //$('body').bind('click', this.bodyClickHandler);
        document.addEventListener('click', this.bodyClickHandler);
    },
    componentWillUnmount: function() {
        //$('body').unbind('click', this.bodyClickHandler);
        document.removeEventListener('click', this.bodyClickHandler);
    },
    anchorClickHandler: function(e) {
        console.log('click on anchor - doing some stuff and then stopping propation');
        e.stopPropagation();
        e.nativeEvent.stopImmediatePropagation();
    },
    bodyClickHandler: function(e) {
        console.log('bodyclickhandler');
        this.setState({show: false})
    },
    clickHandler: function(e) {
        // react to click on menu item
    },
    preventEventBubbling: function(e) {
        console.log('click on div - stopping propagation');
        e.stopPropagation();
        e.nativeEvent.stopImmediatePropagation();
    },
    render: function() {
        return (
            <div>
                <a href="#" onClick={this.anchorClickHandler}>Link - will stop propagation</a>
                <div id="menudrop" onClick={this.preventEventBubbling} style={{display: this.state.show ? 'block' : 'none'}}>
                    <ul>
                        <li onClick={this.clickHandler}>Menu Item</li>
                    </ul>
                </div>
            </div>
        )
    }
});

React.renderComponent(<Menu />, document.getElementById('menu'));
Run Code Online (Sandbox Code Playgroud)

启动控制台,然后单击div,anchor和body以查看其工作原理.我不知道为什么使用addEventListener而不是jQuery更改事件处理程序的触发顺序.

  • 大声笑.-1解决问题的答案? (2认同)

Mik*_*ver 9

请参阅上面的评论.

基本上onClick,当结合使用jquery(或普通的JS事件)时,你不能依赖于能够像React 一样停止传播事件.

您可以做的是检查您的$('body').click()功能,即您没有点击菜单(因此单击其中的项目后它不会关闭您的菜单).另外值得注意的是,你应该在反应组件中绑定这个主体事件,这样你就可以通过安装组件来干净地添加/删除它,而不会让组件本身不知道的任何东西显着改变组件的功能. .

所以你做的是这样的(nb:未经测试的代码,但基于我对同一问题的组件)

var Menu = React.createClass({
    getInitialState: function() {
        return {
            show: true
        }
    },
    componentDidMount: function() {
        $('body').bind('click', this.bodyClickHandler);
    },
    componentWillUnmount: function() {
        $('body').unbind('click', this.bodyClickHandler);
    },
    bodyClickHandler: function(e) {
        if ($(e.target).parents('div').get(0) !== this.getDOMNode()) {
            this.setState({
                show: false
            })
        }
    },
    clickHandler: function(e) {
        // react to click on menu item
    },
    render: function() {
        return (
            <div style={{display: this.state.show ? 'block' : 'none'}}>
                <ul>
                    <li onClick={this.clickHandler}>Menu Item</li>
                </ul>
            </div>
        )
    }
});
Run Code Online (Sandbox Code Playgroud)

因此,在上面的代码中,我们假设您希望在组件安装到dom后立即显示菜单下拉列表.这可能不是这种情况,因为您可能希望安装组件,然后根据悬停显示/隐藏它.在这种情况下,只需要在显示菜单的任何地方将$('body').bind()事件绑定从componentDidMount回调移动到回调.$('body').unbind()应该在组件被隐藏时移动到.然后你会得到一个完美的情况,当菜单显示时,我们绑定一个等待点击的身体事件,并在它被隐藏和/或卸载时取消绑定.

发生的是在点击菜单bodyClickHandler之前调用clickHandler的,但是bodyClickHandler如果你点击的父树不包含div我们组件的根,则只隐藏菜单(this.getDOMNode()render()返回时给出根节点的html元素).

因此,在组件外部单击将关闭它,单击组件内部将不执行任何操作,单击<li>将触发clickHandler.一切按预期工作.

希望这可以帮助.