Использование миксинов и компонентов для повторного использования кода в Facebook React


116

Я начинаю использовать Facebook React в проекте Backbone, и пока все идет очень хорошо.
Однако я заметил, что в мой код React закрадывается некоторое дублирование.

Например, у меня есть несколько виджетов, похожих на формы, с такими состояниями, как INITIAL, SENDINGи SENT. Когда кнопка нажата, необходимо проверить форму, сделать запрос, а затем обновить состояние. Состояние, this.stateконечно же, сохраняется в React вместе со значениями полей.

Если бы это были представления Backbone, я бы извлек базовый класс, называемый, FormViewно у меня сложилось впечатление, что React не поддерживает и не поддерживает подклассы для совместного использования логики представления (поправьте меня, если я ошибаюсь).

Я видел два подхода к повторному использованию кода в React:

Правильно ли я, что миксины и контейнеры предпочтительнее наследования в React? Это осознанное дизайнерское решение? Было бы лучше использовать миксин или компонент-контейнер для моего примера «виджета формы» из второго абзаца?

Вот суть FeedbackWidgetи JoinWidgetв их текущем состоянии . У них схожая структура, аналогичный beginSendметод, и обоим потребуется некоторая поддержка валидации (пока еще нет).


В качестве обновления к этому - у React есть сомнения о поддержке миксинов в долгом будущем, потому что, когда ваши, например, componentDidMount все просто волшебно работают, реакция делает некоторые сложные вещи, поэтому они не перезаписывают друг друга .. потому что миксины очень упрощенный и не соответствующий цели
Доминик

У меня не так много опыта работы с React, но вы можете определить свой собственный миксин с функциями, которые не перекрываются с пространством имен реальных объектов React. затем просто вызовите функции объекта "суперкласс" / композиции из ваших типичных функций компонентов React. тогда функции React не пересекаются с унаследованными функциями. это помогает уменьшить количество шаблонов, но ограничивает волшебство и упрощает работу React за кулисами. неужели это так сложно представить? Надеюсь, я ясно выразился.
Alexander Mills

Миксины никогда не умрут, потому что вы всегда можете просто сделать миксины своими руками. В React просто не будет «нативной» поддержки миксинов, но вы все равно можете делать миксины самостоятельно с помощью собственного JS.
Alexander Mills

Ответы:


109

Обновление: этот ответ устарел. По возможности держитесь подальше от миксинов. Я предупреждал тебя!
Миксины мертвы. Да здравствует композиция

Сначала я пробовал использовать для этого подкомпоненты и извлекать FormWidgetи InputWidget. Однако я отказался от этого подхода на полпути, потому что хотел лучше контролировать сгенерированные inputs и их состояние.

Две статьи, которые мне больше всего помогли:

  • Думая о React, я понял, что на самом деле мне не нужны для этого вложенные компоненты;
  • У Reusable Components есть отличный пример миксина.

Оказалось, что мне нужно было написать всего два (разных) миксина: ValidationMixinи FormMixin.
Вот как я их разделил.

ValidationMixin

Миксин валидации добавляет удобные методы для запуска ваших функций валидатора для некоторых свойств вашего состояния и сохранения свойств с ошибками в 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имеет три метода: validate, hasErrorи resetError.
Он ожидает, что класс определит 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. (Его легко изменить для работы с Q или другой библиотекой обещаний.)

Он предоставляет удобные методы, такие как isFormEditable, isFormSubmittingи isFormSubmitted. Она также обеспечивает способ пнуть запрос: submitForm. Вызвать его можно из onClickобработчика кнопок формы .


2
@jmcejuela На самом деле, позже я перешел на более компонентный подход (все еще активно использую миксины), я мог бы расширить это в какой-то момент ..
Дэн Абрамов

1
Есть какие-нибудь новости о «более компонентном подходе»?
NilColor

3
@NilColor Пока нет, я не совсем доволен. :-) В настоящее время я FormInputразговариваю со своим владельцем через formLink. formLinkподобно valueLink, и возвращаются из FormMixin«S linkValidatedState(name, validator)методы. FormInputполучает свое значение от formLink.valueи вызывает formLink.requestBlurи formLink.requestFocus- они вызывают проверку в FormMixin. Наконец, чтобы настроить фактический компонент, используемый для ввода, я могу передать его FormInput:<FormInput component={React.DOM.textarea} ... />
Дэн Абрамов

Хороший ответ - несколько советов: вам не нужно вызывать donebluebird, и код будет работать, как в Q (или собственных обещаниях) - конечно, bluebird лучше. Также обратите внимание, что с момента ответа синтаксис в React изменился.
Бенджамин Грюнбаум

4

Я создаю SPA с React (в производстве уже 1 год) и почти никогда не использую миксины.

Единственный вариант использования миксинов, который у меня сейчас есть, - это когда вы хотите поделиться поведением, которое использует методы жизненного цикла React (и componentDidMountт. Д.). Эта проблема решается Компонентами высшего порядка, о которых Дэн Абрамов говорит в своей ссылке. (или с помощью наследования классов ES6).

Миксины также часто используются во фреймворках, чтобы сделать API фреймворка доступным для всех компонентов с помощью «скрытой» контекстной функции React. Это больше не понадобится и с наследованием классов ES6.


В большинстве случаев используются миксины, но они на самом деле не нужны, и их можно было бы проще заменить простыми помощниками.

Например:

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 фактически объясняют, как это сделать без примеси. Этот конкретный миксин на самом деле просто немного синтаксического сахара.
nextgentech
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.