ReactJS - 托管复选框组
ReactJS - managed checkbox groups
作为帮助我学习 ReactJS 的一种方式,我正在设置一些本来应该很容易的东西,但事实证明对我来说有点棘手。
我想在 ReactJS 中设置一些托管复选框组。在 HTML 中,复选框 "field" 实际上由许多 input type="checkbox" 元素组成,它们共享一个共同的 NAME 属性。据我了解,这只是一种 UI 元素,应该符合 ReactJS 的组合性质。
我有两个 ReactJS 组件:
首先,CheckboxField 适用于复选框组中的每个单独条目 - 即每个输入类型="checkbox" HTML 元素。
其次,CheckboxFieldGroup 适用于每组复选框条目 - 即共享共同 NAME 属性的每组 HTML 元素。 CheckboxFieldGroup 组件根据传入的初始 props 创建许多 CheckboxField 组件。
状态在 CheckboxFieldGroup 组件中进行管理,而不是在单个 CheckboxField 级别进行管理。根据我的阅读,您应该将状态管理为有意义的最高级别。对我来说,将它放在 CheckboxFieldGroup 级别更有意义。
当 CheckboxFieldGroup 第一次运行时,它的初始状态是从它的初始道具创建的,作为一个数组,也是一个数组。 render 方法(实际上是 renderChoices 方法)循环遍历其状态数组,并将每个状态成员的属性作为后者的 props 传递给 CheckboxField 组件。当用户 ticks/unticks 复选框之一时,该事件通过回调传递给其所有者 CheckboxFieldGroup 的 handleChange 方法。此方法通过询问其 id 属性来确定哪些复选框已更改,然后通过调用 setState() 对 CheckboxFieldGroup 的状态数组的正确成员进行相应的更改。这会导致 CheckboxFieldGroup 自动重新呈现,新的状态数组被传递到各个 CheckboxField 组件,所以一切都是同步的。
/** @jsx React.DOM */
var CheckboxField = React.createClass({
propTypes: {
values: React.PropTypes.object.isRequired
},
getDefaultProps: function () {
return {
values: {
label: "Place holder text"
}
};
},
render: function() {
return (
<label htlmFor={this.props.values.id}>
<input type="checkbox"
name={this.props.values.name}
id={this.props.values.id}
value={this.props.values.value}
checked={this.props.values.checked}
onChange={this.handleChange} />
{this.props.values.label} <br />
</label>
);
},
handleChange: function(event) {
// Should use this to set parent's state via a callback func. Then the
// change to the parent's state will generate new props to be passed down
// to the children in the render().
this.props.callBackOnChange(this, event.target.checked);
}
});
var CheckboxFieldGroup = React.createClass({
propTypes: {
defaultValues: React.PropTypes.object.isRequired
},
getInitialState: function () {
// default props passed in to CheckboxFieldGroup (this componenent) will be used to set up the state. State
// is stored in this component, and *not* in the child CheckboxField components. The state store in this
// component will, in turn, generate the props for the child CheckboxField components. When the latter
// are updated (i.e. clicked) by the user, then the event will call the handleChange() function in
// this component. That will generate update this component's state, which in turn will generate
// new props for the child CheckboxField components, which will cause those components to re-render!
var that = this;
var initStateArray = this.props.defaultValues.valuesArray.map(function(choice, i) {
var tempObj = {
name: that.props.defaultValues.name,
value: choice.value,
label: choice.label,
id: _.uniqueId("choice"),
checked: choice.checked
};
return tempObj;
});
return {valuesArray: initStateArray};
},
renderChoices: function() {
var that = this; // Could also use .bind(this) on our map() function but that requires IE9+.
return this.state.valuesArray.map(function(choice, i) {
return CheckboxField({
values: {
name: that.props.defaultValues.name,
value: choice.label,
label: choice.label,
id: choice.id,
checked: choice.checked
},
callBackOnChange: that.handleChange
});
});
},
render: function () {
return (
<form>
{this.renderChoices()}
</form>
);
},
handleChange: function(componentChanged, newState) {
// Callback function passed from CheckboxFieldGroup (this component) to each of the
// CheckboxField child components. (See renderChoices func).
var idx = -1;
var stateMemberToChange = _.find(this.state.valuesArray, function(obj, num) {
idx = num;
return obj.id === componentChanged.props.values.id;
});
// Threw an error when I tried to update and indiviudal member of the state array/object. So, take a copy
// of the state, update the copy and do a setState() on the whole thing. Using setState() rather than
// replaceState() should be more efficient here.
var newStateValuesArray = this.state.valuesArray;
newStateValuesArray[idx].checked = newState;
this.setState({valuesArray: newStateValuesArray}); // Automatically triggers render() !!
},
getCheckedValues: function() {
// Get an array of state objects that are checked
var checkedObjArray = [];
checkedObjArray = _.filter(this.state.valuesArray, function(obj){
return obj.checked;
});
// Get an array of value properties for the checked objects
var checkedArray = _.map(checkedObjArray, function(obj){
return obj.value;
});
console.log("CheckboxFieldGroup.getCheckedValues() = " + checkedArray);
},
componentDidMount: function() {
this.getCheckedValues();
},
componentDidUpdate: function() {
this.getCheckedValues();
}
});
var defaults = {
name : "mikeyCheck",
valuesArray : [{
label : "My Checkbox Field",
value: "MyCheckboxField",
checked : false
}, {
label : "My Other Checkbox Field",
value : "MyOtherCheckboxField",
checked : false
}, {
label : "Yet Another Checkbox Field",
value : "YetAnotherCheckboxField",
checked : true
},{
label : "Yes, it's a fourth checkbox field",
value : "YesItsAFourthCheckboxField",
checked : false
}]
};
React.renderComponent(<CheckboxFieldGroup defaultValues={defaults} />, document.getElementById("main"));
一切正常,这里是 JSFiddle of it in operation。
但我觉得我在这里做错了很多事情。
- 实现如此简单的事情似乎需要大量代码。我的整个方法是否被误导了?
- 我的 CheckboxFieldGroup 的状态似乎包含很多可能不应该存在的东西,例如它包含名称、值、标签、ID 和已检查,而实际上它只是最后一个将要更改(由用户)更改的名称。那么,那应该是唯一一个处于状态而其他人以某种方式处于道具状态的吗?但是我需要 id 属性处于状态,以便 CheckboxFieldGroup.handleChange() 方法可以确定哪个复选框实际发生了变化。或者有 better/easier 的方法吗?
- 当我更新 CheckboxFieldGroup 组件的状态时,再次在 handleChange() 方法中,我找不到直接更新我需要的状态部分的方法 - 即状态数组元素的 checked 属性对应于 ticked/unticked 的复选框。我最终做的是将状态数组的完整副本复制到另一个变量,在那里更新我的一个属性,然后用新数组替换整个状态。这不是一种浪费的方式吗,即使我使用的是 setState() 而不是 replaceState()?
非常感谢您的帮助。是的,我有谷歌,并仔细阅读了文档。我还购买并阅读了《Developing a React Edge》这本书,这本书目前似乎是一个领域中的第一名!
对于第一个问题,当我第一次使用 React 构建我的第一个组件时,我也有同样的感觉,你猜是这样吗?哈哈
对于第2题和第3题,我只会保存state中的checked
,其余信息都留在props中。然后在处理更新时,我只将某个复选框设置为true/false。
http://jsfiddle.net/p0s58exh/4/
getInitialState: function () {
var that = this;
var states = {};
_.map(this.props.defaultValues.checkboxes, function (choice, key) {
states[key] = choice.checked;
});
return states;
},
记得将 key
添加到子数组元素中,以便 React 准确知道要更新哪个元素。
return _.map(this.props.defaultValues.checkboxes, function (choice, key) {
return CheckboxField({
key: key,
values: {
name: that.props.defaultValues.name,
value: key,
label: choice.label,
id: choice.id,
checked: that.state[key]
},
callBackOnChange: that.handleChange
});
});
作为帮助我学习 ReactJS 的一种方式,我正在设置一些本来应该很容易的东西,但事实证明对我来说有点棘手。
我想在 ReactJS 中设置一些托管复选框组。在 HTML 中,复选框 "field" 实际上由许多 input type="checkbox" 元素组成,它们共享一个共同的 NAME 属性。据我了解,这只是一种 UI 元素,应该符合 ReactJS 的组合性质。
我有两个 ReactJS 组件:
首先,CheckboxField 适用于复选框组中的每个单独条目 - 即每个输入类型="checkbox" HTML 元素。
其次,CheckboxFieldGroup 适用于每组复选框条目 - 即共享共同 NAME 属性的每组 HTML 元素。 CheckboxFieldGroup 组件根据传入的初始 props 创建许多 CheckboxField 组件。
状态在 CheckboxFieldGroup 组件中进行管理,而不是在单个 CheckboxField 级别进行管理。根据我的阅读,您应该将状态管理为有意义的最高级别。对我来说,将它放在 CheckboxFieldGroup 级别更有意义。
当 CheckboxFieldGroup 第一次运行时,它的初始状态是从它的初始道具创建的,作为一个数组,也是一个数组。 render 方法(实际上是 renderChoices 方法)循环遍历其状态数组,并将每个状态成员的属性作为后者的 props 传递给 CheckboxField 组件。当用户 ticks/unticks 复选框之一时,该事件通过回调传递给其所有者 CheckboxFieldGroup 的 handleChange 方法。此方法通过询问其 id 属性来确定哪些复选框已更改,然后通过调用 setState() 对 CheckboxFieldGroup 的状态数组的正确成员进行相应的更改。这会导致 CheckboxFieldGroup 自动重新呈现,新的状态数组被传递到各个 CheckboxField 组件,所以一切都是同步的。
/** @jsx React.DOM */
var CheckboxField = React.createClass({
propTypes: {
values: React.PropTypes.object.isRequired
},
getDefaultProps: function () {
return {
values: {
label: "Place holder text"
}
};
},
render: function() {
return (
<label htlmFor={this.props.values.id}>
<input type="checkbox"
name={this.props.values.name}
id={this.props.values.id}
value={this.props.values.value}
checked={this.props.values.checked}
onChange={this.handleChange} />
{this.props.values.label} <br />
</label>
);
},
handleChange: function(event) {
// Should use this to set parent's state via a callback func. Then the
// change to the parent's state will generate new props to be passed down
// to the children in the render().
this.props.callBackOnChange(this, event.target.checked);
}
});
var CheckboxFieldGroup = React.createClass({
propTypes: {
defaultValues: React.PropTypes.object.isRequired
},
getInitialState: function () {
// default props passed in to CheckboxFieldGroup (this componenent) will be used to set up the state. State
// is stored in this component, and *not* in the child CheckboxField components. The state store in this
// component will, in turn, generate the props for the child CheckboxField components. When the latter
// are updated (i.e. clicked) by the user, then the event will call the handleChange() function in
// this component. That will generate update this component's state, which in turn will generate
// new props for the child CheckboxField components, which will cause those components to re-render!
var that = this;
var initStateArray = this.props.defaultValues.valuesArray.map(function(choice, i) {
var tempObj = {
name: that.props.defaultValues.name,
value: choice.value,
label: choice.label,
id: _.uniqueId("choice"),
checked: choice.checked
};
return tempObj;
});
return {valuesArray: initStateArray};
},
renderChoices: function() {
var that = this; // Could also use .bind(this) on our map() function but that requires IE9+.
return this.state.valuesArray.map(function(choice, i) {
return CheckboxField({
values: {
name: that.props.defaultValues.name,
value: choice.label,
label: choice.label,
id: choice.id,
checked: choice.checked
},
callBackOnChange: that.handleChange
});
});
},
render: function () {
return (
<form>
{this.renderChoices()}
</form>
);
},
handleChange: function(componentChanged, newState) {
// Callback function passed from CheckboxFieldGroup (this component) to each of the
// CheckboxField child components. (See renderChoices func).
var idx = -1;
var stateMemberToChange = _.find(this.state.valuesArray, function(obj, num) {
idx = num;
return obj.id === componentChanged.props.values.id;
});
// Threw an error when I tried to update and indiviudal member of the state array/object. So, take a copy
// of the state, update the copy and do a setState() on the whole thing. Using setState() rather than
// replaceState() should be more efficient here.
var newStateValuesArray = this.state.valuesArray;
newStateValuesArray[idx].checked = newState;
this.setState({valuesArray: newStateValuesArray}); // Automatically triggers render() !!
},
getCheckedValues: function() {
// Get an array of state objects that are checked
var checkedObjArray = [];
checkedObjArray = _.filter(this.state.valuesArray, function(obj){
return obj.checked;
});
// Get an array of value properties for the checked objects
var checkedArray = _.map(checkedObjArray, function(obj){
return obj.value;
});
console.log("CheckboxFieldGroup.getCheckedValues() = " + checkedArray);
},
componentDidMount: function() {
this.getCheckedValues();
},
componentDidUpdate: function() {
this.getCheckedValues();
}
});
var defaults = {
name : "mikeyCheck",
valuesArray : [{
label : "My Checkbox Field",
value: "MyCheckboxField",
checked : false
}, {
label : "My Other Checkbox Field",
value : "MyOtherCheckboxField",
checked : false
}, {
label : "Yet Another Checkbox Field",
value : "YetAnotherCheckboxField",
checked : true
},{
label : "Yes, it's a fourth checkbox field",
value : "YesItsAFourthCheckboxField",
checked : false
}]
};
React.renderComponent(<CheckboxFieldGroup defaultValues={defaults} />, document.getElementById("main"));
一切正常,这里是 JSFiddle of it in operation。
但我觉得我在这里做错了很多事情。
- 实现如此简单的事情似乎需要大量代码。我的整个方法是否被误导了?
- 我的 CheckboxFieldGroup 的状态似乎包含很多可能不应该存在的东西,例如它包含名称、值、标签、ID 和已检查,而实际上它只是最后一个将要更改(由用户)更改的名称。那么,那应该是唯一一个处于状态而其他人以某种方式处于道具状态的吗?但是我需要 id 属性处于状态,以便 CheckboxFieldGroup.handleChange() 方法可以确定哪个复选框实际发生了变化。或者有 better/easier 的方法吗?
- 当我更新 CheckboxFieldGroup 组件的状态时,再次在 handleChange() 方法中,我找不到直接更新我需要的状态部分的方法 - 即状态数组元素的 checked 属性对应于 ticked/unticked 的复选框。我最终做的是将状态数组的完整副本复制到另一个变量,在那里更新我的一个属性,然后用新数组替换整个状态。这不是一种浪费的方式吗,即使我使用的是 setState() 而不是 replaceState()?
非常感谢您的帮助。是的,我有谷歌,并仔细阅读了文档。我还购买并阅读了《Developing a React Edge》这本书,这本书目前似乎是一个领域中的第一名!
对于第一个问题,当我第一次使用 React 构建我的第一个组件时,我也有同样的感觉,你猜是这样吗?哈哈
对于第2题和第3题,我只会保存state中的checked
,其余信息都留在props中。然后在处理更新时,我只将某个复选框设置为true/false。
http://jsfiddle.net/p0s58exh/4/
getInitialState: function () {
var that = this;
var states = {};
_.map(this.props.defaultValues.checkboxes, function (choice, key) {
states[key] = choice.checked;
});
return states;
},
记得将 key
添加到子数组元素中,以便 React 准确知道要更新哪个元素。
return _.map(this.props.defaultValues.checkboxes, function (choice, key) {
return CheckboxField({
key: key,
values: {
name: that.props.defaultValues.name,
value: key,
label: choice.label,
id: choice.id,
checked: that.state[key]
},
callBackOnChange: that.handleChange
});
});