Utilizzo di mixin e componenti per il riutilizzo del codice in Facebook React


116

Sto iniziando a usare Facebook React in un progetto Backbone e finora sta andando davvero bene.
Tuttavia, ho notato che alcune duplicazioni si insinuano nel mio codice React.

Per esempio, ho parecchi forma-come i widget con stati come INITIAL, SENDINGe SENT. Quando si preme un pulsante, il modulo deve essere convalidato, viene effettuata una richiesta e quindi lo stato viene aggiornato. Lo stato è this.stateovviamente mantenuto all'interno di React , insieme ai valori dei campi.

Se queste fossero viste Backbone, avrei estratto una classe base chiamata FormViewma la mia impressione è stata che React non approva né supporta la sottoclasse per condividere la logica della vista (correggimi se sbaglio).

Ho visto due approcci al riutilizzo del codice in React:

Ho ragione sul fatto che mixin e contenitori siano preferiti all'ereditarietà in React? È una decisione progettuale deliberata? Avrebbe più senso usare un componente mixin o contenitore per il mio esempio di "widget modulo" del secondo paragrafo?

Ecco una sintesi con FeedbackWidgete JoinWidgetnel loro stato attuale . Hanno una struttura simile, un beginSendmetodo simile ed entrambi avranno bisogno di un supporto di convalida (non ancora disponibile).


Come aggiornamento a questo, i React stanno avendo dei ripensamenti sul supporto dei mixin nel lungo futuro, perché quando i tuoi ad es. ComponentDidMount funzionano tutti magicamente, reagire sta facendo delle cose complicate, quindi non si sovrascrivono l'un l'altro .. perché i mixin sono molto semplicistico e non adatto allo scopo
Domenico

Non ho molta esperienza con React, ma potresti definire il tuo mixin con funzioni che non si sovrappongono allo spazio dei nomi degli oggetti React reali. quindi chiama semplicemente le funzioni dell'oggetto "superclasse" / composizione dalle funzioni tipiche dei componenti di React. quindi non c'è sovrapposizione tra le funzioni React e le funzioni ereditate. questo aiuta a ridurre alcuni boilerplate, ma limita la magia che accade e rende più facile per React stesso operare dietro le quinte. è davvero così difficile da concepire? Spero di essere stato chiaro.
Alexander Mills

I mixin non moriranno mai perché puoi sempre fare solo mixin fai-da-te. React non avrà il supporto "nativo" per i mixin, ma puoi ancora fare i mixin da solo con JS nativo.
Alexander Mills

Risposte:


109

Aggiornamento: questa risposta è obsoleta. Stai lontano dai mixin se puoi. Ti ho avvertito!
I mixin sono morti. Lunga vita alla composizione

All'inizio, ho provato a utilizzare i sottocomponenti per questo ed estrarre FormWidgete InputWidget. Tuttavia, ho abbandonato a metà questo approccio perché volevo un controllo migliore sui messaggi generati inpute sul loro stato.

Due articoli che mi hanno aiutato di più:

Si è scoperto che avevo solo bisogno di scrivere due (diversi) mixin: ValidationMixine FormMixin.
Ecco come li ho separati.

ValidationMixin

Il mixin di convalida aggiunge metodi di convenienza per eseguire le funzioni di convalida su alcune delle proprietà del tuo stato e memorizzare le proprietà "errate" in un state.errorsarray in modo da poter evidenziare i campi corrispondenti.

Fonte ( sintesi )

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;

});

uso

ValidationMixinha tre metodi: validate, hasErrore resetError.
Si aspetta che la classe definisca l' validatorsoggetto, simile a propTypes:

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

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

  // ...

});

Quando l'utente preme il pulsante di invio, chiamo validate. Una chiamata a validateeseguirà ogni validatore e popolerà this.state.errorsun array che contiene le chiavi delle proprietà che hanno fallito la convalida.

Nel mio rendermetodo, utilizzo hasErrorper generare la classe CSS corretta per i campi. Quando l'utente mette il focus all'interno del campo, chiamo resetErrorper rimuovere l'evidenziazione dell'errore fino alla validatechiamata successiva .

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

Il mixin del modulo gestisce lo stato del modulo (modificabile, in fase di invio, inviato). È possibile utilizzarlo per disabilitare input e pulsanti durante l'invio della richiesta e per aggiornare la visualizzazione in modo corrispondente quando viene inviata.

Fonte ( sintesi )

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;

});

uso

Si aspetta che il componente fornisca un metodo:, sendRequestche dovrebbe restituire una promessa Bluebird. (È banale modificarlo per funzionare con Q o altre librerie di promesse.)

Fornisce metodi convenienti come isFormEditable, isFormSubmittinge isFormSubmitted. Esso fornisce anche un metodo per dare il via alla richiesta: submitForm. Puoi chiamarlo dal onClickgestore dei pulsanti del modulo .


2
@jmcejuela In effetti sono passato a un approccio più basato sui componenti in seguito (ancora usando pesantemente i mixin), potrei espandere su questo ad un certo punto ..
Dan Abramov

1
Ci sono notizie su un "approccio più basato sui componenti"?
NilColor

3
@NilColor Non ancora, non ne sono abbastanza soddisfatto. :-) Attualmente ho FormInputche parla con il suo proprietario tramite formLink. formLinkè come valueLink, ed è tornato da FormMixin's linkValidatedState(name, validator)metodo. FormInputottiene il suo valore da formLink.valuee chiama formLink.requestBlure formLink.requestFocus- causano la convalida in FormMixin. Infine, per personalizzare il componente effettivo utilizzato per l'input, posso passarlo a FormInput:<FormInput component={React.DOM.textarea} ... />
Dan Abramov

Bella risposta - alcuni suggerimenti: non devi chiamare donein bluebird e il codice funzionerà come in Q (o promesse native) - ovviamente bluebird è migliore. Nota anche che la sintassi è cambiata in React dalla risposta.
Benjamin Gruenbaum

4

Sto costruendo una SPA con React (in produzione da 1 anno) e non uso quasi mai i mixin.

L'unico caso d'uso che ho attualmente per i mixin è quando vuoi condividere un comportamento che utilizza i metodi del ciclo di vita di React ( componentDidMountecc.). Questo problema è risolto dai componenti di ordine superiore di cui parla Dan Abramov nel suo link (o usando l'ereditarietà della classe ES6).

I mixin sono spesso utilizzati anche nei framework, per rendere l'API del framework disponibile a tutti i componenti, utilizzando la funzionalità di contesto "nascosto" di React. Questo non sarà più necessario neanche con l'ereditarietà della classe ES6.


La maggior parte delle altre volte vengono utilizzati i mixin, ma non sono realmente necessari e potrebbero essere facilmente sostituiti con semplici helper.

Per esempio:

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

Puoi facilmente eseguire il refactoring del LinkedStateMixincodice in modo che la sintassi sia:

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

C'è qualche grande differenza?


Hai ragione. In effetti i documenti di LinkedStateMixin in realtà spiegano come farlo senza il mixin. Questo particolare mixin è davvero solo un po 'di zucchero sintattico.
nextgentech
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.