我开始在Backbone项目中使用Facebook React,到目前为止它的表现非常好 但是,我注意到我的React代码中出现了一些重复。
例如,我有几个类似于窗体的小部件,其中包含INITIAL
,SENDING
和SENT
等状态。按下按钮时,需要验证表单,发出请求,然后更新状态。当然,状态保留在React this.state
内,还有字段值。
如果这些是Backbone视图,我会提取一个名为FormView
的基类,但我的印象是React既不支持也不支持子类化以共享视图逻辑(如果我',请纠正我我错了。)
我在React中看到了两种代码重用方法:
我是否认为mixin和容器比React中的继承更受欢迎?这是一个刻意的设计决定吗? 使用mixin或容器组件为第二段中的“表单小部件”示例更有意义吗?
Here's a gist with FeedbackWidget
and JoinWidget
in their current state。它们具有类似的结构,类似beginSend
方法,并且都需要一些验证支持(还没有)。
答案 0 :(得分:107)
更新:这个答案已经过时了。如果可以,请远离mixins。 我警告过你!
的 Mixins Are Dead. Long Live Composition 强>
首先,我尝试使用子组件并提取FormWidget
和InputWidget
。但是,我中途放弃了这种方法,因为我希望更好地控制生成的input
及其状态。
两篇对我最有帮助的文章:
原来,我只需要写两个(不同的)mixins:ValidationMixin
和FormMixin
。
这是我如何分开它们。
验证mixin添加了方便的方法来在您的某些州的属性上运行验证器函数,并在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或其他promise库是微不足道的。)
它提供了便捷方法,例如isFormEditable
,isFormSubmitting
和isFormSubmitted
。它还提供了一种启动请求的方法:submitForm
。您可以从表单按钮“onClick
处理程序”中调用它。
答案 1 :(得分:4)
我正在用React构建一个SPA(从1年开始生产),我几乎从不使用mixins。
我目前对mixin的唯一用法是当你想要共享使用React的生命周期方法(componentDidMount
等)的行为时。 Dan Abramov在link(或使用ES6类继承)中谈论的高阶组件解决了这个问题。
Mixins也经常在框架中使用,通过使用React的“隐藏”context feature,使框架API可用于所有组件。使用ES6类继承时不再需要它。
大多数情况下,使用mixins,但并不是真的需要,可以用简单的助手代替。
例如:
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')} />;
}
});
有什么大不同吗?