Verwenden von Mixins vs Komponenten für die Wiederverwendung von Code in Facebook React

116

Ich fange an, Facebook React in einem Backbone-Projekt zu verwenden, und bis jetzt läuft es wirklich gut.
Ich bemerkte jedoch einige Duplikate in meinem React-Code.

Zum Beispiel habe ich mehrere form wie Widgets mit Staaten wie INITIAL, SENDINGund SENT. Wenn eine Schaltfläche gedrückt wird, muss das Formular validiert, eine Anforderung gestellt und der Status aktualisiert werden. Der Status bleibt this.statenatürlich zusammen mit den Feldwerten in React .

Wenn dies Backbone-Ansichten wären, hätte ich eine Basisklasse namens aufgerufen, FormViewaber ich hatte den Eindruck, dass React Unterklassen für die gemeinsame Ansichtslogik weder unterstützt noch unterstützt (korrigieren Sie mich, wenn ich falsch liege).

Ich habe in React zwei Ansätze zur Wiederverwendung von Code gesehen:

Stimmt es, dass Mixins und Container der Vererbung in React vorgezogen werden? Ist das eine bewusste Designentscheidung? Wäre es sinnvoller, ein Mixin oder eine Containerkomponente für mein Beispiel „Formular-Widget“ aus dem zweiten Absatz zu verwenden?

Hier ist ein Kern mit FeedbackWidgetund JoinWidgetin ihrem aktuellen Zustand . Sie haben eine ähnliche Struktur, eine ähnliche beginSendMethode und beide benötigen eine gewisse Validierungsunterstützung (noch nicht vorhanden).

Dan Abramov
quelle
Als Update zu diesem - Reagieren haben Sie Bedenken, Mixins in der langen Zukunft zu unterstützen, denn wenn Ihre zB componentDidMount's alle nur magisch funktionieren, macht React einige komplizierte Dinge, damit sie sich nicht gegenseitig überschreiben. Weil Mixins sehr sind simpel und nicht zweckmäßig
Dominic
Ich habe nicht so viel Erfahrung mit React, aber Sie können Ihr eigenes Mixin mit Funktionen definieren, die sich nicht mit dem Namespace der tatsächlichen React-Objekte überschneiden. Rufen Sie dann einfach die Funktionen "Superklasse" / Kompositionsobjekt aus Ihren typischen React-Komponentenfunktionen auf. dann gibt es keine Überlappung zwischen Reaktionsfunktionen und geerbten Funktionen. Dies hilft, einige Boilerplates zu reduzieren, begrenzt jedoch das magische Geschehen und erleichtert es React selbst, hinter den Kulissen zu agieren. ist das wirklich so schwer vorstellbar? Ich hoffe ich habe mich klar gemacht.
Alexander Mills
Mixins werden niemals sterben, weil Sie immer nur DIY-Mixins machen können. React hat keine "native" Unterstützung für Mixins, aber Sie können Mixins trotzdem selbst mit nativem JS erstellen.
Alexander Mills

Antworten:

109

Update: Diese Antwort ist veraltet. Halte dich von den Mixins fern, wenn du kannst. Ich habe dich gewarnt!
Mixins sind tot. Es lebe die Komposition

Zuerst versuchte ich Subkomponenten für dieses und Extrakt zu verwenden FormWidgetund InputWidget. Ich habe diesen Ansatz jedoch auf halbem Weg aufgegeben, weil ich eine bessere Kontrolle über die generierten inputs und ihren Zustand haben wollte.

Zwei Artikel, die mir am meisten geholfen haben:

Es stellte sich heraus, dass ich nur zwei (verschiedene) Mixins schreiben musste: ValidationMixinund FormMixin.
So habe ich sie getrennt.

ValidationMixin

Das Validierungsmixin fügt praktische Methoden hinzu, mit denen Sie Ihre Validierungsfunktionen für einige Eigenschaften Ihres Staates ausführen und "fehlerhafte" Eigenschaften in einem state.errorsArray speichern können, damit Sie entsprechende Felder hervorheben können.

Quelle ( Kern )

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;

});

Verwendung

ValidationMixindrei Methoden hat: validate, hasErrorund resetError.
Es wird erwartet, dass die Klasse ein validatorsObjekt definiert , ähnlich wie propTypes:

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

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

  // ...

});

Wenn der Benutzer die Übermittlungstaste drückt, rufe ich an validate. Bei einem Aufruf von validatewird jeder Validator ausgeführt und this.state.errorsmit einem Array gefüllt, das Schlüssel der Eigenschaften enthält, bei denen die Validierung fehlgeschlagen ist.

In meiner renderMethode hasErrorgeneriere ich die richtige CSS-Klasse für Felder. Wenn der Benutzer den Fokus in das Feld legt, rufe ich resetErroran, um die Fehlerhervorhebung bis zum nächsten validateAufruf zu entfernen .

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

Das Formular-Mixin behandelt den Formularstatus (bearbeitbar, übermittelnd, übermittelt). Sie können es verwenden, um Eingaben und Schaltflächen zu deaktivieren, während eine Anforderung gesendet wird, und um Ihre Ansicht beim Senden entsprechend zu aktualisieren.

Quelle ( Kern )

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;

});

Verwendung

Es wird erwartet, dass die Komponente eine Methode bereitstellt : sendRequest, die ein Bluebird-Versprechen zurückgeben sollte. (Es ist trivial, es so zu ändern, dass es mit Q oder einer anderen Versprechungsbibliothek funktioniert.)

Es bietet komfortable Methoden wie isFormEditable, isFormSubmittingund isFormSubmitted. Es bietet auch eine Methode zum Starten der Anforderung : submitForm. Sie können es über den onClickHandler der Formularschaltflächen aufrufen .

Dan Abramov
quelle
2
@jmcejuela Tatsächlich bin ich später zu einem komponentenorientierteren Ansatz übergegangen (ich verwende immer noch stark Mixins), ich könnte dies irgendwann erweitern.
Dan Abramov
1
Gibt es Neuigkeiten zum Thema "mehr komponentenorientierter Ansatz"?
NilColor
3
@NilColor Noch nicht, ich bin nicht ganz zufrieden damit. :-) Derzeit habe ich FormInputdas Gespräche mit seinem Besitzer über formLink. formLinkist wie valueLinkund wird von FormMixinder linkValidatedState(name, validator)Methode zurückgegeben. FormInputerhält seinen Wert von formLink.valueund ruft auf formLink.requestBlurund formLink.requestFocus- sie verursachen eine Validierung in FormMixin. Um die tatsächliche Komponente anzupassen, die für die Eingabe verwendet wird, kann ich sie schließlich an FormInputfolgende <FormInput component={React.DOM.textarea} ... />
Adresse übergeben
Gute Antwort - einige Tipps: Sie müssen doneBluebird nicht anrufen und der Code funktioniert wie in Q (oder nativen Versprechungen) - natürlich ist Bluebird besser. Beachten Sie auch, dass sich die Syntax in React seit der Antwort geändert hat.
Benjamin Gruenbaum
4

Ich baue ein SPA mit React (seit 1 Jahr in Produktion) und verwende fast nie Mixins.

Der einzige Anwendungsfall, den ich derzeit für Mixins habe, ist, wenn Sie Verhalten teilen möchten, das die Lebenszyklusmethoden von React ( componentDidMountusw.) verwendet. Dieses Problem wird durch die Komponenten höherer Ordnung gelöst, die Dan Abramov in seinem Link spricht (oder durch Verwendung der ES6-Klassenvererbung).

Mixins werden auch häufig in Frameworks verwendet, um die Framework-API allen Komponenten zur Verfügung zu stellen, indem die "versteckte" Kontextfunktion von React verwendet wird. Dies wird auch bei der Vererbung von ES6-Klassen nicht mehr benötigt.


In den meisten anderen Fällen werden Mixins verwendet, die jedoch nicht wirklich benötigt werden und durch einfache Helfer ersetzt werden könnten.

Beispielsweise:

var WithLink = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={this.linkState('message')} />;
  }
});

Sie können LinkedStateMixinCode sehr einfach umgestalten , sodass die Syntax wie folgt lautet:

var WithLink = React.createClass({
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={LinkState(this,'message')} />;
  }
});

Gibt es einen großen Unterschied?

Sebastien Lorber
quelle
Du hast Recht. Tatsächlich wird in den LinkedStateMixin-Dokumenten beschrieben, wie dies ohne das Mixin möglich ist. Diese besondere Mischung ist wirklich nur ein wenig syntaktischer Zucker.
nextgentech