ReactJS - 管理的复选框组

时间:2015-01-09 04:30:21

标签: reactjs

作为帮助我学习ReactJS的一种方式,我正在设置一些真正应该容易的东西,但事实证明这对我来说有些棘手。

我想在ReactJS中设置一些托管的复选框组。在HTML中,复选框“字段”实际上由许多共享共同NAME属性的input type =“checkbox”元素组成。据我了解,这只是一种应该符合ReactJS组成性质的UI元素。

我有两个ReactJS组件:

首先,CheckboxField用于复选框组中的每个条目 - 即每个输入类型=“复选框”HTML元素。

其次,CheckboxFieldGroup适用于每组复选框条目 - 即每组HTML元素共享一个共同的NAME属性。 CheckboxFieldGroup组件根据传递给它的初始道具创建了许多CheckboxField组件。

状态在CheckboxFieldGroup组件中管理,而不是在单个CheckboxField级别。从我所读到的,你应该把状态作为最有意义的水平来管理。对我而言,将它放在CheckboxFieldGroup级别更有意义。

当CheckboxFieldGroup首次运行时,其初始状态将作为数组从其初始道具(也是数组)创建。 render方法(实际上是renderChoices方法)遍历其状态数组,并将每个状态成员的属性向下传递给CheckboxField组件,作为后者的props。当用户勾选/取消选中其中一个复选框时,该事件将通过回调传递给其所有者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

但我觉得我在这里犯了很多错误。

  1. 实现这么简单的事情似乎是一大堆代码。我的整个方法是否被误导了?
  2. 我的CheckboxFieldGroup的状态似乎包含很多可能不应该存在的东西,例如:它包含名称,值,标签,ID和已检查,实际上它只是最后一次命名(由用户改变)。那么这应该是唯一一个在状态和其他人在道具中的人吗?但我需要将id属性置于状态,以便CheckboxFieldGroup.handleChange()方法可以确定哪个复选框实际已更改。或者有更好/更简单的方法吗?
  3. 当我更新CheckboxFieldGroup组件的状态时,再次在handleChange()方法中,我找不到直接更新所需状态的一部分的方法 - 即对应的状态数组元素的checked属性到刚刚勾选/未勾选的复选框。我最终做的是将状态数组的完整副本转换为另一个变量,在那里更新我的一个属性,然后用新数组替换整个状态。这是不是浪费的方式,即使我使用setState()而不是replaceState()?
  4. 非常感谢您的帮助。是的,我有谷歌,并通过文档仔细考虑。我还购买并阅读了“开发React Edge”一书,该书目前在一个领域中排名第一!

1 个答案:

答案 0 :(得分:4)

对于问题1,当我第一次使用react来构建我的第一个组件时,我有同样的感觉,猜猜是这样的?哈哈

对于问题2和3,我只会将checked保存在状态中,其余信息仍保留在道具中。然后在处理更新时,我只将特定复选框设置为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添加到子数组元素中,以便反应知道要更新哪个元素。

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
  });
});