首页 文章

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

提问于
浏览
113

我'm beginning to use Facebook React in a Backbone project and so far it'真的很顺利 .
但是,我注意到我的React代码中出现了一些重复 .

例如, I have several form-like widgets 具有 INITIALSENDINGSENT 等状态 . 按下按钮时,需要验证表单,发出请求,然后更新状态 . 当然,状态保存在React this.state 中,以及字段值 .

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

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

我是否认为mixin和容器比React中的继承更受欢迎?这是一个刻意的设计决定吗? Would it make more sense to use a mixin or a container component for my “form widget” example from second paragraph?

Here's a gist with FeedbackWidget and JoinWidget in their current state . 它们具有类似的结构,类似 beginSend 方法,并且都需要一些验证支持(还没有) .

2 回答

  • 106

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

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

    两篇最能帮助我的文章:

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

    ValidationMixin

    验证mixin添加了方便的方法来在 state.errors 数组中的某些状态's properties and store “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;
    
    });
    

    用法

    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

    表单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库是微不足道的 . )

    它提供了方便的方法,如 isFormEditableisFormSubmittingisFormSubmitted . 它还提供了启动请求的方法: submitForm . 你可以从表单按钮' onClick handler中调用它 .

  • 4

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

    我目前用于mixin的唯一用例是当你想要共享使用React的生命周期方法( componentDidMount 等)的行为时 . Dan Abramov在他的link(或使用ES6类继承)中谈论的高阶组件解决了这个问题 .

    Mixins也经常在框架中使用,通过使用React的"hidden" 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')} />;
      }
    });
    

    有什么大不同吗?

相关问题