web-dev-qa-db-ja.com

Facebookでのコードの再利用のためのミックスインとコンポーネントの使用React

私はBackboneプロジェクトでFacebook Reactを使用し始めていますが、これまでのところ非常にうまくいっています。
ただし、Reactコードに重複が忍び込んでいることに気付きました。

たとえば、私はINITIALSENDING、およびSENTのような状態のフォームに似たウィジェットをいくつか持っています。ボタンを押すと、フォームを検証し、要求を作成してから状態を更新する必要があります。状態はもちろん、フィールド値とともにReact this.state内に保持されます。

これらがバックボーンビューである場合、FormViewと呼ばれる基本クラスを抽出しますが、私の印象はReactはビューロジックを共有するサブクラス化を支持もサポートもしていなかったということでした (間違っている場合は修正してください)。

Reactでコードを再利用する2つのアプローチを見てきました。

Reactでの継承よりもミックスインとコンテナーが優先されることを修正できますか?これは意図的な設計決定ですか? 2番目の段落の「フォームウィジェット」の例にmixinまたはコンテナコンポーネントを使用する方が理にかなっていますか?

現在の状態にあるFeedbackWidgetJoinWidgetの要点 。それらは同様の構造、同様のbeginSendメソッドを持ち、両方とも何らかの検証サポートが必要です(まだありません)。

115
Dan Abramov

更新:この答えは時代遅れです。可能であれば、ミックスインから離れてください。警告しました!
Mixins Are Dead。Long Live Composition

最初は、これにサブコンポーネントを使用して、FormWidgetInputWidgetを抽出しようとしました。ただし、生成されたinputsとその状態をより適切に制御したいため、このアプローチを途中で中止しました。

最も助けになった2つの記事:

ValidationMixinFormMixinという2つの(異なる)ミックスインを書くだけでよいことがわかりました。
ここで、それらを分離しました。

ValidationMixin

バリデーションミックスインは、状態のプロパティの一部でバリデータ関数を実行し、「エラー」プロパティをstate.errors配列に格納する便利なメソッドを追加して、対応するフィールドを強調表示できるようにします。

ソース( Gist

define(function () {

  'use strict';

  var _ = require('underscore');

  var ValidationMixin = {
    getInitialState: function () {
      return {
        errors: []
      };
    },

    componentWillMount: function () {
      this.assertValidatorsDefined();
    },

    assertValidatorsDefined: function () {
      if (!this.validators) {
        throw new Error('ValidatorMixin requires this.validators to be defined on the component.');
      }

      _.each(_.keys(this.validators), function (key) {
        var validator = this.validators[key];

        if (!_.has(this.state, key)) {
          throw new Error('Key "' + key + '" is defined in this.validators but not present in initial state.');
        }

        if (!_.isFunction(validator)) {
          throw new Error('Validator for key "' + key + '" is not a function.');
        }
      }, this);
    },

    hasError: function (key) {
      return _.contains(this.state.errors, key);
    },

    resetError: function (key) {
      this.setState({
        'errors': _.without(this.state.errors, key)
      });
    },

    validate: function () {
      var errors = _.filter(_.keys(this.validators), function (key) {
        var validator = this.validators[key],
            value = this.state[key];

        return !validator(value);
      }, this);

      this.setState({
        'errors': errors
      });

      return _.isEmpty(errors);
    }
  };

  return ValidationMixin;

});

使用法

ValidationMixinには、validatehasError、およびresetErrorの3つのメソッドがあります。
クラスは、validatorsに似たpropTypesオブジェクトを定義することを想定しています。

var JoinWidget = React.createClass({
  mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],

  validators: {
    email: Misc.isValidEmail,
    name: function (name) {
      return name.length > 0;
    }
  },

  // ...

});

ユーザーが送信ボタンを押すと、validateを呼び出します。 validateを呼び出すと、各バリデーターが実行され、検証に失敗したプロパティのキーを含む配列がthis.state.errorsに入力されます。

renderメソッドでは、hasErrorを使用してフィールドの正しいCSSクラスを生成します。ユーザーがフィールド内にフォーカスを置くと、resetErrorを呼び出して、次のvalidate呼び出しまでエラーの強調表示を削除します。

renderInput: function (key, options) {
  var classSet = {
    'Form-control': true,
    'Form-control--error': this.hasError(key)
  };

  return (
    <input key={key}
           type={options.type}
           placeholder={options.placeholder}
           className={React.addons.classSet(classSet)}
           valueLink={this.linkState(key)}
           onFocus={_.partial(this.resetError, key)} />
  );
}

FormMixin

フォームミックスインは、フォームの状態(編集可能、送信中、送信済み)を処理します。これを使用して、リクエストの送信中に入力とボタンを無効にし、送信時に対応してビューを更新できます。

ソース( Gist

define(function () {

  'use strict';

  var _ = require('underscore');

  var EDITABLE_STATE = 'editable',
      SUBMITTING_STATE = 'submitting',
      SUBMITTED_STATE = 'submitted';

  var FormMixin = {
    getInitialState: function () {
      return {
        formState: EDITABLE_STATE
      };
    },

    componentDidMount: function () {
      if (!_.isFunction(this.sendRequest)) {
        throw new Error('To use FormMixin, you must implement sendRequest.');
      }
    },

    getFormState: function () {
      return this.state.formState;
    },

    setFormState: function (formState) {
      this.setState({
        formState: formState
      });
    },

    getFormError: function () {
      return this.state.formError;
    },

    setFormError: function (formError) {
      this.setState({
        formError: formError
      });
    },

    isFormEditable: function () {
      return this.getFormState() === EDITABLE_STATE;
    },

    isFormSubmitting: function () {
      return this.getFormState() === SUBMITTING_STATE;
    },

    isFormSubmitted: function () {
      return this.getFormState() === SUBMITTED_STATE;
    },

    submitForm: function () {
      if (!this.isFormEditable()) {
        throw new Error('Form can only be submitted when in editable state.');
      }

      this.setFormState(SUBMITTING_STATE);
      this.setFormError(undefined);

      this.sendRequest()
        .bind(this)
        .then(function () {
          this.setFormState(SUBMITTED_STATE);
        })
        .catch(function (err) {
          this.setFormState(EDITABLE_STATE);
          this.setFormError(err);
        })
        .done();
    }
  };

  return FormMixin;

});

使用法

コンポーネントは1つのメソッドsendRequestを提供することを期待しています。これはBluebirdのプロミスを返す必要があります。 (Qまたは他のpromiseライブラリで動作するように変更するのは簡単です。)

isFormEditableisFormSubmitting、およびisFormSubmittedなどの便利なメソッドを提供します。また、リクエストを開始するメソッドsubmitFormも提供します。フォームボタンのonClickハンドラーから呼び出すことができます。

108
Dan Abramov

私はReact(1年から生産中)でSPAを構築していますが、ミックスインはほとんど使用しません。

現在ミックスインの唯一のユースケースは、Reactのライフサイクルメソッド(componentDidMountなど)を使用する動作を共有する場合です。この問題は、Dan Abramovが link で話す(またはES6クラス継承を使用する)高次コンポーネントによって解決されます。

Mixinはフレームワークでもよく使用され、Reactの「非表示」 コンテキスト機能 を使用して、すべてのコンポーネントでフレームワークAPIを使用できるようにします。 ES6クラスの継承でもこれはもう必要ありません。


他のほとんどの場合、ミックスインが使用されますが、実際には必要ではなく、簡単なヘルパーで簡単に置き換えることができます。

例えば:

var WithLink = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={this.linkState('message')} />;
  }
});

LinkedStateMixinコードを非常に簡単にリファクタリングできるため、構文は次のようになります。

var WithLink = React.createClass({
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={LinkState(this,'message')} />;
  }
});

大きな違いはありますか?

4