私はBackboneプロジェクトでFacebook Reactを使用し始めていますが、これまでのところ非常にうまくいっています。
ただし、Reactコードに重複が忍び込んでいることに気付きました。
たとえば、私はINITIAL
、SENDING
、およびSENT
のような状態のフォームに似たウィジェットをいくつか持っています。ボタンを押すと、フォームを検証し、要求を作成してから状態を更新する必要があります。状態はもちろん、フィールド値とともにReact this.state
内に保持されます。
これらがバックボーンビューである場合、FormView
と呼ばれる基本クラスを抽出しますが、私の印象はReactはビューロジックを共有するサブクラス化を支持もサポートもしていなかったということでした (間違っている場合は修正してください)。
Reactでコードを再利用する2つのアプローチを見てきました。
Reactでの継承よりもミックスインとコンテナーが優先されることを修正できますか?これは意図的な設計決定ですか? 2番目の段落の「フォームウィジェット」の例にmixinまたはコンテナコンポーネントを使用する方が理にかなっていますか?
現在の状態にあるFeedbackWidget
とJoinWidget
の要点 。それらは同様の構造、同様のbeginSend
メソッドを持ち、両方とも何らかの検証サポートが必要です(まだありません)。
更新:この答えは時代遅れです。可能であれば、ミックスインから離れてください。警告しました!
Mixins Are Dead。Long Live Composition
最初は、これにサブコンポーネントを使用して、FormWidget
とInputWidget
を抽出しようとしました。ただし、生成されたinput
sとその状態をより適切に制御したいため、このアプローチを途中で中止しました。
最も助けになった2つの記事:
ValidationMixin
とFormMixin
という2つの(異なる)ミックスインを書くだけでよいことがわかりました。
ここで、それらを分離しました。
バリデーションミックスインは、状態のプロパティの一部でバリデータ関数を実行し、「エラー」プロパティを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
には、validate
、hasError
、および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)} />
);
}
フォームミックスインは、フォームの状態(編集可能、送信中、送信済み)を処理します。これを使用して、リクエストの送信中に入力とボタンを無効にし、送信時に対応してビューを更新できます。
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ライブラリで動作するように変更するのは簡単です。)
isFormEditable
、isFormSubmitting
、およびisFormSubmitted
などの便利なメソッドを提供します。また、リクエストを開始するメソッドsubmitForm
も提供します。フォームボタンのonClick
ハンドラーから呼び出すことができます。
私は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')} />;
}
});
大きな違いはありますか?