ReactJS state กับ prop


121

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

มาจาก AngularJS ฉันต้องการส่งโมเดลของฉันไปยังส่วนประกอบเป็นคุณสมบัติและให้ส่วนประกอบนั้นแก้ไขโมเดลโดยตรง หรือฉันควรจะแยกโมเดลออกเป็นstateคุณสมบัติต่างๆและรวบรวมกลับเข้าด้วยกันเมื่อส่งกลับไปที่ต้นน้ำ? วิธี ReactJS คืออะไร?

ใช้ตัวอย่างของโปรแกรมแก้ไขโพสต์บล็อก การพยายามแก้ไขโมเดลโดยตรงจะดูเหมือน:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

ซึ่งดูเหมือนผิด.

เป็นวิธีการตอบสนองในการสร้างtextคุณสมบัติแบบจำลองของเรามากขึ้นstateหรือไม่และรวบรวมกลับเข้าไปในโมเดลก่อนที่จะบันทึกเช่น:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

สิ่งนี้ไม่จำเป็นต้องมีการโทรหาthis.forceUpdate()แต่เมื่อโมเดลเติบโตขึ้น (โพสต์อาจมีผู้เขียนหัวเรื่องแท็กความคิดเห็นการให้คะแนน ฯลฯ ... ) ส่วนประกอบเริ่มซับซ้อนมาก

วิธีแรกกับReactLinkเป็นวิธีที่จะไปหรือไม่?

คำตอบ:


64

แนวทางที่สองของคุณชอบมากกว่า React ไม่สนใจโมเดลมากนักเนื่องจากให้ความสำคัญกับค่านิยมและวิธีที่พวกเขาไหลผ่านแอปของคุณ ตามหลักการแล้วโมเดลโพสต์ของคุณจะถูกเก็บไว้ในองค์ประกอบเดียวที่ราก จากนั้นคุณสร้างส่วนประกอบย่อยที่แต่ละส่วนใช้ส่วนต่างๆของโมเดล

คุณสามารถส่งการโทรกลับไปยังกลุ่มย่อยที่ต้องการแก้ไขข้อมูลของคุณและเรียกใช้จากองค์ประกอบลูก

การแก้ไข this.props หรือ this.state โดยตรงไม่ใช่ความคิดที่ดีเพราะ React จะไม่สามารถรับการเปลี่ยนแปลงได้ นั่นเป็นเพราะ React ทำการเปรียบเทียบโพสต์ของคุณแบบตื้น ๆ เพื่อดูว่ามีการเปลี่ยนแปลงหรือไม่

ฉันสร้างjsfiddleนี้เพื่อแสดงว่าข้อมูลสามารถไหลจากภายนอกไปยังส่วนประกอบภายในได้อย่างไร

handleClickวิธีการแสดง 3 วิธีที่จะ (IM) รัฐปรับปรุงอย่างถูกต้อง

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

แต่เราจะทำอย่างไรถ้าเรามีแบบจำลองทึบแสงที่มีฟังก์ชันการจัดการสถานะของตัวเอง? ตัวอย่างเช่นสมมติว่าแทนที่จะเป็นtextฟิลด์เรามีsetText วิธีการตรวจสอบความถูกต้องและสิ่งอื่น ๆ ฉันสามารถดูวิธีการ (2) ที่ใช้งานได้หากsetTextเป็นแบบบริสุทธิ์และส่งคืนอินสแตนซ์ใหม่ของโมเดล อย่างไรก็ตามหากsetTextเพียงแค่อัปเดตสถานะภายในเราก็ยังต้องโทรหาforceUpdateใช่ไหม?
hugomg

1
ใช่คุณสามารถโทรforceUpdateได้ แต่ ณ จุดนั้นคุณกำลัง "รั่ว" ออกจาก React อาจเป็นการดีกว่าที่จะส่งการsetState()เรียกกลับไปยังโมเดลทึบแสงเพื่อหลีกเลี่ยงการเรียกใช้การแสดงผลซ้ำด้วยตนเอง
jxg

ฉันยังไม่แน่ใจว่าเข้าใจถ่องแท้ ดังนั้นส่วนประกอบใด ๆ ที่ตั้งใจจะแก้ไขข้อมูลจำเป็นต้องทำสำเนาของอุปกรณ์ประกอบฉากอย่างละเอียด จากนั้นแก้ไขและส่งสำเนานั้นขึ้นต้นน้ำเพื่อไม่ให้แก้ไขข้อมูลเดิม? ในที่สุดการเปลี่ยนแปลงจะไหลไปที่รูทซึ่งจัดการกับแอพทั้งหมดหรือไม่ นั่นถูกต้องใช่ไหม?
nicholas

97

การอัปเดต 2016: การ ตอบสนองมีการเปลี่ยนแปลงและคำอธิบาย "อุปกรณ์กับสถานะ" กลายเป็นเรื่องง่ายมาก หากส่วนประกอบต้องการเปลี่ยนแปลงข้อมูลให้วางไว้ในสถานะมิฉะนั้นในอุปกรณ์ประกอบฉาก เนื่องจากอุปกรณ์ประกอบฉากเป็นแบบอ่านอย่างเดียวในขณะนี้

อะไรคือความแตกต่างที่แน่นอนระหว่างอุปกรณ์ประกอบฉากและสถานะ?

คุณสามารถค้นหาคำอธิบายที่ดีได้ที่นี่ (ฉบับเต็ม)

การเปลี่ยนอุปกรณ์และสถานะ


1
จริงๆแล้ว setProps () สามารถเปลี่ยนอุปกรณ์ประกอบฉากภายในส่วนประกอบและทริกเกอร์การเรนเดอร์ใหม่ได้
WaiKit Kung

2
setPropsเลิกใช้แล้วและไม่ควรใช้ การแทนที่คือการแสดงผลส่วนประกอบใหม่และปล่อยให้ React จัดการกับความแตกต่าง
jdmichal

และหากคุณกำลังมองหาวิดีโออธิบาย: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

จาก React doc

อุปกรณ์ประกอบฉากไม่เปลี่ยนรูป: ส่งต่อมาจากผู้ปกครองและ "เป็นเจ้าของ" โดยผู้ปกครอง ในการใช้การโต้ตอบเรานำเสนอสถานะที่ไม่แน่นอนให้กับองค์ประกอบ this.state เป็นไพรเวตของคอมโพเนนต์และสามารถเปลี่ยนแปลงได้โดยเรียก this.setState () เมื่อสถานะถูกอัพเดตคอมโพเนนต์จะแสดงผลเองอีกครั้ง

จากTrySpace : เมื่อมีการอัปเดตอุปกรณ์ประกอบฉาก (หรือสถานะ) (ผ่าน setProps / setState หรือพาเรนต์) คอมโพเนนต์จะแสดงผลเช่นกัน


16

การอ่านจากThinking in React :

ลองดูแต่ละอันแล้วดูว่าอันไหนเป็นสถานะ เพียงถามคำถามสามข้อเกี่ยวกับข้อมูลแต่ละส่วน:

  1. ส่งผ่านมาจากผู้ปกครองผ่านอุปกรณ์ประกอบฉากหรือไม่? ถ้าเป็นเช่นนั้นก็อาจไม่ใช่สถานะ
  2. มีการเปลี่ยนแปลงตลอดเวลาหรือไม่? ถ้าไม่เช่นนั้นก็คงไม่ใช่สถานะ

  3. คุณสามารถคำนวณตามสถานะหรืออุปกรณ์ประกอบฉากอื่น ๆ ในส่วนประกอบของคุณได้หรือไม่ ถ้าเป็นเช่นนั้นก็ไม่ใช่สถานะ


13

ฉันไม่แน่ใจว่าฉันกำลังตอบคำถามของคุณหรือไม่ แต่ฉันพบว่าโดยเฉพาะอย่างยิ่งในแอปพลิเคชันขนาดใหญ่ / ที่กำลังเติบโตรูปแบบคอนเทนเนอร์ / ส่วนประกอบทำงานได้ดีอย่างไม่น่าเชื่อ

โดยพื้นฐานแล้วคุณมีส่วนประกอบของปฏิกิริยาสองอย่าง:

  • องค์ประกอบการแสดงผล "บริสุทธิ์" ซึ่งเกี่ยวข้องกับการจัดรูปแบบและการโต้ตอบ DOM
  • ส่วนประกอบคอนเทนเนอร์ซึ่งเกี่ยวข้องกับการเข้าถึง / บันทึกข้อมูลภายนอกสถานะการจัดการและการแสดงผลคอมโพเนนต์การแสดงผล

ตัวอย่าง

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

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

ประโยชน์ที่ได้รับ

ด้วยการแยกตรรกะการแสดงผลและการจัดการข้อมูล / สถานะออกจากกันคุณจะมีองค์ประกอบการแสดงผลที่ใช้ซ้ำได้ซึ่ง:

  • สามารถทำซ้ำได้อย่างง่ายดายด้วยชุดอุปกรณ์ประกอบฉากที่แตกต่างกันโดยใช้บางสิ่งเช่นreact-component-playground
  • สามารถห่อด้วยภาชนะที่แตกต่างกันสำหรับพฤติกรรมที่แตกต่างกัน (หรือรวมกับส่วนประกอบอื่น ๆ เพื่อสร้างส่วนที่ใหญ่ขึ้นของแอปพลิเคชันของคุณ

คุณยังมีส่วนประกอบคอนเทนเนอร์ที่เกี่ยวข้องกับการสื่อสารภายนอกทั้งหมด วิธีนี้จะช่วยให้คุณมีความยืดหยุ่นในการเข้าถึงข้อมูลของคุณได้ง่ายขึ้นหากคุณทำการเปลี่ยนแปลงที่ร้ายแรงในภายหลัง *

รูปแบบนี้ยังทำให้การเขียนและการใช้การทดสอบหน่วยตรงไปตรงมามากขึ้น

หลังจากทำซ้ำแอป React ขนาดใหญ่สองสามครั้งฉันพบว่ารูปแบบนี้ช่วยให้สิ่งต่าง ๆ ไม่เจ็บปวดโดยเฉพาะอย่างยิ่งเมื่อคุณมีส่วนประกอบขนาดใหญ่ที่มีรูปแบบการคำนวณหรือการโต้ตอบ DOM ที่ซับซ้อน

* อ่านเกี่ยวกับรูปแบบฟลักซ์และดูที่Marty.jsซึ่งส่วนใหญ่เป็นแรงบันดาลใจให้กับคำตอบนี้ (และเมื่อเร็ว ๆ นี้ฉันใช้มาก) Redux (และreact-redux ) ซึ่งใช้รูปแบบนี้ได้ดีมาก

หมายเหตุสำหรับผู้ที่อ่านสิ่งนี้ในปี 2018 หรือใหม่กว่า:

ตอบสนองมีการพัฒนาไม่น้อยตั้งแต่คำตอบนี้ถูกเขียนขึ้นโดยเฉพาะอย่างยิ่งด้วยการแนะนำของเบ็ด อย่างไรก็ตามตรรกะการจัดการสถานะพื้นฐานจากตัวอย่างนี้ยังคงเหมือนเดิมและที่สำคัญกว่านั้นคือประโยชน์ที่คุณจะได้รับจากการแยกตรรกะของรัฐและการนำเสนอออกจากกันยังคงใช้ในลักษณะเดียวกัน


0

ฉันคิดว่าคุณกำลังใช้รูปแบบการต่อต้านซึ่ง Facebook ได้อธิบายไว้แล้วที่ลิงค์นี้

นี่คือสิ่งที่คุณพบ:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

ในครั้งแรกที่แสดงผลส่วนประกอบภายในส่วนประกอบจะมี {foo: 'bar'} เป็นตัวกำหนดค่า หากผู้ใช้คลิกที่จุดยึดสถานะขององค์ประกอบหลักจะได้รับการอัปเดตเป็น {value: {foo: 'barbar'}} โดยเรียกกระบวนการแสดงผลขององค์ประกอบภายในซึ่งจะได้รับ {foo: 'barbar'} เป็น ค่าใหม่สำหรับเสา

ปัญหาคือเนื่องจากพาเรนต์และคอมโพเนนต์ภายในแชร์การอ้างอิงไปยังอ็อบเจ็กต์เดียวกันเมื่ออ็อบเจ็กต์กลายพันธุ์ในบรรทัดที่ 2 ของฟังก์ชัน onClick การสนับสนุนส่วนประกอบภายในจะเปลี่ยนไป ดังนั้นเมื่อกระบวนการเรนเดอร์เริ่มต้นและ shouldComponentUpdate ถูกเรียกใช้ this.props.value.foo จะเท่ากับ nextProps.value.foo เพราะอันที่จริง this.props.value อ้างอิงอ็อบเจกต์เดียวกันกับ nextProps.value

ดังนั้นเนื่องจากเราพลาดการเปลี่ยนแปลงบนเสาและทำให้กระบวนการเรนเดอร์ลัดวงจร UI จะไม่ได้รับการอัปเดตจาก 'bar' เป็น 'barbar'


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