Sử dụng mixins và các thành phần để tái sử dụng mã trong Facebook React


116

Tôi đang bắt đầu sử dụng Facebook React trong một dự án xương sống và cho đến nay nó sẽ thực sự tốt.
Tuy nhiên, tôi nhận thấy một số trùng lặp leo vào mã React của tôi.

Ví dụ, tôi có một vài hình thức giống như các widget với các quốc gia như INITIAL, SENDINGSENT. Khi nhấn nút, biểu mẫu cần được xác thực, yêu cầu được thực hiện và sau đó trạng thái được cập nhật. Trạng thái được giữ trong React this.statetất nhiên, cùng với các giá trị trường.

Nếu đây là các khung nhìn Backbone, tôi sẽ trích xuất một lớp cơ sở được gọi FormViewnhưng ấn tượng của tôi là React không tán thành cũng không hỗ trợ phân lớp để chia sẻ logic xem (sửa tôi nếu tôi sai).

Tôi đã thấy hai cách tiếp cận để sử dụng lại mã trong React:

Tôi có đúng không khi mixin và container được ưu tiên kế thừa trong React? Đây có phải là một quyết định thiết kế có chủ ý? Nó sẽ có ý nghĩa hơn khi sử dụng một mixin hoặc một thành phần chứa cho ví dụ phụ tùng biểu mẫu hình chữ nhật của tôi từ đoạn thứ hai?

Đây là một ý chính với FeedbackWidgetJoinWidgettrong trạng thái hiện tại của họ . Chúng có cấu trúc tương tự, beginSendphương pháp tương tự và cả hai sẽ cần phải có một số hỗ trợ xác nhận (chưa có).


Như một bản cập nhật cho điều này - phản ứng đang có những suy nghĩ thứ hai về việc hỗ trợ mixin trong tương lai lâu dài, bởi vì khi thành phần ví dụ của bạn, tất cả chỉ là phản ứng làm việc kỳ diệu đang làm một số thứ phức tạp để chúng không viết quá nhiều .. vì mixin rất đơn giản và không phù hợp với mục đích
Đaminh

Tôi không có nhiều kinh nghiệm với React, nhưng bạn có thể định nghĩa mixin của riêng mình với các hàm không trùng với không gian tên của các đối tượng React thực tế. sau đó chỉ cần gọi các hàm "siêu lớp" / hàm đối tượng từ các hàm thành phần React điển hình của bạn. sau đó không có sự chồng chéo giữa các hàm React và các hàm được kế thừa. điều này giúp giảm bớt một số nồi hơi, nhưng hạn chế phép thuật xảy ra và giúp React dễ dàng hoạt động hơn trong hậu trường. Điều này thực sự khó thụ thai? Tôi hy vọng tôi làm cho mình rõ ràng.
Alexander Mills

Mixins sẽ không bao giờ chết vì bạn luôn có thể tạo ra mixins DIY. React sẽ không có hỗ trợ "bản địa" cho mixins nhưng bạn vẫn có thể tự làm mixin với JS gốc.
Alexander Mills

Câu trả lời:


109

Cập nhật: câu trả lời này đã lỗi thời. Tránh xa các mixins nếu bạn có thể. Tôi đã cảnh báo bạn!
Mixins đã chết. Thành phần sống lâu

Lúc đầu, tôi đã thử sử dụng các thành phần con cho việc này và giải nén FormWidgetInputWidget. Tuy nhiên, tôi đã từ bỏ cách tiếp cận này nửa chừng vì tôi muốn kiểm soát tốt hơn đối với inputs được tạo và trạng thái của chúng.

Hai bài viết giúp tôi nhiều nhất:

Hóa ra tôi chỉ cần viết hai mixin (khác nhau): ValidationMixinFormMixin.
Đây là cách tôi tách chúng ra.

Xác nhậnMixin

Xác thực mixin thêm các phương thức tiện lợi để chạy các chức năng trình xác nhận của bạn trên một số thuộc tính trạng thái của bạn và lưu trữ các thuộc tính Lỗi lỗi trong một state.errorsmảng để bạn có thể đánh dấu các trường tương ứng.

Nguồn ( ý chính )

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;

});

Sử dụng

ValidationMixincó ba phương pháp: validate, hasErrorresetError.
Nó mong đợi lớp định nghĩa validatorsđối tượng, tương tự như propTypes:

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

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

  // ...

});

Khi người dùng nhấn nút gửi, tôi gọi validate. Một cuộc gọi đến validatesẽ chạy từng trình xác nhận và điền this.state.errorsvào một mảng có chứa các khóa của các thuộc tính không xác thực.

Trong renderphương thức của tôi , tôi sử dụng hasErrorđể tạo lớp CSS chính xác cho các trường. Khi người dùng đặt tiêu điểm bên trong trường, tôi gọi resetErrorđể xóa lỗi tô sáng cho đến validatecuộc gọi tiếp theo .

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

Mẫu mixin xử lý trạng thái biểu mẫu (có thể chỉnh sửa, gửi, gửi). Bạn có thể sử dụng nó để vô hiệu hóa các đầu vào và nút trong khi yêu cầu đang được gửi và để cập nhật chế độ xem của bạn tương ứng khi nó được gửi.

Nguồn ( ý chính )

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;

});

Sử dụng

Nó hy vọng thành phần sẽ cung cấp một phương thức : sendRequest, sẽ trả về một lời hứa Bluebird. (Việc sửa đổi nó để làm việc với Q hoặc thư viện lời hứa khác là chuyện nhỏ.)

Nó cung cấp các phương thức tiện lợi như isFormEditable, isFormSubmittingisFormSubmitted. Nó cũng cung cấp một phương thức để khởi động yêu cầu : submitForm. Bạn có thể gọi nó từ onClicktrình xử lý nút biểu mẫu .


2
@jmcejuela Trên thực tế tôi đã chuyển sang cách tiếp cận nhiều thành phần hơn sau này (vẫn sử dụng mixins rất nhiều), tôi có thể mở rộng điều này vào một lúc nào đó ..
Dan Abramov

1
Có một số tin tức về "cách tiếp cận nhiều thành phần hơn"?
NilColor

3
@NilColor Chưa, tôi không hoàn toàn hài lòng với nó. :-) Hiện tại tôi có FormInputcuộc nói chuyện với chủ sở hữu của nó thông qua formLink. formLinkcũng giống như valueLink, và được trả về từ FormMixin's linkValidatedState(name, validator)phương pháp. FormInputnhận được giá trị của nó từ formLink.valuevà các cuộc gọi formLink.requestBlurformLink.requestFocusxác thực nguyên nhân trong đó FormMixin. Cuối cùng, để tùy chỉnh thành phần thực tế đang được sử dụng cho đầu vào, tôi có thể chuyển nó tới FormInput:<FormInput component={React.DOM.textarea} ... />
Dan Abramov

Câu trả lời hay - một số mẹo: bạn không phải gọi donetrong bluebird và mã sẽ hoạt động như trong Q (hoặc lời hứa riêng) - tất nhiên bluebird tốt hơn. Cũng lưu ý rằng cú pháp đã thay đổi trong React kể từ câu trả lời.
Benjamin Gruenbaum 04/03/2015

4

Tôi đang xây dựng một SPA với React (sản xuất từ ​​1 năm) và tôi gần như không bao giờ sử dụng mixins.

Usecase duy nhất tôi hiện có cho mixins là khi bạn muốn chia sẻ hành vi sử dụng các phương thức vòng đời của React ( componentDidMountv.v.). Vấn đề này được giải quyết bằng các Thành phần bậc cao mà Dan Abramov nói trong liên kết của mình (hoặc bằng cách sử dụng kế thừa lớp ES6).

Mixins cũng thường được sử dụng trong các khung, để cung cấp API khung cho tất cả các thành phần, bằng cách sử dụng tính năng ngữ cảnh "ẩn" của React. Điều này sẽ không còn cần thiết nữa với sự kế thừa lớp ES6.


Hầu hết các lần khác, mixins được sử dụng, nhưng không thực sự cần thiết và có thể được thay thế bằng trình trợ giúp đơn giản.

Ví dụ:

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

Bạn có thể dễ dàng cấu trúc lại LinkedStateMixinmã để cú pháp sẽ là:

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

Có sự khác biệt lớn nào không?


Bạn nói đúng. Trong thực tế, các tài liệu LinkedStateMixin thực sự đánh vần cách làm mà không cần mixin. Mixin đặc biệt này thực sự chỉ là một chút đường cú pháp.
nextgentech
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.