动态地向React.DOM元素添加事件处理程序

时间:2014-12-30 15:13:19

标签: reactjs

我正在使用RadioButtonGroup组件,它类似于无线电输入,但带有按钮:

enter image description here

如果使用这个组件很容易就会这样:

var SelectThing = React.createClass({
    render: function render() {
        // I would not like to add onClick handler to every button element
        // outside the RadioButtonGroup component
        return (
            <RadioButtonGroup onChange={this._onActiveChange}>
                <button>Thing 1</button>
                <button>Thing 2</button>
                <button>Thing 3</button>
            </RadioButtonGroup>
        )
    },

    _onActiveChange: function _onActiveChange(index) {
        console.log('You clicked button with index:', index);
    }
});

实际问题:如何使用React实现最优雅的效果?(我发现another similar question但它并没有完全回答这个问题。)

我的第一个直觉是添加和删除组件内的onClick处理程序,以从组件的用户中删除锅炉板代码。我想到的另一个选择是给出例如<p>元素而不是按钮元素,并将它们放在将在RadioButtonGroup组件内创建的按钮元素中。我不喜欢后者那么多,因为与传递按钮相比,它在语义上没有那么大的意义。

这里的(显然不起作用)组件现在看起来像:

// Radio input with buttons
// http://getbootstrap.com/javascript/#buttons-checkbox-radio
var RadioButtonGroup = React.createClass({
    getInitialState: function getInitialState() {
        return {
            active: this.props.active || 0
        };
    },

    componentWillMount: function componentWillMount() {
        var buttons = this.props.children;
        buttons[this.props.active].className += ' active';

        var self = this;
        buttons.forEach(function(button, index) {
            // How to dynamically set onclick handler for virtual dom
            // element created inside JSX?
            button.addEventListener('onClick', function(event) {
                self._onAnyButtonClick(index, event);
            }
        });
    },

    componentWillUnmount: function componentWillUnmount() {
        var buttons = this.props.children;

        buttons.forEach(function(button, index) {
            button.removeEventListener('onClick');
        });  
    },

    render: function render() {
        return (
            <div className="radio-button-group">
                {buttons}
            </div>
        )
    },

    _onAnyButtonClick: function _onAnyButtonClick(index, event) {
        this.setState({
            active: index
        });

        this.props.onChange(index);
    }
});

4 个答案:

答案 0 :(得分:20)

您不想在每个按钮上弄乱点击处理程序,只需听取容器上的点击。然后根据单击的子项更新状态。

此外,使用React最好将所有DOM内容保存在渲染功能中。在这种情况下,定义元素的类名。

以下是这可行的方法:

var RadioButtonGroup = React.createClass({
    getInitialState: function getInitialState() {
        return {
            active: this.props.active || 0
        };
    },

    clickHandler: function clickHandler(e) {
        // Getting an array of DOM elements
        // Then finding which element was clicked
        var nodes = Array.prototype.slice.call( e.currentTarget.children );
        var index = nodes.indexOf( e.target );
        this.setState({ active: index });
    },

    render: function render() {
        var buttons = this.children.map(function(child, i) {
            if (i === this.state.active) child.props.className += ' active';
            return child;
        }, this);

        return (
            <div className="radio-button-group" onClick={ this.clickHandler }>
                { buttons }
            </div>
        )
    }
});

答案 1 :(得分:6)

要获得这样的api(类似于<input/>),我们需要使用cloneWithProps插件。

  <RadioButtonGroup onChange={this.handleChange} value={this.state.selected}>
    <button>Test 1</button>
    <button>Test 2</button>
    <button>Test 3</button>
  </RadioButtonGroup>

所有这一切都是为每个孩子,添加一个点击处理程序,并有条件地添加一个className为'active'。您可以(并且应该)修改它以将活动类名称作为道具。

var RadioButtonGroup = React.createClass({
  render: function(){
    return <div>{React.Children.map(this.props.children, this.renderItem)}</div>
  },
  renderItem: function(button, index){
    return React.cloneElement(button, {
      className: this.props.value === index ? ' active ' : '',
      onClick: function(){ 
        this.props.onChange(index); 
      }.bind(this),
      key: index
    });
  }
});

demo

如果你不想使用cloneWithProps,你可以使用包装器div,但样式可能会更复杂。

  renderItem: function(button, index){
    return React.createElement('div', {
      className: this.props.value === index ? ' active ' : '',
      onClick: function(){ 
        this.props.onChange(index); 
      }.bind(this),
      key: index
    }, button);
  }

一切都使用索引的原因是因为你传递的反应元素是不透明的。没有干净的方法从这些按钮中获取任何数据,但我们知道他们的索引,因为我们使用React.Children.map迭代它们。另一种api看起来像这样:

<RadioButtonGroup value={'test1'} onChange={fn} options={{
   test1: <button>Test 1</button>,
   test2: <button>Test 2</button>,
   test3: <button>Test 3</button>
}} />

在这里,我们可以迭代this.props.options,并将密钥传递给onChange回调,并采取例如'test1'作为价值道具。

答案 2 :(得分:0)

这是我的代码。我不知道这是否是最佳方式,因为我开始使用反应仅在5小时前。但它运作正常。

var LeftPane = React.createClass({
    getInitialState: function() {
        return {list:[{name:"Item 1",isactive:1},
                      {name:"Item 2",isactive:0},
                      {name:"Item 3",isactive:0},                     
                      {name:"Item 4",isactive:0},
                      {name:"Item 5",isactive:0}]};
    },
    clickHandler: function(index) {
        var current_list = this.state.list;
        current_list.map(function(record,i){
          if(record.isactive==1){
            if(i!=index){
              record.isactive =0;
            }else{
              return;
            }
          }else{
            if(i==index){
              record.isactive =1;
            }
          }
        });
        this.setState({data:current_list}); 
    },   
    render: function() {
        var active_item = "list-group-item active"
        var non_active_item ="list-group-item";
        var _this = this;
        return (              
          <div className="list-group">
            {this.state.list.map(function(record,i) {                          
                if(record.isactive==1){
                return <a className={active_item} onClick={_this.clickHandler.bind(null, i)}>{record.name}</a>
                }else{
                return <a className={non_active_item} onClick={_this.clickHandler.bind(null, i)}>{record.name}</a>
                }                  
            })}
          </div>
        </div>
        )
    }
});

答案 3 :(得分:0)

  

我不想将onClick处理程序添加到每个按钮元素

React使用事件委托模式,因此您可以根据需要自由添加任意数量的onClick处理程序。

另一种方式,如果你只是想让代码清晰,你可以创建自己的按钮组件,并将_onActiveChange传递给它道具,但我不确定你的情况是否有必要。

请记住,React适用于合成事件,在某些情况下,在本机事件处理程序中使用setState可能会导致不可预测的行为。