javascript ReactJS 表单组件的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26626454/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 06:14:09  来源:igfitidea点击:

Best practice for ReactJS form components

javascriptformsreactjs

提问by henit

I am looking for a best practice to have a ReactJS component responsible for the form for users to edit a given entity. Very simplified example here. Actual forms would in many cases have several more fields and more GUI functionality.

我正在寻找一种最佳实践,让 ReactJS 组件负责用户编辑给定实体的表单。非常简单的例子在这里。在许多情况下,实际的表单会有更多的字段和更多的 GUI 功能。

React.createClass({
    getInitialState: function() {
        return {
            entity: {
                property1: null,
                property2: null
            }
        };
    },

    handleChange: function(e) {
        var entity = this.state.entity;

        switch(e.target.name) {
            case 'property1':
                entity.property1 = e.target.value;
                break;
            case 'property2':
                entity.property2 = e.target.value;
                break;
        }

        this.setState({
            entity: entity
        });
    },

    render: function() {
        return (
            <div className="entity-form">
                <form onChange={this.handleChange}>
                    <input type="text" name="property1" value={this.state.entity.property1} />
                    <br />

                    <textarea name="property2" value={this.state.entity.property2}></textarea>
                    <br />
                </form>
            </div>
        );
    }
});

The fields of the form is directly editing an entity object, that could then be saved to a RESTful api. I want the component to be updated as the user change the fields, so the GUI could react based on the input during typing (like validations, info etc).

表单的字段直接编辑实体对象,然后可以将其保存到 RESTful api。我希望在用户更改字段时更新组件,以便 GUI 可以根据键入期间的输入(如验证、信息等)做出反应。

In theory, I could have the whole state object represent the entity that is being edited, so every property of the entity is first level state variables. However, I want to be able to add additional state variables for GUI functions and other things related to what the component is going to do, so I would prefer the entity object to be one state variable like the "entity" state variable above. The object could of course be some more complicated object, like a Backbone model or similar, but in this simplified example, I just use a simple object whit the required properties.

理论上,我可以让整个状态对象代表正在编辑的实体,因此实体的每个属性都是一级状态变量。但是,我希望能够为 GUI 函数和其他与组件将要执行的操作相关的事情添加额外的状态变量,因此我希望实体对象是一个状态变量,如上面的“实体”状态变量。该对象当然可以是一些更复杂的对象,例如 Backbone 模型或类似的对象,但在这个简化的示例中,我只使用了一个具有所需属性的简单对象。

So, in search of the best practice way to make React components for this purpose, I have some questions:

因此,为了寻找为此目的制作 React 组件的最佳实践方法,我有一些问题:

  1. Props or state.
  1. 道具或状态。

In this case, I have chosen to put the entity object with the content for the form in a state variable instead of prop. This is to be able to update the object during form input without having to call the parent and update the props. As far as my React experience goes, that would be the best practice for a form component like this.

在这种情况下,我选择将包含表单内容的实体对象放在状态变量而不是 prop 中。这是为了能够在表单输入期间更新对象,而无需调用父级和更新道具。就我的 React 经验而言,这将是像这样的表单组件的最佳实践。

  1. Controlled or uncontrolled inputs.
  1. 受控或非受控输入。

In the simplified example above, I use controlled inputs. This leads to updating the state and re-rendering the component on every change (like every character entered of a text field). Is this the best practice? The good thing is that the component has full control of what happens, instead of having defaultValue paramters, and on some event (like the user pressing a save button), the component extract the values, update the entity and save it to the server. Is there any reasons (or opinions) on if controlled or uncontrolled inputs should be used in cases like this?

在上面的简化示例中,我使用了受控输入。这会导致更新状态并在每次更改时重新渲染组件(如文本字段中输入的每个字符)。这是最佳做法吗?好处是组件可以完全控制发生的事情,而不是具有 defaultValue 参数,并且在某些事件(例如用户按下保存按钮)时,组件提取值,更新实体并将其保存到服务器。在这种情况下是否应该使用受控或非受控输入,是否有任何理由(或意见)?

  1. onChange for the form or every input
  1. 表单或每个输入的 onChange

The example has an onChange on the form tag, and it causes the handleChange method to be called every time any of the fields in the form is changed. However, since the inputs are controlled (have value parameters), React complains that the input fields does not have an onChange property. Does this mean having a common onChange on the form tag is bad practice, and I should remove it and put onChange on every single field instead?

该示例在表单标记上有一个 onChange,它会导致每次更改表单中的任何字段时都会调用 handleChange 方法。然而,由于输入是受控的(有值参数),React 抱怨输入字段没有 onChange 属性。这是否意味着在表单标签上有一个通用的 onChange 是不好的做法,我应该删除它并将 onChange 放在每个字段上?

  1. Updating individual properties
  1. 更新个人属性

In the above example, I use a switch based on what input field is being update (when handleChange is called). I guess I could instead make sure all field names is in sync with the property names of the entity, and I can set properties of the entity object in handleChange based on the name of the field from the event (e.target.name). However, this makes it hard to have individual needs per field, even if most fields just update an entity property directly. I guess an alternativ is a switch with a default block setting based on the name of the input, and case blocks for any field that require other ways of updating (like filtering the value before setting it on the entity). Please comment this if you know some much better way of handeling field updates this way.

在上面的示例中,我根据正在更新的输入字段(调用 handleChange 时)使用开关。我想我可以改为确保所有字段名称与实体的属性名称同步,并且我可以根据事件中的字段名称(e.target.name)在 handleChange 中设置实体对象的属性。然而,这使得每个字段很难有单独的需求,即使大多数字段只是直接更新实体属性。我猜一个替代方案是一个开关,它具有基于输入名称的默认块设置,以及需要其他更新方式的任何字段的 case 块(例如在将值设置在实体上之前对其进行过滤)。如果您知道以这种方式处理字段更新的更好方法,请对此发表评论。

  1. Updating the state entity
  1. 更新状态实体

One big problem of this example, is the way the entity object is updated. Since the entity variable in the handleChange is set to the entity object from current state, this is just a pointer, and updating the entity variable will change the object in state. The React pages say you should never update state directly. One of the reasons is something I have experienced when updating the state this way before calling setState. If having a shouldComponentUpdate method, the prevState contain the new state, since the content of the prevState argument sent to the shouldComponentUpdate is based on what was in the state when setState was called. As far as I know, there is no simple way to clone a object in javascript. So the question is, when having whole objects that I need to update properties of (and not touching the other values in the object) instead of just running setState of a single state variable, what is the best way to do this without causing theese kinds of state mixups?

这个例子的一个大问题是实体对象的更新方式。由于handleChange中的实体变量设置为当前状态的实体对象,所以这只是一个指针,更新实体变量会改变对象的状态。React 页面说你永远不应该直接更新状态。原因之一是我在调用 setState 之前以这种方式更新状态时所经历的事情。如果有 shouldComponentUpdate 方法,则 prevState 包含新状态,因为发送到 shouldComponentUpdate 的 prevState 参数的内容基于调用 setState 时的状态。据我所知,没有简单的方法可以在 javascript 中克隆对象。所以问题是,

回答by captray

  1. Anything that is going to change goes in State.
  2. If you're looking at loading an existing entity and editing it, you want controlled inputs, and you want to set the values accordingly. I tend to stay away from defaultValue in most cases (outside of dropdowns)
  3. This ties back in to your previous question. If you specify a value, you are using a controlled input, and you have to provide an onChange handler for any controlled input, otherwise it is set in stone. A benefit of controlled inputs is that users can't edit them, so if you had some properties locked down (maybe for read only, security reasons), when the user attempts to save, even if they edited the HTML directly, React should pull the property's value from the vDOM representation (could be wrong here, but I believe I've tested this before). Anyway, you have to have onChange set directly on controlled inputs. As far as using event delgation (at the form level), this isn't a bad practice at all for a lot of events, this is just a specific scenario (controlled inputs) where you need onChange events specified for each element.
  4. Not entirely sure what the ask on this one is, but I used refs instead of target.name.
  5. So, you're correct in that you should never alter the state directly, and this is a tricky bit from the docs. React is going to alter state directly, it's just going to do it in the implementation through setState. If you alter state outside of this method call, unexpected things will happen and errors will be thrown.
  1. 任何要改变的东西都会进入状态。
  2. 如果您正在查看加载现有实体并对其进行编辑,您需要受控输入,并且您需要相应地设置值。在大多数情况下(下拉菜单之外),我倾向于远离 defaultValue
  3. 这与您之前的问题有关。如果您指定一个值,则您使用的是受控输入,并且您必须为任何受控输入提供一个 onChange 处理程序,否则它是一成不变的。受控输入的一个好处是用户无法编辑它们,因此如果您锁定了某些属性(可能是出于只读、安全原因),当用户尝试保存时,即使他们直接编辑了 HTML,React 也应该拉来自 vDOM 表示的属性值(这里可能是错误的,但我相信我之前已经测试过)。无论如何,您必须直接在受控输入上设置 onChange。至于使用事件委托(在表单级别),这对于很多事件来说根本不是一个坏习惯,这只是一个特定场景(受控输入),您需要为每个元素指定 onChange 事件。
  4. 不完全确定这个问题是什么,但我使用了 refs 而不是 target.name。
  5. 所以,你是正确的,你不应该直接改变状态,这是文档中的一个棘手问题。React 将直接改变状态,它只是通过 setState 在实现中完成。如果您在此方法调用之外更改状态,则会发生意外并引发错误。

shouldComponentUpdate only does shallow comparisons, but there are a few solutions here.

shouldComponentUpdate 只做浅层比较,但这里有几个解决方案。

One is to stringify the objects, this is a quick and dirty object comparison, don't really recommend it, but it works.

一种是对对象进行字符串化,这是一个快速而脏的对象比较,并不真正推荐它,但它有效。

A better solution, and one I have used with React + Flux is to implement a propertyChanged bool, and just check that in your shouldComponentUpdate.

一个更好的解决方案,我在 React + Flux 中使用过的一个是实现一个 propertyChanged bool,然后在你的 shouldComponentUpdate 中检查它。

Now, this will require you to be aware of setting it when things change, i.e., you changed something deeper in the object graph. Say propertyOne is an object with a property that gets changed in your handleChange method. You would validate the input however you wish, then set propertyChanged = true, and you then need to implement componentDidUpdate. We're making an assumption here, but if the component has updated, you set propertyChanged back to false so you don't have any further triggering of unwanted updates. I hope that makes sense. It's kinda like a one-way notifyPropertyChanged.

现在,这将要求您在事情发生变化时注意设置它,即您更改了对象图中更深层次的内容。假设 propertyOne 是一个具有在 handleChange 方法中更改的属性的对象。您可以根据需要验证输入,然后设置 propertyChanged = true,然后您需要实现 componentDidUpdate。我们在这里做一个假设,但如果组件已更新,您将 propertyChanged 设置回 false,这样您就不会进一步触发不需要的更新。我希望这是有道理的。这有点像单向的notifyPropertyChanged。

I'm providing a quick example of what I would probably do for a more dynamic implementation that allows you to add more properties to your object (only shallow properties in this implementation, obviously you could write a more robust solution). Let me know if you have any further questions or if I didn't answer something.

我正在提供一个快速示例,说明我可能会为更动态的实现做些什么,该实现允许您向对象添加更多属性(此实现中只有浅层属性,显然您可以编写更强大的解决方案)。如果您有任何其他问题或者我没有回答某些问题,请告诉我。

http://jsfiddle.net/rpv9trhh/

http://jsfiddle.net/rpv9trhh/

var e = {
    prop1: 'test',
    prop2: 'wee',
    prop3: 'another property',
    propFour: 'Oh yeah!'
};

var FormComp = React.createClass({
    getInitialState: function(){
        return {
            entity: this.props.entity
        }
    },
    render: function() {

        var ent = this.state.entity;
        var that = this;
        var inputs = [];

        for(var key in ent){
            inputs.push(<input 
               key={key} 
               style={{display:'block'}} 
               type="text" 
               ref={key} onChange={that._propertyChanged.bind(null, key)}
               value={ent[key]} />)
        }

        return <form>
            {inputs}
            <input 
                type="button" 
                onClick={this._saveChanges} 
                value="Save Changes" />
        </form>;
    },
    _propertyChanged: function(propName) {
        var nextProp = this.refs[propName].getDOMNode().value;
        var nextEntity = this.state.entity;
        nextEntity[propName] = nextProp;

        this.setState({
            entity: nextEntity
        });
    },
    _saveChanges: function() {
        var updatedEntity = this.state.entity;

        for(var key in updatedEntity){
            alert(updatedEntity[key]);
        }

        //TODO: Call to service to save the entity, i.e.    
        ActionCreators.saveEntity(updatedEntity);
    }
});

React.renderComponent(<FormComp entity={e} />, document.body);