ความแตกต่างระหว่าง markForCheck () และ detectChanges () คืออะไร


174

ความแตกต่างระหว่างChangeDetectorRef.markForCheck()และChangeDetectorRef.detectChanges()คืออะไร?

ฉันพบข้อมูลเกี่ยวกับ SO เท่านั้นถึงความแตกต่างระหว่างNgZone.run()แต่ไม่ใช่ระหว่างฟังก์ชันทั้งสองนี้

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



@Milad คุณรู้จักเขาลงคะแนนได้อย่างไร? มีผู้คนจำนวนมากที่อ่านเว็บไซต์นี้
Goodbye StackExchange

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

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

3
ช่างเป็นแผนการคดเคี้ยว @parliament!
HankCa

คำตอบ:


234

จากเอกสาร:

detectChanges (): เป็นโมฆะ

ตรวจสอบเครื่องมือตรวจจับการเปลี่ยนแปลงและลูก ๆ ของมัน

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

สถานการณ์ที่เป็นไปได้อาจเป็น:

1- ตัวตรวจจับการเปลี่ยนแปลงถูกแยกออกจากมุมมอง (ดูตัวถอด )

2- มีการอัปเดตเกิดขึ้น แต่ไม่ได้อยู่ในโซนแองกูลาร์ดังนั้นแองกูลาร์จึงไม่ทราบ

เช่นเมื่อฟังก์ชั่นของบุคคลที่สามปรับปรุงโมเดลของคุณและคุณต้องการอัปเดตมุมมองหลังจากนั้น

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

เนื่องจากรหัสนี้อยู่นอกโซนของ Angular (อาจเป็นไปได้) คุณส่วนใหญ่จำเป็นต้องตรวจสอบการเปลี่ยนแปลงและอัปเดตมุมมองดังนี้:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

บันทึก :

มีวิธีอื่นในการทำงานข้างต้นกล่าวอีกนัยหนึ่งมีวิธีอื่นที่จะนำการเปลี่ยนแปลงนั้นมาใช้ในวงจรการเปลี่ยนแปลงเชิงมุม

** คุณสามารถห่อฟังก์ชั่นของบุคคลที่สามภายใน zone.run:

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** คุณสามารถห่อฟังก์ชั่นภายใน setTimeout:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3- นอกจากนี้ยังมีกรณีที่คุณอัปเดตโมเดลหลังจากchange detection cycleเสร็จสิ้นซึ่งในกรณีเหล่านั้นคุณได้รับข้อผิดพลาดที่น่ากลัวนี้:

"นิพจน์เปลี่ยนไปหลังจากตรวจสอบแล้ว";

โดยทั่วไปหมายถึง (จากภาษา Angular2):

ฉันเห็นการเปลี่ยนแปลงในแบบจำลองของคุณที่เกิดจากหนึ่งในวิธีที่ฉันยอมรับ (เหตุการณ์, คำขอ XHR, setTimeout, และ ... ) จากนั้นฉันก็ตรวจจับการเปลี่ยนแปลงเพื่ออัปเดตมุมมองของคุณและฉันก็ทำเสร็จแล้ว ฟังก์ชั่นในรหัสของคุณซึ่งปรับปรุงโมเดลอีกครั้งและฉันไม่ต้องการเรียกใช้การตรวจจับการเปลี่ยนแปลงอีกครั้งเพราะไม่มีการตรวจสอบที่สกปรกเหมือน AngularJS อีกต่อไป: D และเราควรใช้การไหลของข้อมูลทางเดียว!

คุณจะพบข้อผิดพลาดนี้อย่างแน่นอน: P

สองสามวิธีในการแก้ไข:

1- วิธีที่เหมาะสม : ตรวจสอบให้แน่ใจว่าการอัปเดตนั้นอยู่ในวงจรการตรวจจับการเปลี่ยนแปลง (การอัปเดต Angular2 เป็นวิธีหนึ่งที่เกิดขึ้นเพียงครั้งเดียวอย่าอัปเดตโมเดลหลังจากนั้นและย้ายรหัสของคุณไปยังสถานที่ / เวลาที่ดีขึ้น

2- วิธีขี้เกียจ : เรียกใช้ detectChanges () หลังจากการอัปเดตนั้นจะทำให้ angular2 มีความสุขนี่ไม่ใช่วิธีที่ดีที่สุด แต่เมื่อคุณถามว่าสถานการณ์เป็นไปได้อย่างไรนี่เป็นหนึ่งในนั้น

วิธีนี้คุณจะพูดว่า: ฉันรู้ว่าคุณตรวจจับการเปลี่ยนแปลงอย่างจริงใจ แต่ฉันต้องการให้คุณทำอีกครั้งเพราะฉันต้องอัปเดตบางอย่างได้ทันทีหลังจากที่คุณตรวจสอบเสร็จ

3- ใส่รหัสใน a setTimeout, เพราะได้setTimeoutรับการแก้ไขโดยโซนและจะทำงานdetectChangesหลังจากเสร็จสิ้น


จากเอกสาร

markForCheck() : void

ทำเครื่องหมายบรรพบุรุษบรรพบุรุษ ChangeDetectionStrategy ทั้งหมดที่จะตรวจสอบ

นี้เป็นสิ่งจำเป็นส่วนใหญ่เมื่อChangeDetectionStrategyของคอมโพเนนต์ของคุณเป็นOnPush

OnPush หมายความว่าให้รันการตรวจจับการเปลี่ยนแปลงหากสิ่งเหล่านี้เกิดขึ้น:

1- หนึ่งใน @inputs ขององค์ประกอบได้ถูกแทนที่อย่างสมบูรณ์ด้วยค่าใหม่หรือเพียงแค่ใส่ถ้าการอ้างอิงของคุณสมบัติ @Input มีการเปลี่ยนแปลงทั้งหมด

ดังนั้นหากChangeDetectionStrategyของคอมโพเนนต์ของคุณคือOnPushแล้วคุณมี:

   var obj = {
     name:'Milad'
   };

จากนั้นคุณอัปเดต / กลายพันธุ์เช่น:

  obj.name = "a new name";

นี่จะไม่อัปเดตการอ้างอิงobjดังนั้นการตรวจจับการเปลี่ยนแปลงจะไม่ถูกเรียกใช้ดังนั้นมุมมองไม่ได้สะท้อนการปรับปรุง / การกลายพันธุ์

ในกรณีนี้คุณต้องบอก Angular ด้วยตนเองเพื่อตรวจสอบและอัปเดตมุมมอง (markForCheck)

ดังนั้นถ้าคุณทำสิ่งนี้:

  obj.name = "a new name";

คุณต้องทำสิ่งนี้:

  this.cd.markForCheck();

ค่อนข้างด้านล่างจะทำให้การตรวจจับการเปลี่ยนแปลงทำงาน:

    obj = {
      name:"a new name"
    };

ที่สมบูรณ์แทนที่ obj ก่อนหน้านี้มีใหม่{};

2- เหตุการณ์ถูกไล่ออกเช่นคลิกหรืออะไรทำนองนั้นหรือองค์ประกอบย่อยใด ๆ ที่ทำให้เกิดเหตุการณ์

กิจกรรมที่ชอบ:

  • คลิก
  • keyup
  • กิจกรรมการสมัครสมาชิก
  • เป็นต้น

ดังนั้นในระยะสั้น:

  • ใช้detectChanges()เมื่อคุณอัปเดตโมเดลหลังจากแองกูลาร์แล้วมันเป็นการตรวจจับการเปลี่ยนแปลงหรือหากการอัปเดตไม่ได้อยู่ในโลกเชิงมุมเลย

  • ใช้markForCheck()หากคุณกำลังใช้ OnPush และคุณอ้อมChangeDetectionStrategyโดยกรรมวิธีข้อมูลบางส่วนหรือคุณอัปเดตรูปแบบภายในsetTimeout ;


6
ดังนั้นหากคุณกลายพันธุ์ obj มุมมองนั้นจะไม่ได้รับการปรับปรุงและแม้ว่าคุณจะเรียกใช้ detectChanges ก็จะไม่ทำงานเพราะไม่มีการเปลี่ยนแปลงใด ๆ - สิ่งนี้ไม่เป็นความจริง detectChangesมุมมองการปรับปรุง ดูคำอธิบายเชิงลึกนี้
Max Koretskyi

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

@ Maximus เกี่ยวกับความคิดเห็นแรกของคุณฉันอ่านโพสต์ของคุณขอบคุณที่มันดี แต่ในคำอธิบายของคุณคุณกำลังบอกว่ากลยุทธ์คือ OnPush หมายความว่าถ้าthis.cdMode === ChangeDetectorStatus.Checkedไม่อัปเดตมุมมองนั่นคือเหตุผลที่คุณจะใช้ markForCheck
Milad

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

@Milad ความคิดเห็นเหล่านั้นมาจาก @estus :) detectChangesเป็นเรื่องเกี่ยวกับเหมืองแร่ และไม่มีในเชิงมุมcdMode 4.x.xฉันเขียนเกี่ยวกับสิ่งนั้นในบทความของฉัน ดีใจที่คุณชอบมัน. อย่าลืมว่าคุณสามารถแนะนำบนสื่อหรือตามฉัน :)
Max Koretskyi

99

ความแตกต่างที่ใหญ่ที่สุดระหว่างสองคือการdetectChanges()กระตุ้นการตรวจจับการเปลี่ยนแปลงในขณะที่markForCheck()ไม่เรียกการตรวจจับการเปลี่ยนแปลง

detectChanges

หนึ่งนี้จะใช้ในการเรียกใช้การตรวจจับการเปลี่ยนแปลงต้นไม้ของส่วนประกอบที่เริ่มต้นด้วยส่วนประกอบที่คุณเรียกdetectChanges()บน ดังนั้นการตรวจจับการเปลี่ยนแปลงจะทำงานสำหรับองค์ประกอบปัจจุบันและลูกทั้งหมด Angular เก็บการอ้างอิงไปยังทรีคอมโพเนนต์ของรากในApplicationRefและเมื่อการดำเนินการใด ๆ ของ async เกิดขึ้นทริกเกอร์การตรวจหาการเปลี่ยนแปลงในองค์ประกอบของรูทนี้ด้วยวิธีการ wrapper tick():

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

viewนี่คือมุมมององค์ประกอบรูท สามารถมีส่วนประกอบรากมากที่สุดเท่าที่ผมอธิบายไว้ในสิ่งที่เป็นความหมายของการ bootstrapping ส่วนประกอบหลาย

@milad อธิบายถึงสาเหตุที่คุณอาจต้องเรียกใช้การตรวจจับการเปลี่ยนแปลงด้วยตนเอง

markForCheck

อย่างที่ฉันพูดไปผู้ชายคนนี้ไม่เรียกการตรวจจับการเปลี่ยนแปลงเลย ChecksEnabledมันก็จะไปขึ้นมาจากองค์ประกอบปัจจุบันไปยังส่วนรากและการปรับปรุงสถานะของมุมมองของพวกเขาไป นี่คือรหัสที่มา:

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  <-----------------
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

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

วิธีการนี้มักใช้ใน ngDoCheckตะขอวงจรชีวิต คุณสามารถอ่านเพิ่มเติมในหากคุณคิดว่าngDoCheckหมายถึงองค์ประกอบของคุณจะถูกตรวจสอบ - อ่านบทความนี้

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


1
เหตุใด detectChanges จึงทำงานกับส่วนประกอบและลูก ๆ ของมันในขณะที่ทำเครื่องหมาย forCheck กับส่วนประกอบและบรรพบุรุษ
pablo

@pablo นั่นคือการออกแบบ ฉันไม่คุ้นเคยกับเหตุผลจริง ๆ
Max Koretskyi

@ AngularInDepth.com มีการเปลี่ยนแปลงการบล็อก UI หรือไม่หากมีการประมวลผลที่เข้มข้นมาก
alt255

1
@jerry วิธีที่แนะนำคือการใช้ท่อ async markForCheckซึ่งภายในติดตามการสมัครสมาชิกและในทุกการเรียกค่าใหม่ ดังนั้นหากคุณไม่ได้ใช้ async ไปป์นั่นอาจเป็นสิ่งที่คุณควรใช้ อย่างไรก็ตามโปรดทราบว่าการอัปเดตร้านค้าควรเกิดขึ้นเนื่องจากเหตุการณ์ async บางอย่างเพื่อให้การตรวจจับการเปลี่ยนแปลงเริ่มต้นขึ้น นั่นคือกรณีส่วนใหญ่เสมอ แต่มีข้อยกเว้นblog.angularindepth.com/…
Max Koretskyi

1
@ MaxKoretskyiakaWizard ขอบคุณสำหรับการตอบกลับ ใช่การอัปเดตร้านค้าส่วนใหญ่เป็นผลมาจากการดึงข้อมูลหรือการตั้งค่า isFetching มาก่อน และหลังจากการดึงข้อมูล .. แต่เราไม่สามารถใช้งานได้ตลอดเวลาasync pipeเนื่องจากภายในการสมัครรับข้อมูลเรามักจะมีบางสิ่งที่ต้องทำcall setFromValues do some comparison.. และถ้าasyncตัวเองเรียกmarkForCheckว่าปัญหาคืออะไรถ้าเราเรียกตัวเองว่า? แต่อีกครั้งเรามักจะมีตัวเลือก 2-3 ตัวหรือมากกว่านั้นในngOnInitการรับข้อมูลที่แตกต่างกัน ... และเราเรียกmarkForCheckพวกเขาทั้งหมด .. ตกลงไหม?
jerry

0

cd.detectChanges() จะเรียกใช้การตรวจจับการเปลี่ยนแปลงทันทีจากองค์ประกอบปัจจุบันลงผ่านลูกหลานของมัน

cd.markForCheck()จะไม่เรียกใช้การตรวจจับการเปลี่ยนแปลง แต่ทำเครื่องหมายบรรพบุรุษของมันว่าต้องการเรียกใช้การตรวจจับการเปลี่ยนแปลง การตรวจจับการเปลี่ยนแปลงครั้งต่อไปทำงานได้ทุกที่มันจะทำงานสำหรับองค์ประกอบเหล่านั้นที่ทำเครื่องหมายไว้ด้วย

  • cd.markForCheck()หากคุณต้องการที่จะลดจำนวนครั้งของการตรวจสอบการเปลี่ยนแปลงที่เรียกว่าการใช้งาน บ่อยครั้งที่การเปลี่ยนแปลงส่งผลกระทบต่อหลายองค์ประกอบและการตรวจจับการเปลี่ยนแปลงบางอย่างจะถูกเรียก คุณกำลังบอกว่า: ขอให้แน่ใจว่าองค์ประกอบนี้ยังได้รับการปรับปรุงเมื่อมันเกิดขึ้น (มุมมองจะได้รับการอัปเดตทันทีในทุกโครงการที่ฉันเขียน แต่ไม่ใช่ในการทดสอบทุกหน่วย)
  • ถ้าคุณไม่สามารถมั่นใจได้ว่าcd.detectChanges()จะไม่ได้ขณะ ทำงานcd.markForCheck()การตรวจสอบการเปลี่ยนแปลงการใช้งาน detectChanges()จะเกิดข้อผิดพลาดในกรณีนั้น นี่อาจหมายความว่าคุณกำลังพยายามแก้ไขสถานะขององค์ประกอบที่เป็นบรรพบุรุษซึ่งทำงานกับสมมติฐานที่การตรวจจับการเปลี่ยนแปลงของ Angular ได้รับการออกแบบมา
  • ถ้ามันสำคัญที่การปรับปรุงมุมมองพร้อมกันก่อนที่จะดำเนินการอื่น ๆ detectChanges()บางการใช้งาน markForCheck()อาจไม่อัปเดตมุมมองของคุณตามเวลาจริง ตัวอย่างเช่นการทดสอบหน่วยมีผลกระทบต่อมุมมองของคุณเช่นคุณอาจต้องโทรหาคุณด้วยตนเองfixture.detectChanges()เมื่อไม่จำเป็นต้องใช้แอพ
  • หากคุณกำลังเปลี่ยนสถานะในองค์ประกอบที่มีบรรพบุรุษมากกว่าลูกหลานคุณอาจได้รับประสิทธิภาพเพิ่มขึ้นโดยใช้detectChanges()เนื่องจากคุณไม่ได้เรียกใช้การตรวจจับการเปลี่ยนแปลงในบรรพบุรุษขององค์ประกอบโดยไม่จำเป็น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.