Я начинаю использовать Facebook React в проекте Backbone, и пока все идет хорошо. Тем не менее, я заметил, что некоторые дублирования ползут в моем коде React.
Например, У меня есть несколько виджетов вида с такими состояниями, как INITIAL
, SENDING
и SENT
. При нажатии кнопки форма должна быть проверена, делается запрос, а затем состояние обновляется. Состояние сохраняется внутри React this.state
, конечно, вместе с значениями поля.
Если бы это были представления Backbone, я бы извлек базовый класс под названием FormView
, но мое впечатление было то, что React не поддерживает и не поддерживает подклассы, чтобы обмениваться логикой представления (исправьте меня, если я ошибаюсь).
Я видел два подхода к повторному использованию кода в React:
Правильно ли, что миксины и контейнеры предпочтительнее наследования в React? Это преднамеренное дизайнерское решение? Было бы разумнее использовать mixin или компонент контейнера для моего примера виджета формы из второго абзаца?
Вот суть с FeedbackWidget
и JoinWidget
в их текущем состоянии. Они имеют аналогичную структуру, аналогичный метод beginSend
, и оба должны иметь некоторую поддержку проверки (еще нет).
Обновление: этот ответ устарел. Держитесь подальше от миксинов, если сможете. Я предупреждал тебя! Микшины мертвы. Длительный живой состав
Сначала я попытался использовать подкомпоненты для этого и извлечь FormWidget
и InputWidget
. Тем не менее, я отказался от этого подхода на полпути, потому что мне нужен лучший контроль над сгенерированными input
и их состоянием.
Две статьи, которые помогли мне больше всего:
Оказалось, что мне нужно было написать только два (разных) mixins: ValidationMixin
и FormMixin
.
Вот как я их отделил.
Validation mixin добавляет удобные методы для запуска ваших функций проверки правильности некоторых свойств вашего состояния и хранения свойств "error'd" в массиве 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
.
Он ожидает, что класс определит объект 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)} />
);
}
Форма 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;
});
Он ожидает, что компонент предоставит один метод: sendRequest
, который должен вернуть обещание Bluebird. (Это тривиально, чтобы изменить его для работы с Q или другой библиотекой обещаний.)
Он предоставляет удобные методы, такие как isFormEditable
, isFormSubmitting
и isFormSubmitted
. Он также предоставляет метод для запуска запроса: submitForm
. Вы можете вызвать его из обработчика формы onClick
.
Я создаю SPA с реактивом (в производстве с 1 года), и я почти никогда не использую mixins.
Единственное, что я сейчас использую для mixins, - это когда вы хотите делиться поведением, которое использует методы жизненного цикла React (componentDidMount
и т.д.). Эта проблема решается компонентами более высокого порядка, которые Дэн Абрамов говорит в своей ссылке (или с помощью наследования класса ES6).
Mixins также часто используются в рамках, чтобы сделать API-интерфейс Framework доступным для всех компонентов, используя контекстную функцию , Это больше не понадобится либо с наследованием класса 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')} />;
}
});
Есть ли какая-то большая разница?