Menggunakan komponen vs mixins untuk penggunaan kembali kode di Facebook React

116

Saya mulai menggunakan Facebook React dalam proyek Backbone dan sejauh ini berjalan dengan sangat baik.
Namun, saya melihat beberapa duplikasi merayap ke kode React saya.

Misalnya, saya memiliki beberapa widget seperti formulir dengan status seperti INITIAL, SENDINGdan SENT. Ketika tombol ditekan, formulir perlu divalidasi, permintaan dibuat, dan kemudian status diperbarui. State disimpan di dalam React this.statetentunya, bersama dengan nilai field.

Jika ini adalah tampilan Backbone, saya akan mengekstrak kelas dasar yang disebut FormViewtetapi kesan saya adalah bahwa Bereaksi tidak mendukung atau mendukung subkelas untuk berbagi logika tampilan (perbaiki saya jika saya salah).

Saya telah melihat dua pendekatan untuk menggunakan kembali kode di React:

Apakah saya benar bahwa mixin dan container lebih disukai daripada warisan di React? Apakah ini keputusan desain yang disengaja? Apakah lebih masuk akal untuk menggunakan mixin atau komponen kontainer untuk contoh "widget formulir" saya dari paragraf kedua?

Berikut adalah inti dari FeedbackWidgetdan JoinWidgetdalam keadaan mereka saat ini . Mereka memiliki struktur yang mirip, beginSendmetode yang serupa dan keduanya perlu memiliki beberapa dukungan validasi (belum ada).

Dan Abramov
sumber
Sebagai pembaruan untuk ini - react sedang berpikir dua kali tentang mendukung mixin di masa mendatang, karena ketika misalnya componentDidMount Anda semua bekerja secara ajaib, react adalah melakukan beberapa hal rumit sehingga mereka tidak saling menimpa satu sama lain .. karena mixin sangat sederhana dan tidak cocok untuk tujuan
Dominic
Saya tidak memiliki banyak pengalaman dengan React, tetapi Anda dapat menentukan mixin Anda sendiri dengan fungsi yang tidak tumpang tindih dengan namespace objek React yang sebenarnya. lalu panggil saja fungsi objek "superclass" / komposisi dari fungsi komponen React khas Anda. maka tidak ada tumpang tindih antara fungsi React dan fungsi yang diwariskan. ini membantu mengurangi beberapa boilerplate, tetapi membatasi keajaiban yang terjadi dan membuatnya lebih mudah untuk React sendiri untuk beroperasi di belakang layar. apakah ini benar-benar sulit untuk dibayangkan? Saya harap saya membuat diri saya jelas.
Alexander Mills
Mixin tidak akan pernah mati karena Anda selalu bisa membuat mixin DIY. React tidak akan memiliki dukungan "native" untuk mixin tetapi Anda masih dapat melakukan mixin sendiri dengan JS native.
Alexander Mills

Jawaban:

109

Pembaruan: jawaban ini sudah ketinggalan zaman. Jauhi mixin jika Anda bisa. Saya memperingatkan Anda!
Mixins Are Dead. Komposisi Umur Panjang

Pada awalnya, saya mencoba menggunakan subkomponen untuk ini dan mengekstrak FormWidgetdan InputWidget. Namun, saya mengabaikan pendekatan ini setengah jalan karena saya ingin kontrol yang lebih baik atas yang dihasilkan inputdan statusnya.

Dua artikel yang paling membantu saya:

Ternyata saya hanya perlu menulis dua mixin (berbeda): ValidationMixindan FormMixin.
Begini cara saya memisahkan mereka.

ValidationMixin

Mixin validasi menambahkan metode praktis untuk menjalankan fungsi validator Anda pada beberapa properti negara bagian Anda dan menyimpan properti "error'd" dalam state.errorslarik sehingga Anda dapat menyorot kolom terkait.

Sumber ( inti )

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;

});

Pemakaian

ValidationMixinmemiliki tiga metode: validate, hasErrordan resetError.
Ini mengharapkan kelas untuk mendefinisikan validatorsobjek, mirip dengan propTypes:

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

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

  // ...

});

Ketika pengguna menekan tombol pengiriman, saya menelepon validate. Panggilan ke validateakan menjalankan setiap validator dan terisi this.state.errorsdengan array yang berisi kunci properti yang gagal validasi.

Dalam rendermetode saya , saya gunakan hasErroruntuk menghasilkan kelas CSS yang benar untuk bidang. Ketika pengguna menempatkan fokus di dalam lapangan, saya memanggil resetErroruntuk menghapus sorotan kesalahan hingga validatepanggilan berikutnya .

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

Form mixin menangani status formulir (dapat diedit, dikirim, dikirim). Anda dapat menggunakannya untuk menonaktifkan input dan tombol saat permintaan dikirim, dan untuk memperbarui tampilan Anda secara bersamaan saat permintaan dikirim.

Sumber ( inti )

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;

});

Pemakaian

Ia mengharapkan komponen untuk menyediakan satu metode:, sendRequestyang harus mengembalikan janji Bluebird. (Sangat mudah untuk memodifikasinya agar berfungsi dengan Q atau library promise lainnya.)

Ini memberikan metode kenyamanan seperti isFormEditable, isFormSubmittingdan isFormSubmitted. Hal ini juga menyediakan metode untuk memulai permintaan: submitForm. Anda dapat menyebutnya dari onClickpenangan tombol formulir .

Dan Abramov
sumber
2
@jmcejuela Sebenarnya saya pindah ke pendekatan yang lebih komponen-ish nanti (masih menggunakan banyak mixin), saya mungkin akan mengembangkan ini di beberapa titik ..
Dan Abramov
1
Apakah ada berita tentang "pendekatan komponen yang lebih banyak"?
NilColor
3
@NilColor Belum, saya belum cukup puas dengan itu. :-) Saat ini saya sedang FormInputberbicara dengan pemiliknya melalui formLink. formLinkseperti valueLink, dan kembali dari FormMixin's linkValidatedState(name, validator)metode. FormInputmendapatkan nilainya dari formLink.valuedan memanggil formLink.requestBlurdan formLink.requestFocus—mereka menyebabkan validasi masuk FormMixin. Akhirnya, untuk menyesuaikan komponen aktual yang digunakan untuk input, saya dapat meneruskannya ke FormInput:<FormInput component={React.DOM.textarea} ... />
Dan Abramov
Jawaban yang bagus - beberapa tip: Anda tidak perlu menelepon donedi bluebird dan kodenya akan berfungsi seperti di Q (atau promise asli) - tentu saja bluebird lebih baik. Perhatikan juga bahwa sintaks di React berubah sejak jawabannya.
Benjamin Gruenbaum
4

Saya membangun SPA dengan React (dalam produksi sejak 1 tahun), dan saya hampir tidak pernah menggunakan mixin.

Satu-satunya kasus penggunaan yang saya miliki saat ini untuk mixin adalah ketika Anda ingin berbagi perilaku yang menggunakan metode siklus hidup React ( componentDidMountdll). Masalah ini diselesaikan oleh Komponen Orde Tinggi yang dibicarakan Dan Abramov di tautannya (atau dengan menggunakan warisan kelas ES6).

Mixin juga sering digunakan dalam kerangka kerja, untuk membuat API kerangka kerja tersedia untuk semua komponen, dengan menggunakan fitur konteks "tersembunyi" dari React. Ini tidak akan dibutuhkan lagi baik dengan warisan kelas ES6.


Sering kali, mixin digunakan, tetapi tidak terlalu dibutuhkan dan dapat diganti dengan lebih mudah dengan bantuan sederhana.

Sebagai contoh:

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

Anda dapat dengan mudah melakukan refactor LinkedStateMixinkode sehingga sintaksnya menjadi:

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

Apakah ada perbedaan besar?

Sebastien Lorber
sumber
Anda benar. Faktanya, dokumen LinkedStateMixin sebenarnya menjelaskan bagaimana melakukannya tanpa mixin. Mixin ini sebenarnya hanya sedikit gula sintaksis.
nextgentech