Facebook React에서 코드 재사용을 위해 믹스 인 대 구성 요소 사용


116

저는 Backbone 프로젝트에서 Facebook React를 사용하기 시작했으며 지금까지 정말 잘 진행되고 있습니다.
그러나 React 코드에 중복이 발생하는 것을 발견했습니다.

예를 들어, 나는 몇 가지 형태와 같은 위젯이 같은 상태로를 INITIAL, SENDING하고 SENT. 버튼을 누르면 양식의 유효성을 검사하고 요청한 다음 상태를 업데이트해야합니다. 상태는 this.state물론 필드 값과 함께 React 내부에 보관 됩니다.

이것이 Backbone 뷰라면,라는 기본 클래스를 추출했을 FormView것이지만 React는 뷰 로직을 공유하기 위해 서브 클래 싱을 지원하지도 지원하지도 않는다는 인상을 받았습니다 .

React에서 코드 재사용에 대한 두 가지 접근 방식을 보았습니다.

믹스 인과 컨테이너가 React의 상속보다 선호된다는 것이 맞습니까? 이것은 의도적 인 디자인 결정입니까? 두 번째 단락의 "양식 위젯"예제에 mixin 또는 컨테이너 구성 요소를 사용하는 것이 더 합리적입니까?

여기에 요지입니다 FeedbackWidgetJoinWidget현재 상태는 . 그들은 유사한 구조, 유사한 beginSend방법을 가지고 있으며 둘 다 일부 유효성 검사 지원이 필요합니다 (아직 존재하지 않음).


이것에 대한 업데이트로-react는 긴 미래에 mixin을 지원하는 것에 대해 두 번째 생각을 가지고 있습니다. 왜냐하면 예를 들어 componentDidMount가 모두 마술처럼 작동 할 때 반응은 복잡한 작업을 수행하므로 서로 덮어 쓰지 않기 때문입니다. 단순하고 목적에 적합하지 않음
Dominic

React에 대한 경험은 많지 않지만 실제 React 객체의 네임 스페이스와 겹치지 않는 함수로 자신 만의 믹스 인을 정의 할 수 있습니다. 그런 다음 일반적인 React 구성 요소 함수에서 "수퍼 클래스"/ 컴포지션 객체 함수를 호출하면됩니다. 그러면 React 함수와 상속 된 함수가 겹치지 않습니다. 이것은 일부 상용구를 줄이는 데 도움이되지만 마법이 발생하는 것을 제한하고 React 자체가이면에서 작동하는 것을 더 쉽게 만듭니다. 이게 정말 상상하기 어려운가요? 내가 분명히했으면 좋겠어.
Alexander Mills

Mixin은 항상 DIY mixin을 만들 수 있기 때문에 절대 죽지 않습니다. React는 믹스 인을 "네이티브"로 지원하지 않지만 네이티브 JS로 믹스 인을 직접 할 수 있습니다.
Alexander Mills

답변:


109

업데이트 :이 답변은 구식입니다. 가능하면 믹스 인을 멀리하십시오. 경고 했어!
Mixins는 죽었습니다. 긴 라이브 작곡

처음에는이를 위해 하위 구성 요소를 사용 FormWidget하고 InputWidget. 그러나 생성 된 inputs와 그 상태에 대한 더 나은 제어를 원했기 때문에이 접근 방식을 중간에 포기했습니다 .

가장 도움이 된 두 가지 기사 :

내가 두 (다른)이나 mixin을 쓸 필요가 있다고 밝혀졌다 : ValidationMixinFormMixin.
내가 그들을 분리 한 방법은 다음과 같습니다.

유효성 검사

유효성 검사 믹스 인은 상태의 일부 속성에서 유효성 검사기 함수를 실행하고 "오류가 발생한"속성을 state.errors배열 에 저장하는 편리한 메서드를 추가하여 해당 필드를 강조 표시 할 수 있습니다.

출처 ( 요점 )

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;

});

용법

ValidationMixin세 가지 방법이 있습니다 validate, hasError그리고 resetError.
다음 validators과 같이 클래스가 객체 를 정의 할 것으로 예상 합니다 propTypes.

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

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

  // ...

});

사용자가 제출 버튼을 누르면을 호출 validate합니다. 에 대한 호출 validate은 각 유효성 검사기를 실행하고 유효성 this.state.errors검사에 실패한 속성의 키를 포함하는 배열로 채 웁니다 .

render방법 에서는 hasError필드에 대한 올바른 CSS 클래스를 생성 하는 데 사용 합니다. 사용자가 필드 안에 포커스를 resetError두면 다음 validate호출 까지 오류 강조 표시를 제거하도록 호출합니다.

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

양식 믹스 인은 양식 상태 (편집 가능, 제출, 제출)를 처리합니다. 이를 사용하여 요청이 전송되는 동안 입력 및 버튼을 비활성화하고 전송시 해당 뷰를 업데이트 할 수 있습니다.

출처 ( 요점 )

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;

});

용법

컴포넌트가 sendRequestBluebird promise를 반환해야하는 하나의 메서드를 제공 할 것으로 예상합니다 . (Q 또는 다른 promise 라이브러리와 함께 작동하도록 수정하는 것은 간단합니다.)

이 같은 편리한 방법을 제공합니다 isFormEditable, isFormSubmitting하고 isFormSubmitted. 또한 요청을 시작하는 방법을 제공합니다 submitForm.. 폼 버튼의 onClick핸들러 에서 호출 할 수 있습니다 .


2
@jmcejuela 사실 나중에 좀 더 구성 요소 같은 접근 방식으로 옮겼습니다 (여전히 믹스 인을 많이 사용). 언젠가는 이에 대해 확장 할 수 있습니다.
Dan Abramov

1
"더 많은 구성 요소 접근 방식"에 대한 뉴스가 있습니까?
NilColor

3
@NilColor 아직은 그다지 만족스럽지 않습니다. :-) 현재 나는을 FormInput통해 소유자와 이야기하고 formLink있습니다. formLink과 같고 의 메서드 valueLink에서 반환됩니다 . 에서의 값을 가져옵니다 전화와 와 에 - 그들은 원인을 확인 . 마지막으로, 입력에 사용되는 실제 구성 요소의 존재를 사용자 정의 할 수, 나는 그것을 전달할 수 있습니다 :FormMixinlinkValidatedState(name, validator)FormInputformLink.valueformLink.requestBlurformLink.requestFocusFormMixinFormInput<FormInput component={React.DOM.textarea} ... />
댄 아브라모프에게

좋은 대답-몇 가지 팁 : done블루 버드 를 호출 할 필요가 없으며 코드는 Q (또는 네이티브 약속)에서와 같이 작동합니다. 물론 블루 버드가 더 좋습니다. 또한 답변 이후 React에서 구문이 변경되었습니다.
Benjamin Gruenbaum 2015 년

4

React로 SPA를 구축하고 있으며 (1 년 이후 생산 중) 믹스 인을 거의 사용하지 않습니다.

현재 믹스 인에 대한 유일한 사용 사례는 React의 수명주기 메서드 ( componentDidMount등) 를 사용하는 동작을 공유하려는 경우 입니다. 이 문제는 Dan Abramov가 자신의 링크 에서 말하는 Higher-Order Components (또는 ES6 클래스 상속 사용)로 해결됩니다.

Mixins는 React 의 "숨겨진" 컨텍스트 기능 을 사용하여 모든 구성 요소에서 프레임 워크 API를 사용할 수 있도록 프레임 워크에서 자주 사용됩니다 . 이것은 ES6 클래스 상속에서도 더 이상 필요하지 않습니다.


대부분의 경우 믹스 인이 사용되지만 실제로는 필요하지 않으며 간단한 도우미로 쉽게 대체 할 수 있습니다.

예를 들면 :

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

LinkedStateMixin구문이 다음과 같 도록 코드를 매우 쉽게 리팩터링 할 수 있습니다.

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

큰 차이가 있습니까?


당신이 올바른지. 실제로 LinkedStateMixin 문서는 실제로 믹스 인없이이를 수행하는 방법을 설명합니다. 이 특정 믹스 인은 실제로 약간의 구문 적 설탕 일뿐입니다.
nextgentech
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.