Sự khác biệt giữa markForCheck () và DetChanges ()


174

Sự khác biệt giữa ChangeDetectorRef.markForCheck()và là ChangeDetectorRef.detectChanges()gì?

Tôi chỉ tìm thấy thông tin về SO về sự khác biệt giữa NgZone.run(), nhưng không phải giữa hai chức năng này.

Đối với câu trả lời chỉ có tham chiếu đến tài liệu, vui lòng minh họa một số tình huống thực tế để chọn cái khác.



@Milad Làm thế nào để bạn biết anh ấy hạ cấp nó? Có rất nhiều người lướt qua trang web này.
Tạm biệt StackExchange

2
@FrankerZ, vì tôi đang viết và tôi đã thấy downvote và một giây sau câu hỏi đã được cập nhật rằng "Đối với câu trả lời chỉ có tham chiếu đến tài liệu, vui lòng minh họa một số tình huống thực tế để chọn một kịch bản khác? Điều đó sẽ giúp làm rõ nó trong tâm trí của tôi ".
Milad

3
Các downvote là để khuyến khích bạn hoàn thành câu trả lời ban đầu chỉ là sao chép và dán từ các tài liệu mà tôi đã thấy. Va no đa hoạt động! Bây giờ câu trả lời đã có nhiều sự rõ ràng và là câu trả lời được chấp nhận, cảm ơn :)
quốc hội

3
Thật là một kế hoạch lệch lạc @par Nghị!
HankCa

Câu trả lời:


234

Từ tài liệu:

DetChanges (): void

Kiểm tra các máy dò thay đổi và con của nó.

Điều đó có nghĩa là, nếu có trường hợp bất kỳ điều gì trong mô hình của bạn (lớp của bạn) đã thay đổi nhưng nó không phản ánh chế độ xem, bạn có thể cần thông báo cho Angular để phát hiện những thay đổi đó (phát hiện các thay đổi cục bộ) và cập nhật chế độ xem.

Các tình huống có thể xảy ra có thể là:

1- Bộ phát hiện thay đổi được tách ra khỏi chế độ xem (xem phần tách ra )

2- Một bản cập nhật đã xảy ra nhưng nó không nằm trong Khu vực góc, do đó, Angular không biết về nó.

Giống như khi chức năng của bên thứ ba đã cập nhật mô hình của bạn và bạn muốn cập nhật chế độ xem sau đó.

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

Vì mã này nằm ngoài vùng của Angular (có thể), nên rất có thể bạn cần đảm bảo phát hiện các thay đổi và cập nhật chế độ xem, do đó:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

LƯU Ý :

Có nhiều cách khác để thực hiện công việc trên, nói cách khác, có những cách khác để mang lại sự thay đổi đó trong chu kỳ thay đổi Angular.

** Bạn có thể bao bọc chức năng của bên thứ ba bên trong một khu vực.

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

** Bạn có thể gói chức năng bên trong setTimeout:

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

3- Cũng có trường hợp bạn cập nhật mô hình sau khi change detection cyclekết thúc, trong trường hợp đó bạn gặp phải lỗi đáng sợ này:

"Biểu hiện đã thay đổi sau khi được kiểm tra";

Điều này thường có nghĩa là (từ ngôn ngữ Angular2):

Tôi đã thấy một sự thay đổi trong mô hình của bạn do một trong những cách được chấp nhận của tôi (sự kiện, yêu cầu XHR, setTimeout và ...) và sau đó tôi đã chạy phát hiện thay đổi của mình để cập nhật chế độ xem của bạn và tôi đã hoàn thành nó, nhưng sau đó có một cách khác Chức năng trong mã của bạn đã cập nhật lại mô hình và tôi không muốn chạy lại phát hiện thay đổi của mình vì không còn kiểm tra bẩn như AngularJS nữa: D và chúng ta nên sử dụng luồng dữ liệu một chiều!

Bạn chắc chắn sẽ gặp phải lỗi này: P.

Một số cách để khắc phục nó:

1- Cách thích hợp : đảm bảo rằng bản cập nhật nằm trong chu kỳ phát hiện thay đổi (Cập nhật Angular2 là luồng một chiều xảy ra một lần, không cập nhật mô hình sau đó và di chuyển mã của bạn đến nơi / thời gian tốt hơn).

2- Cách lười biếng : chạy DetChanges () sau bản cập nhật đó để làm cho angular2 hài lòng, đây chắc chắn không phải là cách tốt nhất, nhưng khi bạn hỏi các tình huống có thể xảy ra, đây là một trong số chúng.

Theo cách này bạn đang nói: Tôi chân thành biết bạn đã chạy phát hiện thay đổi, nhưng tôi muốn bạn làm lại vì tôi phải cập nhật một cái gì đó ngay sau khi bạn hoàn thành việc kiểm tra.

3- Đặt mã bên trong a setTimeout, vì setTimeoutđược vá theo vùng và sẽ chạy detectChangessau khi hoàn thành.


Từ các tài liệu

markForCheck() : void

Đánh dấu tất cả các tổ tiên ChangeDetectionStrargety sẽ được kiểm tra.

Điều này chủ yếu là cần thiết khi ChangeDetectionStrargety của thành phần của bạn là OnPush .

OnPush có nghĩa là, chỉ chạy phát hiện thay đổi nếu bất kỳ điều nào trong số này đã xảy ra:

1- Một trong những @input của thành phần đã được thay thế hoàn toàn bằng một giá trị mới, hoặc đơn giản là, nếu tham chiếu của thuộc tính @Input đã thay đổi hoàn toàn.

Vì vậy, nếu ChangeDetectionStrargety của thành phần của bạn là OnPush và sau đó bạn có:

   var obj = {
     name:'Milad'
   };

Và sau đó bạn cập nhật / biến đổi nó như sau:

  obj.name = "a new name";

Điều này sẽ không cập nhật tham chiếu obj , do đó phát hiện thay đổi sẽ không chạy, do đó chế độ xem không phản ánh cập nhật / đột biến.

Trong trường hợp này, bạn phải thông báo thủ công cho Angular để kiểm tra và cập nhật chế độ xem (markForCheck);

Vì vậy, nếu bạn đã làm điều này:

  obj.name = "a new name";

Bạn cần phải làm điều này:

  this.cd.markForCheck();

Thay vào đó, bên dưới sẽ khiến phát hiện thay đổi chạy:

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

Mà thay thế hoàn toàn obj trước bằng một cái mới {};

2- Một sự kiện đã được kích hoạt, như một cú nhấp chuột hoặc một cái gì đó tương tự hoặc bất kỳ thành phần con nào đã phát ra một sự kiện.

Các sự kiện như:

  • Nhấp chuột
  • Keyup
  • Sự kiện đăng ký
  • Vân vân.

Vì vậy, trong ngắn hạn:

  • Sử dụng detectChanges()khi bạn đã cập nhật mô hình sau khi góc đã chạy phát hiện thay đổi hoặc nếu bản cập nhật hoàn toàn không ở trong thế giới góc cạnh.

  • Sử dụng markForCheck()nếu bạn đang sử dụng OnPush và bạn đang bỏ qua ChangeDetectionStrategybằng cách thay đổi một số dữ liệu hoặc bạn đã cập nhật mô hình bên trong setTimeout ;


6
Vì vậy, nếu bạn thay đổi obj đó, chế độ xem sẽ không được cập nhật và ngay cả khi bạn chạy DetChanges, sẽ không hoạt động vì không có bất kỳ thay đổi nào - điều này không đúng. detectChangesxem cập nhật. Xem giải thích sâu sắc này .
Max Koretskyi

Về kết luận markForCheck, nó cũng không chính xác. Đây là ví dụ được sửa đổi từ câu hỏi này , nó không phát hiện các thay đổi đối tượng với OnPush và markForCheck. Nhưng ví dụ tương tự sẽ hoạt động nếu không có chiến lược OnPush.
Estus Flask

@Maximus, Về bình luận đầu tiên của bạn, tôi đã đọc bài viết của bạn, cảm ơn vì điều đó là tốt. Nhưng trong lời giải thích của bạn, bạn đang nói nếu chiến lược là OnPush, có nghĩa là nếu this.cdMode === ChangeDetectorStatus.Checkednó không cập nhật chế độ xem, đó là lý do tại sao bạn sử dụng markForCheck.
Milad

Và về các liên kết của bạn với plunker, cả hai ví dụ đều hoạt động tốt với tôi, tôi không biết ý của bạn là gì
Milad

@Milad, những bình luận đó đến từ @estus :). Của tôi là về detectChanges. Và không có cdModetrong Angular 4.x.x. Tôi viết về điều đó trong bài viết của tôi. Vui mừng bạn thích nó. Đừng quên bạn có thể giới thiệu nó trên phương tiện hoặc theo dõi tôi :)
Max Koretskyi

99

Sự khác biệt lớn nhất giữa hai là detectChanges()thực sự kích hoạt phát hiện thay đổi, trong khi markForCheck()không kích hoạt phát hiện thay đổi.

phát hiện thay đổi

Cái này được sử dụng để chạy phát hiện thay đổi cho cây các thành phần bắt đầu bằng thành phần mà bạn kích hoạt detectChanges(). Vì vậy, phát hiện thay đổi sẽ chạy cho thành phần hiện tại và tất cả các con của nó. Angular giữ các tham chiếu đến cây thành phần gốc trong ApplicationRefvà khi bất kỳ hoạt động không đồng bộ nào xảy ra, nó kích hoạt phát hiện thay đổi trên thành phần gốc này thông qua phương thức trình bao bọc 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Đây là khung nhìn thành phần gốc. Có thể có nhiều thành phần gốc như tôi đã mô tả trong phần Ý nghĩa của việc khởi động nhiều thành phần .

@milad đã mô tả lý do tại sao bạn có thể cần phải kích hoạt phát hiện thay đổi theo cách thủ công.

markForCheck

Như tôi đã nói, anh chàng này hoàn toàn không kích hoạt phát hiện thay đổi. Nó chỉ đơn giản là đi lên từ thành phần hiện tại đến thành phần gốc và cập nhật trạng thái xem của chúng thành ChecksEnabled. Đây là mã nguồn:

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;
  }
}

Phát hiện thay đổi thực tế cho thành phần không được lên lịch nhưng khi nó sẽ xảy ra trong tương lai (là một phần của chu kỳ CD hiện tại hoặc tiếp theo), các khung nhìn thành phần cha mẹ sẽ được kiểm tra ngay cả khi chúng đã tách rời các bộ phát hiện thay đổi. Các bộ phát hiện thay đổi có thể được tách ra bằng cách sử dụng cd.detach()hoặc bằng cách chỉ định OnPushchiến lược phát hiện thay đổi. Tất cả các trình xử lý sự kiện riêng đánh dấu tất cả các khung nhìn thành phần cha mẹ để kiểm tra.

Cách tiếp cận này thường được sử dụng trong ngDoCheckmóc vòng đời. Bạn có thể đọc thêm trong phần Nếu bạn nghĩ ngDoCheckcó nghĩa là thành phần của bạn đang được kiểm tra - hãy đọc bài viết này .

Xem thêm Mọi thứ bạn cần biết về phát hiện thay đổi trong Angular để biết thêm chi tiết.


1
Tại sao DetChanges hoạt động trên thành phần và con của nó trong khi markForCheck trên thành phần và tổ tiên?
pablo

@pablo, đó là do thiết kế. Tôi không thực sự quen thuộc với lý do
Max Koretskyi

@ AngularInDepth.com có ​​thay đổi chặn giao diện người dùng nếu có xử lý rất chuyên sâu?
alt255

1
@jerry, cách tiếp cận được đề xuất là sử dụng ống async, theo dõi nội bộ đăng ký và trên mỗi kích hoạt giá trị mới markForCheck. Vì vậy, nếu bạn không sử dụng ống không đồng bộ, đó có lẽ là những gì bạn nên sử dụng. Tuy nhiên, hãy nhớ rằng việc cập nhật cửa hàng sẽ xảy ra do một số sự kiện không đồng bộ để phát hiện thay đổi bắt đầu. Đó luôn là trường hợp. Nhưng có những trường hợp ngoại lệ blog.angularindepth.com/
Kor Korkykyi 27/11/18

1
@MaxKoretskyiakaWizard cảm ơn bạn đã trả lời. Có, bản cập nhật cửa hàng chủ yếu là kết quả của quá trình tìm nạp hoặc cài đặt là Tìm nạp trước đó. và sau khi tìm nạp .. nhưng chúng ta không thể luôn luôn sử dụng async pipevì trong đăng ký, chúng ta thường có một vài việc phải làm như thế call setFromValues do some comparison.. và nếu asyncchính nó gọi markForChecklà vấn đề gì nếu chúng ta tự gọi nó? nhưng một lần nữa, chúng ta thường có 2-3 hoặc đôi khi nhiều bộ chọn hơn trong ngOnInitviệc lấy dữ liệu khác nhau ... và chúng ta gọi markForChecktất cả chúng .. có ổn không?
jerry

0

cd.detectChanges() sẽ chạy phát hiện thay đổi ngay lập tức từ thành phần hiện tại xuống qua con cháu của nó.

cd.markForCheck()sẽ không chạy phát hiện thay đổi, nhưng đánh dấu tổ tiên của nó là cần chạy phát hiện thay đổi. Lần phát hiện thay đổi tiếp theo chạy ở bất cứ đâu, nó cũng sẽ chạy cho những thành phần được đánh dấu.

  • Nếu bạn muốn giảm số lần phát hiện thay đổi được gọi là sử dụng cd.markForCheck(). Thông thường, các thay đổi ảnh hưởng đến nhiều thành phần và phát hiện thay đổi ở đâu đó sẽ được gọi. Về cơ bản bạn đang nói: hãy đảm bảo rằng thành phần này cũng được cập nhật khi điều đó xảy ra. (Chế độ xem được cập nhật ngay lập tức trong mọi dự án tôi đã viết, nhưng không phải trong mọi bài kiểm tra đơn vị).
  • Nếu bạn không thể chắc chắn rằng hiệncd.detectChanges() không chạy phát hiện thay đổi, hãy sử dụng . sẽ lỗi trong trường hợp đó. Điều này có thể có nghĩa là bạn đang cố gắng chỉnh sửa trạng thái của thành phần tổ tiên, hoạt động chống lại các giả định rằng phát hiện thay đổi của Angular được thiết kế xung quanh. cd.markForCheck()detectChanges()
  • Nếu điều quan trọng là chế độ xem cập nhật đồng bộ trước một số hành động khác, hãy sử dụng detectChanges(). markForCheck()có thể không thực sự cập nhật quan điểm của bạn trong thời gian. Kiểm tra đơn vị một cái gì đó ảnh hưởng đến chế độ xem của bạn, ví dụ, có thể yêu cầu bạn gọi thủ công fixture.detectChanges()khi không cần thiết trong chính ứng dụng.
  • Nếu bạn đang thay đổi trạng thái trong một thành phần có nhiều tổ tiên hơn con cháu, bạn có thể tăng hiệu suất bằng cách sử dụng detectChanges()vì bạn không cần thiết phải chạy phát hiện thay đổi trên tổ tiên của thành phần.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.