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

2022-08-30 05:18:12

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

例如,我有几个类似表单的小部件,其状态如 、 和 。按下按钮时,需要验证表单,发出请求,然后更新状态。当然,状态与字段值一起保存在 React 中。INITIALSENDINGSENTthis.state

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

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

在 React 中,mixin 和容器比继承更可取,我说得对吗?这是一个深思熟虑的设计决策吗?在第二段中的“表单小部件”示例中使用mixin或容器组件会更有意义吗?

下面是反馈小数和 JoinWidget 处于当前状态的要点。它们具有类似的结构,类似的方法,并且都需要有一些验证支持(尚未实现)。beginSend


答案 1

更新:这个答案已经过时了。如果可以的话,远离混杂物。我警告过你!
Mixins Dead。构图万岁

起初,我尝试为此使用子组件,并提取和.但是,我半途而废了这种方法,因为我希望更好地控制生成的 s 及其状态。FormWidgetInputWidgetinput

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

事实证明,我只需要写两个(不同的)mixins:和。
以下是我如何将它们分开。ValidationMixinFormMixin

验证微辛

验证 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有三种方法:和 。
它期望类定义对象,类似于:validatehasErrorresetErrorvalidatorspropTypes

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

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

  // ...

});

当用户按下提交按钮时,我呼叫 。对 的调用将运行每个验证程序,并填充一个数组,其中包含未通过验证的属性的键。validatevalidatethis.state.errors

在我的方法中,我用于为字段生成正确的CSS类。当用户将焦点放在字段内时,我调用以删除错误突出显示,直到下一次调用。renderhasErrorresetErrorvalidate

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;

});

用法

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

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


答案 2

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

我目前对 mixins 的唯一用例是当你想要共享使用 React 生命周期方法的行为时( 等等)。这个问题可以通过Dan Abramov在他的链接中谈论的高阶组件(或使用ES6类继承)来解决。componentDidMount

Mixins也经常用于框架中,通过使用 React 的“隐藏”上下文功能,使框架 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')} />;
  }
});

有什么大不了的区别吗?