我正在使用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);
}
});
实际问题:如何使用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);
}
});
答案 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
});
}
});
如果你不想使用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可能会导致不可预测的行为。