การใช้ async componentDidMount () ดีหรือไม่?


139

การใช้componentDidMount()เป็นฟังก์ชัน async แนวปฏิบัติที่ดีใน React Native หรือฉันควรหลีกเลี่ยง

ฉันต้องการข้อมูลบางอย่างจากAsyncStorageเวลาที่ส่วนประกอบติดตั้ง แต่วิธีเดียวที่ฉันรู้ว่าจะทำให้เป็นไปได้คือทำให้componentDidMount()ฟังก์ชันไม่ตรงกัน

async componentDidMount() {
    let auth = await this.getAuth();
    if (auth) 
        this.checkAuth(auth);
}

มีปัญหาหรือไม่และมีวิธีแก้ไขปัญหาอื่น ๆ หรือไม่?


2
"การปฏิบัติดี" เป็นเรื่องของความคิดเห็น ได้ผลหรือไม่? ใช่.
Kraylog

2
นี่คือบทความดีๆที่แสดงให้เห็นว่าทำไม async ถึงคอยเป็นตัวเลือกที่ดีมากกว่าคำสัญญาที่hackernoon.com/…
Shubham Khatri

เพียงใช้ redux-thunk มันจะช่วยแก้ปัญหาได้
Tilak Maddy

@TilakMaddy ทำไมคุณถึงคิดว่าทุกแอป react ใช้ redux?
Mirakurun

@ มิราคุรันทำไมทั้งสแต็กล้นจึงคิดว่าฉันใช้ jQuery เมื่อฉันเคยถามคำถามจาวาสคริปต์ธรรมดาในวันนั้น
Tilak Maddy

คำตอบ:


162

เริ่มต้นด้วยการชี้ให้เห็นความแตกต่างและพิจารณาว่าจะทำให้เกิดปัญหาได้อย่างไร

นี่คือรหัสของ async และ "sync" componentDidMount()life-cycle method:

// This is typescript code
componentDidMount(): void { /* do something */ }

async componentDidMount(): Promise<void> {
    /* do something */
    /* You can use "await" here */
}

เมื่อดูรหัสฉันสามารถชี้ให้เห็นความแตกต่างดังต่อไปนี้:

  1. asyncคำหลัก: ใน typescript นี้เป็นเพียงเครื่องหมายรหัส มันทำ 2 อย่าง:
    • บังคับให้พิมพ์กลับจะเป็นแทนPromise<void> voidหากคุณระบุประเภทการส่งคืนอย่างชัดเจนว่าไม่ใช่สัญญา (เช่นโมฆะ) typescript จะพ่นข้อผิดพลาดมาที่คุณ
    • อนุญาตให้คุณใช้awaitคีย์เวิร์ดในเมธอด
  2. ประเภทการส่งคืนเปลี่ยนจากvoidเป็นPromise<void>
    • หมายความว่าคุณสามารถทำได้แล้ว:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. ตอนนี้คุณสามารถใช้awaitคำสำคัญในวิธีการและหยุดการทำงานชั่วคราว แบบนี้:

    async componentDidMount(): Promise<void> {
        const users = await axios.get<string>("http://localhost:9001/users");
        const questions = await axios.get<string>("http://localhost:9001/questions");
    
        // Sleep for 10 seconds
        await new Promise(resolve => { setTimeout(resolve, 10000); });
    
        // This line of code will be executed after 10+ seconds
        this.setState({users, questions});
        return Promise.resolve();
    }

ตอนนี้พวกเขาสร้างปัญหาได้อย่างไร?

  1. asyncคำหลักที่เป็นอันตรายอย่างยิ่ง
  2. ฉันนึกภาพไม่ออกว่าสถานการณ์ใดที่คุณต้องโทรไปที่componentDidMount()เมธอดดังนั้นประเภทการส่งคืนPromise<void>ก็ไม่เป็นอันตรายเช่นกัน

    การเรียกใช้เมธอดที่มีผลตอบแทนประเภทPromise<void>ไม่มีawaitคีย์เวิร์ดจะไม่สร้างความแตกต่างจากการเรียกใช้แบบรีเทิvoidร์น

  3. เนื่องจากไม่มีวิธีการตลอดอายุการใช้งานหลังจากcomponentDidMount()ชะลอการดำเนินการจึงดูเหมือนจะค่อนข้างปลอดภัย แต่มี gotcha

    สมมติว่าข้างต้นthis.setState({users, questions});จะดำเนินการหลังจาก 10 วินาที ในช่วงเวลาที่ล่าช้าอีก ...

    this.setState({users: newerUsers, questions: newerQuestions});

    ... ดำเนินการสำเร็จและมีการอัปเดต DOM ผู้ใช้สามารถมองเห็นผลลัพธ์ได้ นาฬิกายังคงเดินต่อไปและเวลาผ่านไป 10 วินาที ล่าช้าthis.setState(...)จากนั้นจะดำเนินการและ DOM จะได้รับการอัปเดตอีกครั้งในครั้งนั้นกับผู้ใช้เก่าและคำถามเก่า ผลลัพธ์ก็จะปรากฏให้ผู้ใช้เห็นเช่นกัน

=> ค่อนข้างปลอดภัย (ไม่แน่ใจ 100%) ที่จะใช้asyncกับcomponentDidMount()วิธีการ ฉันเป็นแฟนตัวยงของเรื่องนี้และจนถึงตอนนี้ฉันยังไม่พบปัญหาใด ๆ ที่ทำให้ฉันปวดหัวมากเกินไป


เมื่อคุณพูดถึงปัญหาที่ setState อื่นเกิดขึ้นก่อน Promise ที่รอดำเนินการนั้นไม่เหมือนกันกับ Promise ที่ไม่มีน้ำตาลซิงก์ / await หรือแม้แต่การเรียกกลับแบบคลาสสิก
Clafou

3
ใช่ ความล่าช้าsetState()มักมีความเสี่ยงเล็กน้อย เราควรดำเนินการด้วยความระมัดระวัง
CùĐứcHiếu

ฉันเดาว่าวิธีหนึ่งในการหลีกเลี่ยงปัญหาคือการใช้บางอย่างเช่นisFetching: trueภายในสถานะของส่วนประกอบ ฉันใช้สิ่งนี้กับ redux เท่านั้น แต่ฉันคิดว่ามันใช้ได้อย่างสมบูรณ์กับการจัดการสถานะตอบสนองเท่านั้น แม้ว่ามันจะไม่ได้แก้ปัญหาของสถานะเดียวกันที่ได้รับการอัปเดตที่อื่นในรหัส
ก็ตาม

1
ฉันเห็นด้วยกับที่ ในความเป็นจริงisFetchingวิธีแก้ปัญหาการตั้งค่าสถานะเป็นเรื่องปกติโดยเฉพาะอย่างยิ่งเมื่อเราต้องการเล่นภาพเคลื่อนไหวในส่วนหน้าในขณะที่รอการตอบกลับส่วนหลัง ( isFetching: true)
CùĐứcHiếu

3
คุณสามารถประสบปัญหาได้หากคุณทำ setState หลังจากที่ไม่ได้ต่อเชื่อมส่วนประกอบ
Eliezer Steinbock

18

อัปเดตเมษายน 2020: ดูเหมือนว่าปัญหาจะได้รับการแก้ไขแล้วใน React 16.13.1 ล่าสุดโปรดดูตัวอย่างแซนด์บ็อกซ์นี้ ขอบคุณ @abernier ที่ชี้ให้เห็นสิ่งนี้


ฉันได้ทำการวิจัยและพบความแตกต่างที่สำคัญอย่างหนึ่ง: React ไม่ประมวลผลข้อผิดพลาดจากวิธีการ async lifecycle

ดังนั้นหากคุณเขียนสิ่งนี้:

componentDidMount()
{
    throw new Error('I crashed!');
}

จากนั้นข้อผิดพลาดของคุณจะถูกจับโดยขอบเขตของข้อผิดพลาดและคุณสามารถประมวลผลและแสดงข้อความที่สวยงามได้

หากเราเปลี่ยนรหัสดังนี้:

async componentDidMount()
{
    throw new Error('I crashed!');
}

ซึ่งเทียบเท่ากับสิ่งนี้:

componentDidMount()
{
    return Promise.reject(new Error('I crashed!'));
}

จากนั้นข้อผิดพลาดของคุณจะถูกกลืนหายไปอย่างเงียบ ๆข้อผิดพลาดของคุณจะถูกกลืนกินอย่างเงียบอัปยศคุณตอบสนอง ...

ดังนั้นเราจะประมวลผลข้อผิดพลาดได้อย่างไร? วิธีเดียวที่ดูเหมือนจะชัดเจนเช่นนี้:

async componentDidMount()
{
    try
    {
         await myAsyncFunction();
    }
    catch(error)
    {
        //...
    }
}

หรือเช่นนี้:

componentDidMount()
{
    myAsyncFunction()
    .catch(()=>
    {
        //...
    });
}

หากเรายังคงต้องการให้ข้อผิดพลาดของเราไปถึงขอบเขตข้อผิดพลาดฉันสามารถคิดถึงเคล็ดลับต่อไปนี้:

  1. ตรวจจับข้อผิดพลาดทำให้ตัวจัดการข้อผิดพลาดเปลี่ยนสถานะส่วนประกอบ
  2. หากสถานะระบุข้อผิดพลาดให้โยนออกจากrenderเมธอด

ตัวอย่าง:

class BuggyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}

  async componentDidMount() {
    try
    {
      await this.buggyAsyncfunction();
    }
    catch(error)
    {
        this.setState({error: error});
    }
  }

  render() {
    if(this.state.error)
        throw this.state.error;

    return <h1>I am OK</h1>;
  }
}

มีรายงานปัญหานี้หรือไม่ อาจเป็นประโยชน์ในการรายงานหากยังคงเป็นกรณีนี้ ... thx
abernier

@abernier ฉันคิดว่ามันเป็นไปโดย deign ... แม้ว่าพวกเขาอาจจะปรับปรุงได้ ฉันไม่ได้ยื่นเรื่องเกี่ยวกับเรื่องนี้ ...
CF

1
ดูเหมือนว่าจะไม่เป็นเช่นนั้นอีกต่อไปอย่างน้อยที่สุดด้วย React 16.13.1 ตามที่ทดสอบที่นี่: codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier

9

รหัสของคุณดีและอ่านง่ายมากสำหรับฉัน ดูบทความของ Dale Jeffersonที่เขาแสดงcomponentDidMountตัวอย่างasync และดูดีมากเช่นกัน

แต่บางคนจะบอกว่าคนที่อ่านโค้ดอาจคิดว่า React ทำอะไรบางอย่างกับสัญญาที่ส่งคืน

ดังนั้นการตีความรหัสนี้และถ้าเป็นแนวปฏิบัติที่ดีหรือไม่เป็นเรื่องส่วนตัวมาก

หากคุณต้องการแก้ไขปัญหาอื่นที่คุณสามารถใช้สัญญา ตัวอย่างเช่น:

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}

3
... หรือใช้asyncฟังก์ชันอินไลน์โดยมีawaits อยู่ข้างใน ... ?
Erik Kaplun

ยังเป็นตัวเลือก @ErikAllik :)
Tiago Alves

@ErikAllik คุณมีตัวอย่างหรือไม่?
Pablo Rincon

1
@PabloRincon smth ชอบ(async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()ที่ไหนfetchและsubmitRequestเป็นฟังก์ชันที่คืนสัญญา
Erik Kaplun

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

6

เมื่อคุณใช้componentDidMountโดยไม่มีasyncคีย์เวิร์ดเอกสารจะกล่าวว่า:

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

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


1
ฉันชอบคำตอบนี้เพราะเอกสารมีความกระชับและสนับสนุน คุณช่วยเพิ่มลิงค์ไปยังเอกสารที่คุณอ้างอิงได้ไหม
theUtherSide

สิ่งนี้อาจเป็นสิ่งที่ดีเช่นหากคุณกำลังแสดงสถานะการโหลดในขณะที่ทรัพยากรกำลังโหลดจากนั้นเนื้อหาเมื่อเสร็จสิ้น
Hjulle

3

ปรับปรุง:

(โครงสร้างของฉัน: React 16, Webpack 4, Babel 7):

เมื่อใช้ Babel 7 คุณจะค้นพบ:

การใช้รูปแบบนี้ ...

async componentDidMount() {
    try {
        const res = await fetch(config.discover.url);
        const data = await res.json();
        console.log(data);
    } catch(e) {
        console.error(e);
    }
}

คุณจะพบข้อผิดพลาดต่อไปนี้ ...

Uncaught ReferenceError: ไม่ได้กำหนด regeneratorRuntime

ในกรณีนี้คุณจะต้องติดตั้งbabel-plugin-transform-runtime

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

หากด้วยเหตุผลบางประการคุณไม่ต้องการติดตั้งแพ็คเกจด้านบน (babel-plugin-transform-runtime) คุณจะต้องยึดติดกับรูปแบบ Promise ...

componentDidMount() {
    fetch(config.discover.url)
    .then(res => res.json())
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error(err));
}

3

ฉันคิดว่ามันสบายดีตราบใดที่คุณรู้ว่าคุณกำลังทำอะไรอยู่ แต่อาจทำให้เกิดความสับสนได้เนื่องจากasync componentDidMount()ยังคงสามารถทำงานcomponentWillUnmountได้หลังจากที่รันและส่วนประกอบถูกยกเลิกการต่อเชื่อม

componentDidMountนอกจากนี้คุณยังอาจต้องการที่จะเริ่มต้นงานทั้งซิงโครและไม่ตรงกันภายใน หากcomponentDidMountเป็น async awaitคุณจะต้องใส่ทุกรหัสซิงโครก่อนคนแรก อาจไม่ชัดเจนสำหรับบางคนที่รหัสก่อนการawaitทำงานครั้งแรกพร้อมกัน ในกรณีนี้ฉันอาจจะคงcomponentDidMountซิงโครนัสไว้แต่ให้เรียกใช้วิธีการซิงค์และ async

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


2

ที่จริงแล้วการโหลดแบบ async ใน ComponentDidMount เป็นรูปแบบการออกแบบที่แนะนำเนื่องจาก React จะย้ายออกจากวิธีการใช้งานแบบเดิม (componentWillMount, componentWillReceiveProps, componentWillUpdate) และไปยัง Async Rendering

บล็อกโพสต์นี้มีประโยชน์มากในการอธิบายว่าเหตุใดจึงปลอดภัยและเป็นตัวอย่างสำหรับการโหลด async ใน ComponentDidMount:

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html


3
การเรนเดอร์ Async ไม่มีส่วนเกี่ยวข้องกับการทำให้วงจรการใช้งานไม่ตรงกันอย่างชัดเจน มันเป็นรูปแบบการต่อต้านจริงๆ วิธีแก้ปัญหาที่แนะนำคือการเรียกเมธอด async จาก lifecycle method
Clayton Ray

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