1. ホーム
  2. javascript

[解決済み] Facebook Reactでコードを再利用するためのミキシンとコンポーネントの使い分け

2022-05-15 22:52:18

質問

BackboneプロジェクトでFacebook Reactを使い始めていますが、今のところとてもうまくいっています。

しかし、私はReactのコードに忍び寄るいくつかの重複に気づきました。

例えば フォームのようなウィジェットがいくつかあるのですが のような状態を持つ INITIAL , SENDINGSENT . ボタンが押されると、フォームの検証、リクエスト、そしてステートの更新が必要です。ステートはReactの内部で保持されます this.state の内部に保持されます。

もしこれがBackboneのビューであれば、私はこのクラスから FormView を抽出したはずですが 私の印象では、Reactはビューロジックを共有するためのサブクラス化を推奨もサポートもしていません。 (ということです(間違っていたら訂正してください)。

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

Reactでは継承よりもミキシンとコンテナが優先されるということでよろしいでしょうか? これは意図的な設計上の決定なのでしょうか? 2段落目の「フォームウィジェット」の例では、ミキシンとコンテナコンポーネントのどちらを使用するのがより理にかなっているのでしょうか?

これは gist で FeedbackWidgetJoinWidget を現在の状態で . これらは同じような構造を持ち、同じような beginSend メソッドがあり、両方とも何らかの検証サポートが必要です (まだありません)。

どのように解決するのですか?

更新:この回答は古くなっています。できればミキシンは使わないでください。 私はあなたに警告しました!

Mixins Are Dead. コンポジション万歳

最初は、これをサブコンポーネントを使って、抽出しようとしたのですが FormWidgetInputWidget . しかし、私はこの方法を途中で断念しました。なぜなら、生成された input とその状態をよりよく制御したいと思ったからです。

一番役に立った記事2つ

結局、2つの(異なる)mixinを書けばいいことがわかりました。 ValidationMixinFormMixin .

こんな感じで分けてみました。

バリデーションミキシン

バリデーションミキシンは、バリデータ関数をステートのプロパティに対して実行するための便利なメソッドを追加し、「エラーが発生した」プロパティを state.errors 配列に保存し、対応するフィールドをハイライトできるようにします。

ソース ( 要旨 )

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 には3つのメソッドがあります。 validate , hasErrorresetError .

これは、クラスが 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)} />
  );
}

フォームミキサー

フォームミキシンはフォームの状態(編集可能、送信、投稿)を処理します。リクエスト送信中は入力やボタンを無効にし、送信されたらそれに応じてビューを更新するために使用することができます。

ソース ( 要点 )

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 このメソッドはブルーバードのプロミスを返す必要があります。(Qや他のプロミスライブラリで動作するように修正するのは些細なことです。)

これは以下のような便利なメソッドを提供します。 isFormEditable , isFormSubmittingisFormSubmitted . また、リクエストをキックオフするためのメソッドも用意されています。 submitForm . このメソッドは、フォームボタンの onClick ハンドラです。