เหตุใด setState จึงอยู่ใน reactjs Async แทนที่จะเป็น Sync


127

ฉันเพิ่งพบว่าในthis.setState()ฟังก์ชันreact ในส่วนประกอบใด ๆ เป็นแบบอะซิงโครนัสหรือเรียกว่าหลังจากเสร็จสิ้นฟังก์ชันที่ถูกเรียกใช้

ตอนนี้ฉันค้นหาและพบบล็อกนี้ ( setState () State Mutation Operation อาจซิงโครนัสใน ReactJS )

ที่นี่เขาพบว่าsetStateเป็น async (เรียกว่าเมื่อ stack ว่างเปล่า) หรือ sync (เรียกทันทีที่เรียก) ขึ้นอยู่กับว่าการเปลี่ยนสถานะถูกกระตุ้นอย่างไร

ตอนนี้สองสิ่งนี้ย่อยยาก

  1. ในบล็อกsetStateฟังก์ชันถูกเรียกใช้ภายในฟังก์ชันupdateStateแต่สิ่งที่เรียกใช้updateStateฟังก์ชันนั้นไม่ใช่สิ่งที่ฟังก์ชันที่เรียกว่าจะรู้
  2. ทำไมพวกเขาถึงทำให้setStateasync เป็น JS เป็นภาษาเธรดเดียวและ setState นี้ไม่ใช่การเรียกใช้ WebAPI หรือเซิร์ฟเวอร์ดังนั้นต้องทำในเธรดของ JS เท่านั้น พวกเขาทำเช่นนี้เพื่อให้ Re-Rendering ไม่หยุดผู้ฟังเหตุการณ์และสิ่งของทั้งหมดหรือมีปัญหาด้านการออกแบบอื่น ๆ


1
วันนี้ผมเขียนบทความที่ช่วยบรรยายสภาพอากาศโดยรอบsetState: medium.com/@agm1984/…
agm1984

คำตอบ:


157

คุณสามารถเรียกใช้ฟังก์ชันหลังจากอัปเดตค่าสถานะแล้ว:

this.setState({foo: 'bar'}, () => { 
    // Do something here. 
});

นอกจากนี้หากคุณมีสถานะมากมายให้อัปเดตพร้อมกันให้จัดกลุ่มสถานะทั้งหมดไว้ในที่เดียวกันsetState:

แทน:

this.setState({foo: "one"}, () => {
    this.setState({bar: "two"});
});

เพียงทำสิ่งนี้:

this.setState({
    foo: "one",
    bar: "two"
});

18
ใช่แล้วเรามีฟังก์ชั่น callBack ที่เราสามารถใช้ได้ แต่ dats ไม่ใช่คำถาม
Anup

12
หวังว่าจะช่วยให้คนอื่นสะดุดกับคำถามนี้ได้อย่างไร
JoeTidee

2
ยา dat มีประโยชน์
Anup

97

1) setStateการดำเนินการเป็นแบบอะซิงโครนัสและเป็นกลุ่มเพื่อเพิ่มประสิทธิภาพ สิ่งนี้ได้อธิบายไว้ในเอกสารของsetState.

setState () ไม่กลายพันธุ์ this.state ทันที แต่สร้างการเปลี่ยนแปลงสถานะที่รอดำเนินการ การเข้าถึง this.state หลังจากเรียกเมธอดนี้สามารถคืนค่าที่มีอยู่ได้ ไม่มีการรับประกันการทำงานแบบซิงโครนัสของการโทรไปยัง setState และการโทรอาจเป็นกลุ่มเพื่อเพิ่มประสิทธิภาพ


2) ทำไมพวกเขาถึงสร้าง setState async เนื่องจาก JS เป็นภาษาเธรดเดียวและนี่setStateไม่ใช่การเรียกใช้ WebAPI หรือเซิร์ฟเวอร์

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

ดังนั้นการเรียก setState จึงเป็นแบบอะซิงโครนัสและแบทช์เพื่อประสบการณ์และประสิทธิภาพ UI ที่ดีขึ้น


59
หากคุณต้องการให้แน่ใจว่ามีการเรียงลำดับเหตุการณ์หลังจากทำการเรียก setState คุณสามารถส่งผ่านฟังก์ชันการโทรกลับได้ this.setState({ something: true }, () => console.log(this.state))
ianks

1
ขอบคุณ @Sachin สำหรับคำอธิบาย อย่างไรก็ตามฉันยังมีข้อสงสัยสามารถซิงโครนัสตามที่บล็อกอธิบายได้หรือไม่
Ajay Gaur

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

ทำไมไม่อนุญาตให้ตั้งค่าตัวเลือกเพื่อทำให้ fucntion เป็น async หรือ sync นั่นจะเป็นคุณสมบัติที่มีประโยชน์
Randall Coding

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

16

ฉันรู้ว่าคำถามนี้เก่า แต่ทำให้ผู้ใช้ reactjs หลายคนสับสนมานานแล้วรวมทั้งฉันด้วย เมื่อเร็ว ๆ นี้ Dan Abramov (จากทีมปฏิกิริยา) เพิ่งเขียนคำอธิบายที่ยอดเยี่ยมว่าเหตุใดลักษณะของsetStateจึงไม่ตรงกัน:

https://github.com/facebook/react/issues/11527#issuecomment-360199710

setStateมีความหมายว่าเป็นแบบอะซิงโครนัสและมีเหตุผลที่ดีบางประการในคำอธิบายที่เชื่อมโยงโดย Dan Abramov นี่ไม่ได้หมายความว่ามันจะเป็นแบบอะซิงโครนัสเสมอไป - ส่วนใหญ่หมายความว่าคุณไม่สามารถขึ้นอยู่กับการซิงโครนัสได้ ReactJS คำนึงถึงตัวแปรหลายตัวในสถานการณ์ที่คุณกำลังเปลี่ยนสถานะเพื่อตัดสินใจว่าstateควรอัปเดตจริงเมื่อใดและส่งคืนส่วนประกอบของคุณ
ตัวอย่างง่ายๆในการสาธิตสิ่งนี้คือหากคุณเรียกsetStateว่าเป็นปฏิกิริยาต่อการกระทำของผู้ใช้สิ่งนั้นstateอาจได้รับการอัปเดตทันที (แม้ว่าคุณจะไม่สามารถวางใจได้อีกครั้ง) ดังนั้นผู้ใช้จะไม่รู้สึกล่าช้า แต่ถ้าคุณโทรsetState ในการตอบสนองต่อการตอบกลับการโทรของ ajax หรือเหตุการณ์อื่น ๆ ที่ไม่ได้ถูกกระตุ้นโดยผู้ใช้สถานะอาจได้รับการอัปเดตด้วยความล่าช้าเล็กน้อยเนื่องจากผู้ใช้จะไม่รู้สึกถึงความล่าช้านี้จริงๆและจะปรับปรุงประสิทธิภาพโดยรอ รวมการอัปเดตหลายสถานะเข้าด้วยกันและแสดงผล DOM ให้น้อยลง


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

@Anup คำตอบนั้นซับซ้อนกว่าแค่ 'async' หรือ 'sync' เล็กน้อย ควรถือว่าเป็น "async" เสมอ แต่ในบางกรณีอาจทำหน้าที่ "sync" ฉันหวังว่าฉันจะให้แสงสว่างแก่คุณ
gillyb

8

บทความดีๆที่นี่ https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

Solution
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

หรือผ่านการโทรกลับ this.setState ({.....},callback)

https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82 https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b



1

ลองนึกภาพการเพิ่มตัวนับในองค์ประกอบบางส่วน:

  class SomeComponent extends Component{

    state = {
      updatedByDiv: '',
      updatedByBtn: '',
      counter: 0
    }

    divCountHandler = () => {
      this.setState({
        updatedByDiv: 'Div',
        counter: this.state.counter + 1
      });
      console.log('divCountHandler executed');
    }

    btnCountHandler = () => {
      this.setState({
        updatedByBtn: 'Button',
        counter: this.state.counter + 1
      });
      console.log('btnCountHandler executed');
    }
    ...
    ...
    render(){
      return (
        ...
        // a parent div
        <div onClick={this.divCountHandler}>
          // a child button
          <button onClick={this.btnCountHandler}>Increment Count</button>
        </div>
        ...
      )
    }
  }

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

อย่างที่เราคิดว่าเหตุการณ์การคลิกเพียงครั้งเดียวบนปุ่มจะทริกเกอร์ตัวจัดการทั้งสองนี้เนื่องจากเหตุการณ์ฟองอากาศจากเป้าหมายไปยังคอนเทนเนอร์ด้านนอกสุดในช่วงฟองสบู่

ดังนั้น btnCountHandler () จึงดำเนินการก่อนโดยคาดว่าจะเพิ่มจำนวนเป็น 1 จากนั้น divCountHandler () จะดำเนินการคาดว่าจะเพิ่มจำนวนเป็น 2

อย่างไรก็ตามจะนับเพิ่มทีละ 1 เท่านั้นตามที่คุณตรวจสอบได้ในเครื่องมือ React Developer

สิ่งนี้พิสูจน์ได้ว่ามีปฏิกิริยา

  • จัดคิวการเรียก setState ทั้งหมด

  • กลับมาที่คิวนี้หลังจากเรียกใช้เมธอดสุดท้ายในบริบท (divCountHandler ในกรณีนี้)

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

  • และส่งผ่านไปยัง setState () เดียวเพื่อป้องกันการแสดงผลซ้ำเนื่องจากการเรียกใช้ setState () หลายครั้ง (นี่เป็นคำอธิบายแบบดั้งเดิมของ batching)

โค้ดผลลัพธ์รันโดย react:

this.setState({
  updatedByDiv: 'Div',
  updatedByBtn: 'Button',
  counter: this.state.counter + 1
})

หากต้องการหยุดพฤติกรรมนี้แทนที่จะส่งอ็อบเจ็กต์เป็นอาร์กิวเมนต์ไปยังเมธอด setState จะมีการส่งการเรียกกลับ

    divCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByDiv: 'Div',
              counter: prevState.counter + 1
            };
          });
          console.log('divCountHandler executed');
        }

    btnCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByBtn: 'Button',
              counter: prevState.counter + 1
            };
          });
      console.log('btnCountHandler executed');
    }

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

การตอบสนองด้วยวิธีนี้ช่วยให้มั่นใจได้ว่าการเรียกกลับครั้งสุดท้ายในคิวจะได้รับการอัปเดตสถานะที่คู่สัญญาก่อนหน้าทั้งหมดได้วางมือไว้


0

ใช่ setState () เป็นแบบอะซิงโครนัส

จากลิงค์: https://reactjs.org/docs/react-component.html#setstate

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

เพราะคิดว่า
จากลิงค์: https://github.com/facebook/react/issues/11527#issuecomment-360199710

... เรายอมรับว่าการแสดงผล setState () แบบซิงโครนัสจะไม่มีประสิทธิภาพในหลาย ๆ กรณี

setState แบบอะซิงโครนัส () ทำให้ชีวิตยากมากสำหรับผู้ที่เริ่มต้นใช้งานและแม้จะประสบกับปัญหาที่น่าเสียดาย:
- ปัญหาการเรนเดอร์ที่ไม่คาดคิด: การแสดงผลล่าช้าหรือไม่มีการเรนเดอร์ (ตามตรรกะของโปรแกรม)
- การส่งผ่านพารามิเตอร์เป็นเรื่องใหญ่
ท่ามกลางปัญหาอื่น ๆ

ตัวอย่างด้านล่างช่วย:

// call doMyTask1 - here we set state
// then after state is updated...
//     call to doMyTask2 to proceed further in program

constructor(props) {
    // ..

    // This binding is necessary to make `this` work in the callback
    this.doMyTask1 = this.doMyTask1.bind(this);
    this.doMyTask2 = this.doMyTask2.bind(this);
}

function doMyTask1(myparam1) {
    // ..

    this.setState(
        {
            mystate1: 'myvalue1',
            mystate2: 'myvalue2'
            // ...
        },    
        () => {
            this.doMyTask2(myparam1); 
        }
    );
}

function doMyTask2(myparam2) {
    // ..
}

หวังว่าจะช่วยได้

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