在 FacebookReact 中使用 Mixins 与组件进行代码重用

我开始在一个 Backbone 项目中使用 Facebook React,到目前为止进展非常顺利。
然而,我注意到在我的 React 代码中出现了一些重复。

例如,具有状态如 INITIALSENDINGSENT我有几个类似表单的小部件。当按下按钮时,需要验证表单,发出请求,然后更新状态。状态当然保存在 React this.state中,以及字段值。

如果这些是 Backbone 视图,我将提取一个名为 FormView但是 my impression was that React neither endorses nor supports subclassing to share view logic的基类(如果我错了请纠正我)。

我在 React 中看到了两种代码重用的方法:

在 React 中,混合和容器优先于继承,对吗?这是一个经过深思熟虑的设计决定吗?对于第二段中的“表单小部件”示例,使用 Mixin 或容器组件是否更有意义?

下面是 FeedbackWidgetJoinWidget当前状态下的要点。它们有相似的结构,相似的 beginSend方法,并且都需要一些验证支持(还没有)。

30151 次浏览

更新: 这个答案已经过时了。如果可以的话,请远离 Mixin。 我警告过你!
混合物已死,成分万岁

首先,我尝试使用子组件来提取 FormWidgetInputWidget。然而,我中途放弃了这种方法,因为我想对生成的 input及其状态进行更好的控制。

对我帮助最大的两篇文章:

It turned out to that I only needed to write two (different) mixins: ValidationMixin and FormMixin.
Here's how I separated them.

验证 Mixin

验证 Mixin 添加了一些方便的方法,可以在状态的某些属性上运行验证器函数,并将“ error’d”属性存储在 state.errors数组中,这样就可以突出显示相应的字段。

资料来源(gist)

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有三种方法: validatehasErrorresetError
它期望类定义 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;


});

用法

It expects component to provide one method: sendRequest, which should return a Bluebird promise. (It's trivial to modify it to work with Q or other promise library.)

它提供了方便的方法,如 isFormEditableisFormSubmittingisFormSubmitted。它还提供了一个启动请求的方法: submitForm。您可以从表单按钮的 onClick处理程序调用它。

我正在用 React 构建一个 SPA (从1年前开始生产) ,而且我几乎从来不使用 misin。

我目前只有在需要共享使用 React 的生命周期方法(componentDidMount等)的行为时才使用混合。这个问题可以通过 Dan Abramov 在他的 链接(或使用 ES6类继承)中提到的高阶组件来解决。

Mixins are also often used in frameworks, to make framework API available to all the components, by using the "hidden" 上下文特征 of React. This won't be needed anymore either with ES6 class inheritance.


在其他大多数情况下,会使用 Mixin,但实际上并不需要,可以用简单的助手更容易地替换它。

For example:

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')} />;
}
});

有什么区别吗?