Mixins versus componenten gebruiken voor hergebruik van code in Facebook React

Ik begin Facebook React te gebruiken in een Backbone-project en tot nu toe gaat het erg goed.
Ik merkte echter dat er wat duplicatie in mijn React-code kroop.

Bijvoorbeeld Ik heb verschillende vormachtige widgetsmet statussen als INITIAL, SENDINGen SENT. Wanneer er op een knop wordt gedrukt, moet het formulier worden gevalideerd, wordt er een verzoek gedaan en wordt de status bijgewerkt. Status wordt natuurlijk binnen React this.statebewaard, samen met veldwaarden.

Als dit Backbone-weergaven waren, zou ik een basisklasse met de naam FormViewhebben geëxtraheerd, maar mijn indruk was dat React geen subklassen onderschrijft of ondersteunt om weergavelogica te delen(corrigeer me als ik het mis heb).

Ik heb twee benaderingen gezien om code opnieuw te gebruiken in React:

Heb ik gelijk dat mixins en containers de voorkeur hebben boven overerving in React? Is dit een bewuste ontwerpbeslissing? Zou het logischer zijn om een ​​mixin- of een containercomponent te gebruiken voor mijn voorbeeld van een “formulierwidget” uit de tweede alinea?

Hier is een kern met FeedbackWidgeten JoinWidgetin hun huidige staat. Ze hebben een vergelijkbare structuur, vergelijkbare beginSend-methode en zullen beide enige validatie-ondersteuning moeten hebben (nog niet).


Antwoord 1, autoriteit 100%

Update: dit antwoord is verouderd. Blijf weg van de mixins als je kunt.
Ik heb je gewaarschuwd!
Mixins zijn dood. Lang leve compositie

Eerst probeerde ik subcomponenten hiervoor te gebruiken en FormWidgeten InputWidgette extraheren. Ik heb deze benadering echter halverwege opgegeven omdat ik een betere controle wilde over gegenereerde inputs en hun status.

Twee artikelen die me het meest hebben geholpen:

Het bleek dat ik maar twee (verschillende) mixins hoefde te schrijven: ValidationMixinen FormMixin.
Zo heb ik ze gescheiden.

ValidatieMixin

Validatiemixin voegt gemaksmethoden toe om uw validatorfuncties uit te voeren op enkele van de eigenschappen van uw staat en om “error’d” -eigenschappen op te slaan in een state.errors-array, zodat u overeenkomstige velden kunt markeren.

>

Bron (gist)

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;
});

Gebruik

ValidationMixinheeft drie methoden: validate, hasErroren resetError.
Het verwacht dat de klasse validators-object definieert, vergelijkbaar met propTypes:

var JoinWidget = React.createClass({
  mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
  validators: {
    email: Misc.isValidEmail,
    name: function (name) {
      return name.length > 0;
    }
  },
  // ...
});

Als de gebruiker op de verzendknop drukt, bel ik validate. Een aanroep om validatezal elke validator uitvoeren en this.state.errorsvullen met een array die sleutels bevat van de eigenschappen waarvan de validatie mislukt is.

In mijn render-methode gebruik ik hasErrorom de juiste CSS-klasse voor velden te genereren. Wanneer de gebruiker de focus in het veld legt, bel ik resetErrorom de foutmarkering te verwijderen tot de volgende validate-aanroep.

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

Formulier mixin verwerkt formulierstatus (bewerkbaar, indienen, ingediend). U kunt het gebruiken om invoer en knoppen uit te schakelen terwijl het verzoek wordt verzonden, en om uw weergave dienovereenkomstig bij te werken wanneer het wordt verzonden.

Bron (gist)

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;
});

Gebruik

Het verwacht dat component één methode biedt: sendRequest, die een Bluebird-belofte zou moeten retourneren. (Het is triviaal om het aan te passen zodat het werkt met Q of een andere beloftebibliotheek.)

Het biedt handige methoden zoals isFormEditable, isFormSubmittingen isFormSubmitted. Het biedt ook een methode om het verzoek te starten: submitForm. Je kunt het oproepen vanuit de onClick-handler van formulierknoppen.


Antwoord 2, autoriteit 4%

Ik bouw een SPA met React (in productie sinds 1 jaar), en ik gebruik bijna nooit mixins.

De enige usecase die ik momenteel heb voor mixins is wanneer je gedrag wilt delen dat gebruikmaakt van de levenscyclusmethoden van React (componentDidMountenz.). Dit probleem wordt opgelost door de componenten van hogere orde waarover Dan Abramov spreekt in zijn link(of door ES6-klasseovererving te gebruiken).

Mixins worden ook vaak gebruikt in frameworks, om framework API beschikbaar te maken voor alle componenten, door gebruik te maken van de “verborgen” contextfunctievan React. Dit is ook niet meer nodig met overerving van de ES6-klasse.


Meestal worden mixins gebruikt, maar deze zijn niet echt nodig en kunnen gemakkelijker worden vervangen door eenvoudige helpers.

Bijvoorbeeld:

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

Je kunt de code van LinkedStateMixinheel gemakkelijk herstructureren, zodat de syntaxis zou zijn:

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

Is er een groot verschil?

Other episodes