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


116

我开始在Backbone项目中使用Facebook React,到目前为止一切进展顺利。
但是,我注意到一些重复出现在我的React代码中。

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

如果这些是Backbone视图,我将提取一个名为的基类,FormView我的印象是React既不认可也不支持子类共享视图逻辑(如果我错了,请纠正我)。

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

我是否正确认为Mixins和容器在React中优先于继承?这是故意设计的决定吗? 在第二段的“表单小部件”示例中使用混合或容器组件是否更有意义?

这里有一个要点FeedbackWidget,并JoinWidget在其当前状态。它们具有相似的结构,相似的beginSend方法,并且都将需要某种验证支持(尚不存在)。


作为对此的更新,React在长远的将来考虑支持混合功能,因为当您的componentDidMount等都神奇地工作时,React会做一些复杂的事情,因此它们不会互相覆盖。简单化且不适合目标
多米尼克2015年

我对React没有太多的经验,但是您可以使用不与实际React对象的名称空间重叠的函数定义自己的mixin。然后只需从典型的React组件函数中调用“超类” /组成对象函数即可。那么React函数和继承的函数之间就没有重叠。这有助于减少一些样板,但可以限制发生的魔力,并使React本身更容易在幕后进行操作。这真的很难想象吗?我希望我能说清楚。
亚历山大·米尔斯

Mixins不会消失,因为您总是可以制作DIY mixins。React只是没有对mixin的“本机”支持,但是您仍然可以使用本机JS自己进行mixins。
亚历山大·米尔斯

Answers:


109

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

首先,我尝试为此使用子组件并提取FormWidgetInputWidget。但是,我中途放弃了这种方法,因为我想更好地控制生成的inputs及其状态。

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

事实证明,我只需要编写两个(不同的)mixin:ValidationMixinFormMixin
这是我分开它们的方式。

验证Mixin

验证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有三种方法: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)} />
  );
}

FormMixin

表单混合处理表单状态(可编辑,提交,已提交)。您可以使用它在发送请求时禁用输入和按钮,并在发送请求时相应地更新视图。

来源(要点

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 Promise。(修改它以与Q或其他Promise库一起使用是微不足道的。)

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


2
@jmcejuela事实上,我搬到了多个组件上下的方法后(仍采用混入重),我可能会在某个时候对这个扩展..
丹·阿布拉莫夫

1
是否有关于“更多采用分量化方法”的新闻?
NilColor 2014年

3
@NilColor尚未,我对此不太满意。:-)目前,我已经FormInput通过与它的所有者进行了交谈formLinkformLink类似于valueLink,并且是从FormMixinlinkValidatedState(name, validator)方法返回的。FormInput从获得其价值formLink.value,并呼吁formLink.requestBlurformLink.requestFocus中-他们的事业验证FormMixin。最后,要自定义用于输入的实际组件,我可以将其传递给FormInput<FormInput component={React.DOM.textarea} ... />
Dan Abramov 2014年

好的答案-一些提示:您不必调用 donebluebird,并且代码将按Q(或本机承诺)的方式工作-当然,bluebird更好。另外请注意,自回答以来,语法已在React中更改。
本杰明·格伦鲍姆

4

我正在用React构建SPA(已投入生产1年),而且我几乎从不使用mixins。

我目前对mixin的唯一用例是当您想共享使用React生命周期方法(componentDidMount等)的行为时。Dan Abramov在其链接中使用的高阶组件(或通过使用ES6类继承)解决了此问题。

通过使用React 的“隐藏” 上下文功能,在框架中还经常使用Mixins,以使框架API可用于所有组件。ES6类继承不再需要此功能。


在其他大多数情况下,都使用了mixin,但并不是真正需要的,可以用简单的助手轻松替换。

例如:

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

有什么大不同吗?


你是对的。实际上,LinkedStateMixin文档实际上说明了没有mixin的情况。这种特殊的mixin实际上只是一点语法糖。
nextgentech
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.