web-dev-qa-db-ja.com

ReactJSフォームコンポーネントのベストプラクティス

ユーザーが特定のエンティティを編集するためのフォームを担当する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>
        );
    }
});

フォームのフィールドはエンティティオブジェクトを直接編集しており、RESTful APIに保存できます。ユーザーがフィールドを変更したときにコンポーネントを更新して、入力中の入力に基づいてGUIが反応するようにします(検証、情報など)。

理論的には、編集中のエンティティを状態オブジェクト全体で表すことができるため、エンティティのすべてのプロパティは第1レベルの状態変数です。ただし、GUI関数やコンポーネントの動作に関連するその他の状態変数を追加できるようにしたいので、エンティティオブジェクトを上記の「エンティティ」状態変数のような1つの状態変数にすることをお勧めします。もちろん、オブジェクトはバックボーンモデルなどのさらに複雑なオブジェクトでもかまいませんが、この単純化された例では、必要なプロパティを持つ単純なオブジェクトを使用します。

したがって、この目的のためにReactコンポーネントを作成するためのベストプラクティスの方法を探して、いくつか質問があります。

  1. 小道具または状態。

この例では、フォームのコンテンツを含むエンティティオブジェクトを、propではなく状態変数に配置することを選択しました。これは、フォームの入力中に、親を呼び出して小道具を更新しなくてもオブジェクトを更新できるようにするためです。私のReactの経験に関する限り、これはこのようなフォームコンポーネントのベストプラクティスです。

  1. 制御または非制御入力。

上記の簡単な例では、制御された入力を使用しています。これにより、状態が更新され、すべての変更(テキストフィールドに入力されたすべての文字など)ごとにコンポーネントが再レンダリングされます。これはベストプラクティスですか?良い点は、コンポーネントがdefaultValueパラメータを使用する代わりに、何が起こるかを完全に制御できることです。また、ユーザーが(保存ボタンを押すなどの)イベントで、コンポーネントは値を抽出し、エンティティを更新してサーバーに保存します。このような場合に制御された入力または制御されていない入力を使用する必要があるかどうかについて、理由(または意見)はありますか?

  1. フォームまたはすべての入力のonChange

この例では、フォームタグにonChangeがあり、フォームのフィールドが変更されるたびに、handleChangeメソッドが呼び出されます。ただし、入力は制御されている(値パラメーターを持つ)ため、Reactは、入力フィールドにonChangeプロパティがないことに不満があります。これは、フォームタグに共通のonChangeがあることは悪い習慣であることを意味します。それを削除して、代わりにすべてのフィールドにonChangeを配置する必要がありますか?

  1. 個々のプロパティの更新

上記の例では、(handleChangeが呼び出されたときに)どの入力フィールドが更新されているかに基づいてスイッチを使用しています。代わりに、すべてのフィールド名がエンティティのプロパティ名と同期していることを確認できます。イベントのフィールド名(e.target.name)に基づいて、handleChangeでエンティティオブジェクトのプロパティを設定できます。ただし、ほとんどのフィールドがエンティティプロパティを直接更新するだけでも、フィールドごとに個別のニーズを持つことは困難です。代替案は、入力の名前に基づくデフォルトのブロック設定と、他の更新方法(エンティティに設定する前に値をフィルタリングするなど)を必要とするフィールドのケースブロックを備えたスイッチだと思います。この方法でフィールドの更新を処理するはるかに優れた方法を知っている場合は、これにコメントしてください。

  1. 状態エンティティの更新

この例の大きな問題の1つは、エンティティオブジェクトの更新方法です。 handleChangeのエンティティ変数は現在の状態のエンティティオブジェクトに設定されているため、これは単なるポインタであり、エンティティ変数を更新すると、状態のオブジェクトが変更されます。 Reactページでは、状態を直接更新しないでくださいと言います。理由の1つは、setStateを呼び出す前にこの方法で状態を更新したときに経験したものです。shouldComponentUpdateメソッドがある場合、prevStateには新しいstate、shouldComponentUpdateに送信されるprevState引数の内容は、setStateが呼び出されたときの状態に基づいているため、私が知る限り、JavaScriptでオブジェクトを複製する簡単な方法はありません。単一の状態変数のsetStateを実行するだけでなく、オブジェクトのプロパティを更新する(オブジェクトの他の値に触れない)必要があるオブジェクト全体がある場合、これらの種類の状態の混同を引き起こさずにこれを行うための最良の方法は何ですか?

30
henit
  1. 変化するものはすべて状態になります。
  2. 既存のエンティティを読み込んで編集する場合、制御された入力が必要であり、それに応じて値を設定します。ほとんどの場合、(ドロップダウン以外では)defaultValueから離れている傾向があります
  3. これは、前の質問に結びついています。値を指定する場合は、制御された入力を使用しているため、制御された入力にはonChangeハンドラーを提供する必要があります。制御された入力の利点は、ユーザーがそれらを編集できないことです。そのため、HTMLを直接編集した場合でも、ユーザーが保存しようとしたときに(読み取り専用、セキュリティ上の理由から)いくつかのプロパティがロックダウンされている場合、Reactは、vDOM表現からプロパティの値をプルする必要があります(ここでは間違っている可能性がありますが、以前にこれをテストしたことがあると思います)。とにかく、制御された入力にonChangeを直接設定する必要があります。イベントデリゲーション(フォームレベル)、これは多くのイベントにとってまったく悪い習慣ではありません。これは、各要素に指定されたonChangeイベントが必要な特定のシナリオ(制御された入力)にすぎません。
  4. これに関する質問が何であるかは完全にはわかりませんが、target.nameの代わりにrefsを使用しました。
  5. だから、あなたは状態を直接変更してはいけないという点で正しいです、そしてこれはドキュメントからのトリッキーなビットです。 Reactは状態を直接変更しますが、setStateを介して実装で変更します。このメソッド呼び出しの外で状態を変更すると、予期しないことが起こり、エラーがスローされます。

shouldComponentUpdateは浅い比較のみを行いますが、ここにはいくつかの解決策があります。

1つはオブジェクトを文字列化することです。これは迅速でダーティなオブジェクト比較です。実際にはお勧めしませんが、機能します。

より良い解決策、およびReact + Fluxで使用した解決策は、propertyChanged boolを実装し、shouldComponentUpdateでそれを確認することです。

これで、物事が変わったとき、つまりオブジェクトグラフのより深いところを変更したときに、設定に注意する必要があります。たとえば、propertyOneは、handleChangeメソッドで変更されるプロパティを持つオブジェクトです。必要に応じて入力を検証し、propertyChanged = trueを設定してから、componentDidUpdateを実装する必要があります。ここでは想定を行っていますが、コンポーネントが更新された場合は、propertyChangedをfalseに戻して、不要な更新がそれ以上トリガーされないようにします。私はそれが理にかなっていると思います。これは、一方向のnotifyPropertyChangedのようなものです。

オブジェクトにプロパティを追加できるようにする、より動的な実装のために私がおそらく何をするかを示す簡単な例を提供します(この実装では浅いプロパティのみ、明らかにより堅牢なソリューションを作成できます)。他にご不明な点がある場合や、私が回答しなかった場合はお知らせください。

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);
6
captray