Pembaruan: jawaban ini sudah ketinggalan zaman. Jauhi mixin jika Anda bisa. Saya memperingatkan Anda!
Mixins Are Dead. Komposisi Umur Panjang
Pada awalnya, saya mencoba menggunakan subkomponen untuk ini dan mengekstrak FormWidget
dan InputWidget
. Namun, saya mengabaikan pendekatan ini setengah jalan karena saya ingin kontrol yang lebih baik atas yang dihasilkan input
dan statusnya.
Dua artikel yang paling membantu saya:
Ternyata saya hanya perlu menulis dua mixin (berbeda): ValidationMixin
dan FormMixin
.
Begini cara saya memisahkan mereka.
ValidationMixin
Mixin validasi menambahkan metode praktis untuk menjalankan fungsi validator Anda pada beberapa properti negara bagian Anda dan menyimpan properti "error'd" dalam state.errors
larik sehingga Anda dapat menyorot kolom terkait.
Sumber ( inti )
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;
});
Pemakaian
ValidationMixin
memiliki tiga metode: validate
, hasError
dan resetError
.
Ini mengharapkan kelas untuk mendefinisikan validators
objek, mirip dengan propTypes
:
var JoinWidget = React.createClass({
mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
validators: {
email: Misc.isValidEmail,
name: function (name) {
return name.length > 0;
}
},
// ...
});
Ketika pengguna menekan tombol pengiriman, saya menelepon validate
. Panggilan ke validate
akan menjalankan setiap validator dan terisi this.state.errors
dengan array yang berisi kunci properti yang gagal validasi.
Dalam render
metode saya , saya gunakan hasError
untuk menghasilkan kelas CSS yang benar untuk bidang. Ketika pengguna menempatkan fokus di dalam lapangan, saya memanggil resetError
untuk menghapus sorotan kesalahan hingga validate
panggilan berikutnya .
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
Form mixin menangani status formulir (dapat diedit, dikirim, dikirim). Anda dapat menggunakannya untuk menonaktifkan input dan tombol saat permintaan dikirim, dan untuk memperbarui tampilan Anda secara bersamaan saat permintaan dikirim.
Sumber ( inti )
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;
});
Pemakaian
Ia mengharapkan komponen untuk menyediakan satu metode:, sendRequest
yang harus mengembalikan janji Bluebird. (Sangat mudah untuk memodifikasinya agar berfungsi dengan Q atau library promise lainnya.)
Ini memberikan metode kenyamanan seperti isFormEditable
, isFormSubmitting
dan isFormSubmitted
. Hal ini juga menyediakan metode untuk memulai permintaan: submitForm
. Anda dapat menyebutnya dari onClick
penangan tombol formulir .