Використання mixins vs компонентів для повторного використання коду у Facebook React


116

Я починаю використовувати Facebook React у проекті Backbone, і поки це йде дуже добре.
Однак я помітив деяке дублювання, яке повзе до мого коду React.

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

Якби це перегляди Backbone, я б вилучив базовий клас під назвою, FormViewале моє враження було, що React не підтримує і не підтримує підкласи для спільної логіки перегляду (виправте мене, якщо я помиляюся).

Я бачив два підходи до повторного використання коду в React:

Чи я правда, що міксини та контейнери вважають за краще успадковувати в React? Це навмисне дизайнерське рішення? Чи було б більше сенсу використовувати міксин або компонент контейнера для мого прикладу "форма віджета" з другого абзацу?

Ось суть FeedbackWidgetі JoinWidgetв їх нинішньому стані . Вони мають схожу структуру, схожий beginSendметод, і їм обом потрібно мати певну підтримку перевірки (ще не існує).


Як доповнення до цього - реагуйте, маючи на увазі другі думки щодо підтримки комбінацій у довгостроковому майбутньому, адже коли ваші напр. КомпонентDidMount просто магічно працює, реагує на щось складне, щоб вони не переписували один одного .. бо міксини дуже спрощений і не підходить за призначенням
Домінік

У мене не так багато досвіду роботи з React, але ви можете визначити свій власний міксин з функціями, які не перетинаються з простором імен фактичних об'єктів React. то просто викличте об'єктні функції "суперкласу" / композиції з ваших типових функцій компонентів React. то між функціями React та успадкованими функціями не перетинається. це допомагає зменшити деяку котельну плиту, але обмежує магію, що відбувається, і полегшує роботу самого React за кадром. чи справді це важко уявити? Сподіваюся, я дав зрозуміти.
Олександр Міллс

Міксини ніколи не помруть, тому що ви завжди можете просто робити DIY міксини. Реагувати просто не буде "рідної" підтримки для комбінацій, але ви все одно можете робити мікшування самостійно з нативним JS.
Олександр Міллс

Відповіді:


109

Оновлення: ця відповідь застаріла. Тримайтеся подалі від міксинів, якщо можете. Я тебе попередив!
Міксини мертві. Склад довгого життя

Спочатку я спробував використовувати для цього підкомпоненти і витягнути FormWidgetі InputWidget. Однак я відмовився від цього підходу на півдорозі, тому що хотів кращого контролю над генерованими inputта їх державою.

Дві статті, які мені найбільше допомогли:

Виявилося, що мені потрібно було написати лише два (різні) міксини: ValidationMixinі FormMixin.
Ось як я їх розділив.

ВалідаціяMixin

Валідація 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має три методи: 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

Форма 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 або іншою бібліотекою обіцянок.)

Він надає зручні методи , такі як 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} ... />
Дан Абрамов

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

4

Я будую SPA з React (у виробництві з 1 року), і майже ніколи не використовую міксини.

Єдиним випадком використання, який я зараз маю для комбінацій, є те, коли ви хочете поділитися поведінкою, яка використовує методи життєвого циклу React ( componentDidMountтощо). Цю проблему вирішують компоненти вищого порядку, про які Дан Абрамов говорить у своєму посиланні (або за допомогою успадкування класу ES6).

Mixins також часто використовується в рамках, щоб зробити API 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 насправді пояснюють, як це зробити без mixin. Цей конкретний міксин - це справді лише трохи синтаксичного цукру.
nextgentech
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.