React รักษาลำดับการอัปเดตสถานะหรือไม่


149

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

  1. ส่วนประกอบเดียวกัน?
  2. ส่วนประกอบต่าง ๆ ?

พิจารณาคลิกปุ่มในตัวอย่างต่อไปนี้:

1.มีความเป็นไปได้หรือไม่ที่a เป็นเท็จและ b เป็นจริงสำหรับ:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2.มีความเป็นไปได้หรือไม่ที่a เป็นเท็จและ b เป็นจริงสำหรับ:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

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

คำตอบใด ๆ ที่สำรองไว้โดยเอกสารประกอบจะได้รับการชื่นชมอย่างมาก


1
ดูสิ่งนี้: stackoverflow.com/a/36087156/3776299
helb

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

ตัวอย่างเช่นใช้สิ่งนี้: github.com/facebook/react/issues/11793ฉันเชื่อว่าสิ่งต่างๆที่กล่าวถึงในปัญหานั้นจะเป็นประโยชน์สำหรับนักพัฒนาหลายคน แต่สิ่งนั้นไม่ได้อยู่ในเอกสารอย่างเป็นทางการเพราะคนใน FB ถือว่าขั้นสูง อาจเป็นเรื่องเดียวกันกับสิ่งอื่น ๆ ฉันคิดว่าบทความอย่างเป็นทางการที่มีชื่อว่า 'การจัดการของรัฐในเชิงลึก' หรือ 'ข้อผิดพลาดของการจัดการของรัฐ' ซึ่งสำรวจทุกกรณีของการจัดการของรัฐเช่นในคำถามของคุณจะไม่เลวร้าย บางทีเราอาจผลักดันให้นักพัฒนา FB ขยายเอกสารด้วยเนื้อหาดังกล่าว :)
giorgim

Thre เป็นลิงก์ไปยังบทความที่ยอดเยี่ยมเกี่ยวกับสื่อในคำถามของฉัน ควรครอบคลุม 95% ของกรณีการใช้งานของรัฐ :)
คาล

2
@Michal แต่บทความนั้นยังไม่ตอบคำถามนี้ IMHO
giorgim

คำตอบ:


352

ฉันทำงานกับ React

TLDR:

แต่คุณสามารถเชื่อถือ React เพื่ออัปเดตสถานะตามลำดับเดียวกับที่เรียกใช้ setState

  • ส่วนประกอบเดียวกัน?

ใช่.

  • ส่วนประกอบต่าง ๆ ?

ใช่.

เพื่อการปรับปรุงเป็นที่เคารพนับถือเสมอ ไม่ว่าคุณจะเห็นสถานะกลาง "ระหว่าง" หรือไม่ขึ้นอยู่กับว่าคุณอยู่ในกลุ่มหรือไม่

ปัจจุบัน (React 16 และก่อนหน้า), ปรับปรุงเฉพาะภายใน React จัดการเหตุการณ์จะเป็นแบทช์โดยค่าเริ่มต้น มี API ที่ไม่เสถียรในการบังคับใช้แบตช์นอกตัวจัดการเหตุการณ์สำหรับกรณีที่หายากเมื่อคุณต้องการ

ในเวอร์ชันต่อ ๆ ไป (อาจเป็น React 17 และใหม่กว่า) React จะรวมการอัปเดตทั้งหมดตามค่าเริ่มต้นดังนั้นคุณจะไม่ต้องคิดถึงเรื่องนี้ เช่นเคยเราจะประกาศการเปลี่ยนแปลงใด ๆ เกี่ยวกับเรื่องนี้ในบล็อก Reactและในบันทึกประจำรุ่น


กุญแจสำคัญในการทำความเข้าใจเกี่ยวกับเรื่องนี้ก็คือว่ากี่ไม่มีsetState()สายในหลายวิธีที่คุณทำส่วนประกอบภายใน React จัดการเหตุการณ์ที่พวกเขาจะผลิตเพียงคนเดียวอีกครั้งทำให้ในตอนท้ายของเหตุการณ์ นี่เป็นสิ่งสำคัญสำหรับประสิทธิภาพที่ดีในแอปพลิเคชันขนาดใหญ่เพราะหากChildและParentแต่ละครั้งsetState()เมื่อจัดการเหตุการณ์การคลิกคุณไม่ต้องการแสดงผลChildซ้ำสองครั้ง

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

การปรับปรุงอยู่เสมอรวมตื้นในลำดับที่พวกเขาเกิดขึ้น ดังนั้นหากการปรับปรุงครั้งแรกเป็น{a: 10}ที่สองคือ{b: 20}และที่สามเป็นของรัฐการแสดงผลจะเป็น{a: 30} {a: 30, b: 20}การอัปเดตล่าสุดสำหรับคีย์สถานะเดียวกัน (เช่นaในตัวอย่างของฉัน) จะ "ชนะ" เสมอ

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


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

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

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

เรารู้ว่ามันไม่สะดวกที่พฤติกรรมที่แตกต่างกันขึ้นอยู่กับว่าคุณอยู่ในตัวจัดการเหตุการณ์หรือไม่ สิ่งนี้จะเปลี่ยนไปในเวอร์ชัน React ในอนาคตซึ่งจะรวมการอัปเดตทั้งหมดตามค่าเริ่มต้น (และมี API ที่เลือกใช้เพื่อล้างการเปลี่ยนแปลงพร้อมกัน) จนกว่าเราจะเปลี่ยนพฤติกรรมเริ่มต้น (อาจอยู่ใน React 17) มี API ที่คุณสามารถใช้เพื่อบังคับใช้แบตช์ :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

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

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


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


1
วิธีหนึ่งที่จะ"มักจะได้รับการสั่งซื้อที่เหมาะสม"คือการสร้างวัตถุชั่วคราวกำหนดค่าที่แตกต่างกัน (เช่นobj.a = true; obj.b = true) this.setState(obj)และจากนั้นในตอนท้ายก็ทำ ปลอดภัยไม่ว่าคุณจะอยู่ในตัวจัดการเหตุการณ์หรือไม่ก็ตาม อาจเป็นเคล็ดลับที่ดีหากคุณมักพบว่าตัวเองทำผิดพลาดในการตั้งค่าสถานะหลายครั้งนอกตัวจัดการเหตุการณ์
คริส

ดังนั้นเราจึงไม่สามารถพึ่งพาการจัดกลุ่มเพื่อ จำกัด ไว้ที่ตัวจัดการเหตุการณ์เพียงคนเดียวอย่างที่คุณได้ระบุไว้อย่างชัดเจนอย่างน้อยก็เพราะจะไม่เป็นเช่นนั้นในเร็ว ๆ นี้ จากนั้นเราควรใช้ setState พร้อมฟังก์ชั่นตัวอัปเดตเพื่อเข้าถึงสถานะล่าสุดใช่ไหม แต่ถ้าฉันต้องใช้ state.filter เพื่อสร้าง XHR เพื่ออ่านข้อมูลบางส่วนแล้วทำให้ข้อมูลเหล่านั้นอยู่ในสถานะ? ดูเหมือนว่าฉันจะต้องใส่ XHR ที่มีการโทรกลับรอการตัดบัญชี (และด้วยเหตุนี้ผลข้างเคียง) ในตัวอัปเดต นั่นถือเป็นแนวทางปฏิบัติที่ดีที่สุดหรือไม่?
Maksim Gumerov

1
และนั่นหมายความว่าเราไม่ควรอ่านจาก this.state เลย; วิธีเดียวที่สมเหตุสมผลในการอ่านบางสถานะ X คือการอ่านในฟังก์ชันตัวอัปเดตจากอาร์กิวเมนต์ และการเขียนถึงสิ่งนี้รัฐก็ไม่ปลอดภัยเช่นกัน แล้วทำไมอนุญาตให้เข้าถึง this.state เลย? คำถามเหล่านี้ควรตั้งคำถามแบบเดี่ยว ๆ แต่ส่วนใหญ่ฉันแค่พยายามเข้าใจว่าฉันได้รับคำอธิบายที่ถูกต้อง
Maksim Gumerov

11
ควรเพิ่มคำตอบนี้ในเอกสาร reactjs.org
Deen John

2
คุณช่วยชี้แจงในโพสต์นี้ได้ไหมว่า "React event handler" มีcomponentDidUpdateและการเรียกกลับของวงจรชีวิตอื่น ๆ ใน React 16 หรือไม่ ขอบคุณล่วงหน้า!
Ivan

7

นี่เป็นคำถามที่น่าสนใจ แต่คำตอบไม่ควรซับซ้อนเกินไป มีบทความดีๆเกี่ยวกับสื่อที่มีคำตอบ

1) หากคุณทำเช่นนี้

this.setState({ a: true });
this.setState({ b: true });

ฉันไม่คิดว่าจะมีสถานการณ์ที่aจะเป็นtrueและbจะเป็นfalseเพราะเครื่องผสม

อย่างไรก็ตามหากbขึ้นอยู่กับaแล้วอาจมีสถานการณ์ที่คุณไม่ได้รับสถานะที่คาดหวัง

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

หลังจากประมวลผลการโทรทั้งหมดข้างต้นแล้วthis.state.valueจะเป็น 1 ไม่ใช่ 3 อย่างที่คุณคาดหวัง

สิ่งนี้กล่าวถึงในบทความ: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

สิ่งนี้จะทำให้เรา this.state.value === 3


จะเกิดอะไรขึ้นถ้าthis.state.valueมีการอัปเดตทั้งในตัวจัดการเหตุการณ์ (ที่setStateเป็นกลุ่ม) และการเรียกกลับ AJAX (โดยที่ไม่ได้setStateเป็นแบตช์) ในตัวจัดการเหตุการณ์ฉันจะใช้เพื่อให้แน่ใจเสมอว่าฉันอัปเดตสถานะโดยใช้สถานะที่อัปเดตในปัจจุบันโดยฟังก์ชัน ฉันควรใช้กับฟังก์ชันตัวอัปเดตภายในโค้ดของการเรียกกลับ AJAX แม้ว่าฉันจะรู้ว่ามันไม่ได้เป็นแบตช์ก็ตาม คุณช่วยชี้แจงการใช้งานภายใน AJAX callback โดยใช้หรือไม่ใช้ฟังก์ชันตัวอัปเดตได้หรือไม่ ขอบคุณ! updater functionsetStatesetState
tonix

@Michal สวัสดีมิชอลแค่อยากถามคำถามเท่านั้นจริงไหมถ้าเรามี this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); setState แรกจะถูกละเว้นและจะดำเนินการเฉพาะ setState ที่สอง?
Dickens

@Dickens ฉันเชื่อว่าทั้งคู่setStateจะถูกประหารชีวิต แต่คนสุดท้ายจะชนะ
คาล

3

อาจมีการโทรหลายสายในรอบเดียวกัน ตัวอย่างเช่นหากคุณพยายามเพิ่มปริมาณสินค้ามากกว่าหนึ่งครั้งในรอบเดียวกันนั่นจะส่งผลให้เทียบเท่ากับ:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html


3

เช่นเดียวกับในเอกสาร

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

จะทำการเปลี่ยนแปลงล่วงหน้าตามคิว ( FIFO : First In First Out) การเรียกครั้งแรกจะเป็นแบบฟอร์มก่อน


สวัสดีอาลีเพียงแค่อยากจะถามคำถามเพียงว่าถ้าเรามี this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); setState แรกจะถูกละเว้นและจะดำเนินการเฉพาะ setState ที่สอง?
Dickens
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.