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


116

私はバックボーンプロジェクトでFacebook Reactを使用し始めており、これまでのところ、それは本当に順調です。
しかし、Reactコードに重複が侵入していることに気づきました。

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

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

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

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

ここでの要点だFeedbackWidgetJoinWidgetそれらの現在の状態では。それらは類似した構造、類似したbeginSendメソッドを持ち、両方ともいくつかの検証サポートが必要です(まだありません)。


これの更新として、reactは、将来的にMixinのサポートについて考え直します。たとえば、componentDidMountがすべて魔法のように機能しているとき、reactは複雑な処理を行っているため、相互に上書きされません。単純すぎて目的に適合しない
ドミニク

Reactの経験はあまりありませんが、実際のReactオブジェクトの名前空間と重複しない関数を使用して独自のミックスインを定義できます。次に、通常のReactコンポーネント関数から「スーパークラス」/構成オブジェクト関数を呼び出すだけです。その場合、React関数と継承された関数の間に重複はありません。これは定型文を減らすのに役立ちますが、発生する魔法を制限し、React自体が舞台裏で操作しやすくなります。これは本当に想像するのは難しいですか?私は自分自身を明確にしたいと思います。
Alexander Mills

いつでもDIYのミックスインを作成できるため、ミックスインが死ぬことはありません。Reactはミックスインを「ネイティブ」でサポートしませんが、ネイティブJSを使用して自分でミックスインを実行できます。
Alexander Mills

回答:


109

更新:この回答は古くなっています。可能であれば、ミックスインに近づかないでください。警告しました!
ミックスインは死んだ。長生きする作曲

最初に、これにサブコンポーネントを使用して、とを抽出しようFormWidgetとしましたInputWidget。ただし、生成されたinputとその状態をより適切に制御する必要があったため、このアプローチを途中で中止しました。

最も役立つ2つの記事:

それは私が2つだけ(別の)ミックスインを書くために必要なことが判明:ValidationMixinFormMixin
これが私がそれらを分離する方法です。

ValidationMixin

検証ミックスインは、状態のプロパティの一部で検証関数を実行し、「エラーが発生した」プロパティを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つのメソッドvalidatehasErrorありresetErrorます。
次の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

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

ソース(要旨

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または他のpromiseライブラリで動作するように変更するのは簡単です。)

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


2
私は...、私はいくつかの点で、この上で展開されますに(まだ重くミックスインを使用して)、後でもっとコンポーネントっぽいアプローチに移動実際に@jmcejuela
ダン・アブラモフ

1
「よりコンポーネントっぽいアプローチ」についてのニュースはありますか?
NilColor 2014

3
@NilColorまだ、私はそれに満足していません。:-)現在、私はFormInputを介してその所有者と話し合っていformLinkます。formLinkのようなものでvalueLinkFormMixinlinkValidatedState(name, validator)メソッドから返されます。FormInputから値を取得しformLink.value、呼び出してformLink.requestBlur、でformLink.requestFocus検証を行いFormMixinます。最後に、入力のために使用されている実際のコンポーネントをカスタマイズするために、私はそれを渡すことができますFormInput<FormInput component={React.DOM.textarea} ... />
ダン・アブラモフ

いい答え-いくつかのヒント:doneブルーバードで呼び出す必要はなく 、コードはQ(またはネイティブプロミス)でそのまま機能します-もちろんブルーバードの方が優れています。また、構文が回答後にReactで変更されたことにも注意してください。
Benjamin Gruenbaum 2015年

4

Reactを使用してSPAを構築しており(1年から運用中)、ミックスインを使用することはほとんどありません。

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

ミックスインはフレームワークでもよく使用され、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')} />;
  }
});

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


あなたは正しいです。実際、LinkedStateMixinのドキュメントは、実際にはミックスインなしでそれを行う方法を詳しく説明しています。この特定のミックスインは、実際にはほんの少しの構文糖です。
nextgentech、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.