使用mixins vs组件在Facebook React中重用代码

Dan*_*mov 115 javascript refactoring mixins reactjs

我开始在Backbone项目中使用Facebook React,到目前为止它的表现非常好.
但是,我注意到我的React代码中出现了一些重复.

例如,我有几个类似于窗体的小部件,其状态为INITIAL,SENDINGSENT.按下按钮时,需要验证表单,发出请求,然后更新状态.this.state当然,状态保持在React内部,以及字段值.

如果这些是Backbone视图,我会提取一个被调用的基类,FormView我的印象是React既不支持也不支持子类化来共享视图逻辑(如果我错了,请纠正我).

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

我是否认为mixin和容器比React中的继承更受欢迎?这是一个刻意的设计决定吗? 将mixin或容器组件用于第二段中的"表单小部件"示例会更有意义吗?

这里有一个要点FeedbackWidget,并JoinWidget在其当前状态.它们具有类似的结构,类似的beginSend方法,并且都需要一些验证支持(还没有).

Dan*_*mov 108

更新:这个答案已经过时了.如果可以,请远离mixins.我警告过你!
Mixins死了.万岁作文

起初,我尝试使用子组件,并提取FormWidgetInputWidget.但是,我中途放弃了这种方法,因为我希望更好地控制生成的inputs及其状态.

两篇最能帮助我的文章:

事实证明,我只需要编写两个(不同的)mixins:ValidationMixinFormMixin.
这是我如何分开它们.

ValidationMixin

验证mixin添加了方便的方法来在您的某些州的属性上运行验证器函数,并在state.errors数组中存储"error'd"属性,以便您可以突出显示相应的字段.

来源(要点)

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;

});
Run Code Online (Sandbox Code Playgroud)

用法

ValidationMixin有三种方法:validate,hasErrorresetError.
它期望类定义validators对象,类似于propTypes:

var JoinWidget = React.createClass({
  mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],

  validators: {
    email: Misc.isValidEmail,
    name: function (name) {
      return name.length > 0;
    }
  },

  // ...

});
Run Code Online (Sandbox Code Playgroud)

当用户按下提交按钮时,我会打电话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)} />
  );
}
Run Code Online (Sandbox Code Playgroud)

FormMixin

表单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;

});
Run Code Online (Sandbox Code Playgroud)

用法

它期望组件提供一种方法:sendRequest它应该返回一个Bluebird的承诺.(修改它以使用Q或其他promise库是微不足道的.)

它提供了方便的方法,如isFormEditable,isFormSubmittingisFormSubmitted.它还提供了一种启动请求的方法:submitForm.你可以从表单按钮的onClick处理程序中调用它.

  • @NilColor还没有,我对它不太满意.:-)目前我有`FormInput`通过`formLink`与它的主人交谈.`formLink`就像[`valueLink`](http://facebook.github.io/react/docs/two-way-binding-helpers.html),并从`FormMixin`的`linkValidatedState(name,验证器)`方法.`FormInput`从`formLink.value`获取它的值并调用`formLink.requestBlur`和`formLink.requestFocus`-它们在`FormMixin`中导致验证.最后,为了自定义用于输入的实际组件,我可以将它传递给`FormInput`:`<FormInput component = {React.DOM.textarea} ... />` (3认同)
  • @jmcejuela事实上我后来采用了更多的组件方法(仍然大量使用mixins),我可能会在某些时候扩展它. (2认同)