การใช้ส่วนผสมและส่วนประกอบสำหรับการใช้โค้ดซ้ำใน Facebook React


116

ฉันเริ่มใช้ Facebook React ในโครงการ Backbone และจนถึงตอนนี้มันก็ไปได้ดี
อย่างไรก็ตามฉันสังเกตเห็นความซ้ำซ้อนบางอย่างที่คืบคลานเข้ามาในรหัสการตอบสนองของฉัน

ยกตัวอย่างเช่นผมมีวิดเจ็ตแบบฟอร์มเหมือนหลายกับรัฐชอบINITIAL, และSENDING SENTเมื่อกดปุ่มฟอร์มจะต้องได้รับการตรวจสอบความถูกต้องมีการร้องขอและจากนั้นสถานะจะถูกอัพเดต สถานะถูกเก็บไว้ใน React this.stateแน่นอนพร้อมกับค่าฟิลด์

ถ้าสิ่งเหล่านี้เป็นมุมมอง Backbone ฉันจะดึงคลาสพื้นฐานที่เรียกว่าFormViewแต่ความประทับใจของฉันคือ React ไม่รับรองหรือสนับสนุนคลาสย่อยเพื่อแชร์ตรรกะมุมมอง (แก้ไขฉันถ้าฉันผิด)

ฉันเคยเห็นสองวิธีในการใช้โค้ดซ้ำใน React:

  • Mixins (เช่นLinkedStateMixinที่มาพร้อมกับ React);
  • ส่วนประกอบของคอนเทนเนอร์ (เช่นreact-infinite-scroll )

ฉันถูกต้องหรือไม่ที่ mixins และ container ต้องการสืบทอดใน React? นี่เป็นการตัดสินใจออกแบบโดยเจตนาหรือไม่? การใช้มิกซ์อินหรือส่วนประกอบคอนเทนเนอร์สำหรับตัวอย่าง "วิดเจ็ตฟอร์ม" จากย่อหน้าที่สองเหมาะสมกว่าไหม

และนี่ก็เป็นส่วนสำคัญที่มีFeedbackWidgetและJoinWidgetอยู่ในสภาพปัจจุบันของพวกเขา พวกเขามีโครงสร้างที่คล้ายกันbeginSendวิธีการที่คล้ายกันและทั้งสองจะต้องมีการสนับสนุนการตรวจสอบความถูกต้อง (ยังไม่มี)


ในการอัปเดตนี้ - การตอบสนองกำลังมีความคิดที่สองเกี่ยวกับการสนับสนุนมิกซ์อินในอนาคตอันยาวนานเพราะเมื่อเช่น componentDidMount ของคุณเป็นเพียงการตอบสนองที่ใช้งานได้อย่างน่าอัศจรรย์คือการทำสิ่งที่ซับซ้อนบางอย่างดังนั้นพวกเขาจึงไม่เขียนทับกัน ... เพราะมิกซ์อินมาก เรียบง่ายและไม่เหมาะสำหรับวัตถุประสงค์
Dominic

ฉันไม่มีประสบการณ์กับ React มากขนาดนั้น แต่คุณสามารถกำหนด mixin ของคุณเองด้วยฟังก์ชันที่ไม่ทับซ้อนกับเนมสเปซของวัตถุ React จริง จากนั้นเรียกใช้ฟังก์ชัน "superclass" / composition object จากฟังก์ชันส่วนประกอบ React ทั่วไปของคุณ จากนั้นจะไม่มีการทับซ้อนระหว่างฟังก์ชัน React และฟังก์ชันที่สืบทอดมา สิ่งนี้ช่วยลดขั้นตอนสำเร็จรูปบางส่วน แต่ จำกัด เวทมนตร์ที่เกิดขึ้นและทำให้ React ทำงานเบื้องหลังได้ง่ายขึ้น นี่มันยากที่จะตั้งครรภ์จริงๆเหรอ? ฉันหวังว่าฉันจะทำให้ตัวเองชัดเจน
Alexander Mills

Mixins จะไม่มีวันตายเพราะคุณสามารถทำมิกซ์อิน DIY ได้ตลอดเวลา การตอบสนองจะไม่มีการรองรับมิกซ์อินแบบ "เนทีฟ" แต่คุณยังสามารถมิกซ์อินเองด้วย JS เนทีฟได้
Alexander Mills

คำตอบ:


109

อัปเดต: คำตอบนี้ล้าสมัย อยู่ห่างจากมิกซ์อินถ้าทำได้ ฉันเตือนคุณแล้ว!
Mixins จะตาย องค์ประกอบที่มีชีวิตยาวนาน

ตอนแรกฉันพยายามใช้ส่วนประกอบย่อยสำหรับสิ่งนี้และแยกFormWidgetและInputWidget. อย่างไรก็ตามฉันละทิ้งแนวทางนี้ไปกลางคันเพราะฉันต้องการการควบคุมที่ดีขึ้นสำหรับinputs ที่สร้างขึ้นและสถานะของพวกเขา

สองบทความที่ช่วยฉันมากที่สุด:

มันกลับกลายเป็นว่าผมจำเป็นเท่านั้นที่จะเขียนสอง (แตกต่างกัน) mixins: และValidationMixin นี่คือวิธีที่ฉันแยกออกFormMixin

ValidationMixin

การผสมการตรวจสอบความถูกต้องเพิ่มวิธีอำนวยความสะดวกในการเรียกใช้ฟังก์ชันตัวตรวจสอบความถูกต้องของคุณสมบัติบางอย่างของรัฐและจัดเก็บคุณสมบัติ“ ข้อผิดพลาด” ไว้ใน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
validatorspropTypes

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

แบบฟอร์ม mixin จัดการกับสถานะของฟอร์ม (แก้ไขได้, ส่ง, ส่ง) คุณสามารถใช้เพื่อปิดใช้งานอินพุตและปุ่มในขณะที่กำลังส่งคำขอและเพื่ออัปเดตมุมมองของคุณให้สอดคล้องกันเมื่อถูกส่ง

ที่มา ( ส่วนสำคัญ )

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;

});

การใช้

คาดว่าส่วนประกอบจะให้วิธีการเดียว: sendRequestซึ่งควรส่งคืนคำสัญญาของ Bluebird (เป็นเรื่องเล็กน้อยที่จะแก้ไขให้ทำงานกับ Q หรือไลบรารีสัญญาอื่น ๆ )

มันมีวิธีการอำนวยความสะดวกเช่นisFormEditable, และisFormSubmitting นอกจากนี้ยังมีวิธีการที่จะเตะออกคำขอ:isFormSubmitted submitFormคุณสามารถเรียกใช้จากonClickตัวจัดการปุ่มแบบฟอร์ม


2
@jmcejuela ในความเป็นจริงฉันย้ายไปใช้วิธีการที่เป็นองค์ประกอบมากขึ้นในภายหลัง (ยังคงใช้มิกซ์อินอย่างหนัก) ฉันอาจจะขยายเรื่องนี้ในบางจุด ..
Dan Abramov

1
มีข่าวเกี่ยวกับ "more component-ish approach" หรือไม่?
NilColor

3
@NilColor ยังครับผมไม่ค่อยพอใจเท่าไหร่ :-) ขณะนี้ฉันได้FormInputพูดคุยกับเจ้าของผ่านทางformLink. formLinkเป็นเหมือนvalueLinkและถูกส่งกลับจากFormMixin's linkValidatedState(name, validator)วิธี FormInputได้รับค่าจากformLink.valueและโทรformLink.requestBlurและformLink.requestFocus- ทำให้เกิดการตรวจสอบในFormMixin. สุดท้ายในการปรับแต่งส่วนประกอบจริงที่ใช้สำหรับการป้อนข้อมูลฉันสามารถส่งต่อไปยังFormInput:<FormInput component={React.DOM.textarea} ... />
Dan Abramov

คำตอบที่ดี - เคล็ดลับบางประการ: คุณไม่จำเป็นต้องโทร doneด้วย bluebird และรหัสจะทำงานตามที่อยู่ใน Q (หรือคำสัญญาดั้งเดิม) แน่นอนว่า bluebird นั้นดีกว่า โปรดทราบว่าไวยากรณ์เปลี่ยนไปใน React ตั้งแต่คำตอบ
Benjamin Gruenbaum

4

ฉันกำลังสร้างสปาด้วย React (ในการผลิตตั้งแต่ 1 ปี) และฉันแทบไม่เคยใช้มิกซ์อินเลย

กรณีการใช้งานเดียวที่ฉันมีสำหรับมิกซ์อินคือเมื่อคุณต้องการแชร์พฤติกรรมที่ใช้วิธีวงจรชีวิตของ React ( componentDidMountฯลฯ ) ปัญหานี้แก้ไขได้โดยส่วนประกอบระดับสูงที่ Dan Abramov พูดในลิงค์ของเขา(หรือโดยใช้การสืบทอดคลาส ES6)

มักใช้มิกซ์อินในเฟรมเวิร์กเพื่อให้ API เฟรมเวิร์กพร้อมใช้งานสำหรับคอมโพเนนต์ทั้งหมดโดยใช้คุณลักษณะบริบท "ซ่อน" ของ React สิ่งนี้จะไม่จำเป็นอีกต่อไปด้วยการสืบทอดคลาส ES6


ส่วนใหญ่มักใช้มิกซ์อิน แต่ไม่จำเป็นจริงๆและสามารถแทนที่ตัวช่วยง่ายๆได้

ตัวอย่างเช่น:

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

คุณสามารถ refactor code ได้อย่างง่ายดายLinkedStateMixinเพื่อให้ไวยากรณ์เป็น:

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

มีความแตกต่างกันมากหรือไม่?


คุณถูก. ในความเป็นจริงเอกสาร LinkedStateMixin สะกดคำว่าทำอย่างไรโดยไม่ต้องใช้ mixin มิกซินเฉพาะนี้เป็นเพียงน้ำตาลสังเคราะห์เล็กน้อย
nextgentech
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.