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

Kim*_*mmo 14 reactjs

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

在此输入图像描述

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

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);
    }
});
Run Code Online (Sandbox Code Playgroud)

实际问题: 如何用React实现最优雅的效果?(我发现了另一个类似的问题,但它并没有完全回答这个问题).

我的第一个直觉是在组件内添加和删除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);
    }
});
Run Code Online (Sandbox Code Playgroud)

小智 20

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

另外,使用React最好将所有DOM内容保存在render函数中.在这种情况下,定义元素的类名.

这是如何工作的:

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>
        )
    }
});
Run Code Online (Sandbox Code Playgroud)


Bri*_*and 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>
Run Code Online (Sandbox Code Playgroud)

所有这一切都是为每个孩子,添加一个单击处理程序,并有条件地添加一个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
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

演示

如果您不想使用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);
  }
Run Code Online (Sandbox Code Playgroud)

一切都使用索引的原因是因为你传递的反应元素是不透明的.没有干净的方法从这些按钮中获取任何数据,但我们知道他们的索引,因为我们使用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>
}} />
Run Code Online (Sandbox Code Playgroud)

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