이 글에서는 Angular의 변경 감지에 대해 자세히 알아보고 Angular의 DOM 업데이트 메커니즘, 변경 감지로 해결할 수 있는 문제 등을 소개합니다.
이 기사를 통해 다음 지식을 얻는 데 도움이 될 수 있습니다.
다음 중 하나를 먼저 살펴보겠습니다. 가장 인기있는 Simply Demo
버튼을 클릭하면 컴포넌트의 name 속성이 변경되고, 변경된 값이 DOM에도 순간적으로 표시되는 게 좀 "마법"처럼 느껴집니다.
실제 DOM의 innterText가 요소 변경 문 직후에 인쇄되었지만 여전히 이전 값이지만 뷰의 값이 분명히 변경된 것으로 확인되면 이 두 부분에서 정확히 무슨 일이 일어났는가? 코드? 이것도 궁금하시다면 저와 함께 답을 공개해 보세요.
방금 일어난 일을 주의 깊게 기억해 봅시다.
버튼을 클릭하세요
값이 변경됩니다
네이티브 JS를 사용하여 이 코드를 작성하면 버튼을 클릭한 후의 뷰는 절대 변경되지 않으며, Angular에서는 뷰가 변경됩니다. Angular에 대해 조금 더 깊이 이해했다면 zone.js라는 라이브러리에 대해 알게 될 것입니다. 주의 깊게 살펴보면 zone.js가 다음과 같이 값을 변경할 수 있는 모든 이벤트에 대해 처리 계층을 수행한다는 것을 알 수 있습니다. :
Angular는 zone.js를 비활성화하는 방법도 제공합니다. .
영역을 비활성화한 후 버튼을 다시 클릭하면 보기가 업데이트되지 않습니다.
호기심으로 Angular 소스 코드에서 뷰 업데이트의 핵심 코드를 발견했습니다
이번에는 코드에서 이 메서드를 수동으로 호출했습니다.
예상과 똑같습니다! 뷰가 업데이트되고, 더욱 놀라운 점은 인쇄된 innerText도 업데이트된다는 것입니다!
이 시점에서 우리는 DOM 업데이트가 Tick()의 트리거링에 의존하고 zone.js가 개발자가 수동 트리거링의 필요성을 줄이는 데 도움이 된다는 결론에 도달했습니다.
자, 약간의 테스트를 거친 후 Angular 뷰 업데이트 뒤에 어떤 일이 일어나는지 자세히 살펴보겠습니다.
먼저 상위 구성 요소의 이름 값이 ngOnInit에서 변경되는 것을 살펴보겠습니다. 그 결과, 누구나 한 번쯤 접했을 오류 메시지가 나타났습니다
그러나 이렇게 작성한다고 해서 항상 오류가 발생하는 것은 아닙니다. 예를 들어, 하위 구성 요소의 입력 속성을 제거하면, 새로 고치고 동일한 코드가 실행될 수 있음을 확인하면 상위 구성 요소의 이름이 정상적으로 변경될 수 있습니다.
emm... 생각에 잠겨...
아마도 당신은 제가 처음 Angular를 배우기 시작했을 때의 저처럼 stackoverflow에서 이 문제를 검색하고, 왜 작동하는지 모르는 코드를 복사하고, 그냥 붙여넣었다가 또 이런 문제가 발생하면 계속해서 스택오버플로에서 검색하고 복사해서 붙여넣는 등...
시간이 지날수록 다양한 CRUD에 능숙한 여러분은 이런 스택오버플로우 중심의 프로그래밍에 점점 불만을 가지게 됩니다. 질문에 대한 답을 커뮤니티, 문서, 포럼에서 계속 찾아보게 되지만, 그 답변을 읽고 나면 기사에 따르면 ChangeDetection이라는 것이 있다는 것만 알고 있는 것 같은데, 이 버그의 원인이 무엇인지 정확히 설명할 수는 없습니다. 저와 같은 경험이 있으신 분들은... 계속해서 읽어보시고 진실을 알아보세요. !
모델의 데이터를 변경할 때 프레임워크 계층은 다음을 알아야 합니다.
모두가 Virtual Dom에 익숙해야 합니다. React. React는 DOM을 비교합니다. 모든 DOM을 업데이트하는 대신 새 상태와 이전 상태를 사용하여 DOM의 어느 부분을 업데이트할지 결정합니다. 이는 Angular의 변경 감지와도 같습니다.
전체 Angular 애플리케이션은 구성 요소의 변경으로 인해 모든 구성 요소가 업데이트되는 것은 불가능합니다. 예를 들어 사용자가 버튼 상태를 변경하는 경우 가장 이상적인 접근 방식은 전체 애플리케이션을 업데이트하는 대신 이 버튼의 스타일이나 텍스트만 업데이트하는 것입니다. 이것이 변경 감지의 목적입니다.
기본적으로(ChangeDetectionStrategy.Default
), 상위 구성 요소의 변경 감지가 발생하면 하위 구성 요소도 변경 감지를 트리거합니다. ChangeDetectionStrategy.Default
),父组件的变更检测发生时,子组件也会触发变更检测。
(CD
即为 changeDetection
)
每次变更检测时,都会比较新旧状态,如果两次变更检测(开发环境下)的结果不一致就会报错,例如:
Expression has changed after it was checked
这也就解释了为什么在子组件中更改了父组件的值会报错。
但是!在前面的两个例子中我们都在子组件中更改了父组件的值,只有第一个报错,第二个是可以正常更新的,如果你也同样很疑惑这中间真正的差异点在哪里,那么接着往下阅读吧~
先上结论:
更新所有子组件的绑定属性
调用所有子组件的 OnChanges,OnInit,DoCheck,AfterContentInit 生命周期钩子
更新当前组件的 DOM
子组件触发变更检测
调用所有子组件的的 AfterViewInit 的生命周期钩子
这里我们不关注于太细的细节(不用好奇为什么是这样的顺序,只要记住 Angular 里就是这样设定的就可以了,如果有大佬想谈谈 Angular 在这部分的设计思想,欢迎在评论区留言探讨~)
第一个例子中,父组件 parent 给子组件 child 传入了输入属性 name,且子组件在 ngOnInit 中更新了父组件的 name 属性,也就是说这段代码**违背了检测顺序(**在顺序的第二步中操作了第一步)!
<p>{{ name }}<p> <child [name]="name"></child>
而在第二个例子中,就算子组件在 ngOnInit 中也更新了父组件的 name 属性,但是由于父组件parent 中没有给子组件 child 绑定输入属性 name,不会出现与违背变更检测队列顺序的情况,所以就可以正常运行。
<p>{{ name }}<p> <child></child>
这个时候再去看看 stackoverflow 上的高赞回答 是不是就清晰明了很多,按照上述的检测顺序,我们会发现只要父组件中对子组件做了属性绑定,不管是在 OnChanges,OnInit,DoCheck,AfterContentInit 和 AfterViewInit 中的任意一个声明周期钩子中执行下述代码都会报错。
this.parentCmpt.name = 'child'
好了,到这里我们已经明白了这种错误发生的真正原因,但是我还是要提醒一下,这种错误只会在开发环境下触发,生产环境下会调用 enableProdMode()
,变更检测次数会从 2 降到 1,这部分在 Angular 源码当中也有描述。
当然你不能因为这个 bug 就强制在开发环境下使用生产模式...
ChangeDetectionStrategy
默认为 Default,也就是父组件的 CD 会触发子组件的 CD,但是很显然有些情况下我们可以自行判断出某些子组件在父组件 CD 时并不用触发,而 OnPush
CD
는 changeDetection
입니다. )🎜🎜변경 사항이 감지될 때마다 두 가지 변경 감지 결과(개발 환경에서)가 이전 상태와 새 상태를 비교됩니다. 일관성이 없으면 오류가 보고됩니다. 예를 들어 :🎜🎜Expression has beenchanged after it was checked
🎜🎜이것은 또한 하위 구성 요소에서 상위 구성 요소의 값이 변경될 때 오류가 보고되는 이유를 설명합니다. 요소. 🎜🎜하지만! 이전 두 예제에서는 자식 컴포넌트에서 부모 컴포넌트의 값을 변경했는데, 첫 번째 컴포넌트만 오류를 보고했고, 두 번째 컴포넌트의 실제 차이점도 헷갈리신다면 읽어보세요. on~🎜export declare abstract class ChangeDetectorRef { abstract checkNoChanges(): void; abstract detach(): void; abstract detectChanges(): void; abstract markForCheck(): void; abstract reattach(): void; }
enableProdMode()<가 호출된다는 점을 상기시켜 드리고 싶습니다. 환경 /code>, 변경 감지 횟수가 2에서 1로 감소합니다. 이 부분은 <a href="https://github.com/angular/angular/blob/6b79ab5abec8b5a4b43d563ce65f032990b3e3bc/packages/core/src/application_ref에 있습니다. ts#L553" target="_blank" ref="nofollow noopener noreferrer">Angular 소스 코드</a>도 설명되어 있습니다. 🎜🎜<img src="https://img.php.cn/upload/image/766/561/889/162864959954984Angular의 변경 감지에 대해 자세히 알아보세요." title="162864959954984Angular의 변경 감지에 대해 자세히 알아보세요." alt="Angular의 변경 감지에 대해 자세히 알아보세요."/>🎜🎜Of 물론 이 버그 때문에 개발 환경에서 프로덕션 모드를 강제로 사용할 수는 없습니다...🎜<h3 data-id="heading-5">🎜4. 다들 흔히 얘기하는 ChangeDetectionStrategy.OnPush란 무엇인가요? 🎜🎜🎜<code>ChangeDetectionStrategy
의 기본값은 기본값입니다. 즉, 상위 구성 요소의 CD가 하위 구성 요소의 CD를 트리거하지만 어떤 경우에는 일부 하위 구성 요소가 사용되지 않는다고 스스로 판단할 수 있습니다. OnPush
🎜 동안 상위 구성 요소 CD가 트리거된 경우则是 Angular 为开发者提供的一便捷操作方式。
用动图来表示就是:查看链接
知名的 Angular 开源组件库 ng-zorro 就使用了大量的 OnPush 策略,这也是 Angular 性能优化的方法之一。
Angular 给每个组件都关联了一份组件视图,通过 ChangeDetectorRef
可以拿到相关联的视图,在定义中我们可以看到:
export declare abstract class ChangeDetectorRef { abstract checkNoChanges(): void; abstract detach(): void; abstract detectChanges(): void; abstract markForCheck(): void; abstract reattach(): void; }
观察下面的动图,被 detached
的组件将不会被检查变更。
而 reattach
则可以让被 detached
的组件重新可以被检测到变更。
reattach
只会重新启用对当前组件的变更检测,但是如果父组件没有启动变更检测,那么 reattach
并不会起作用,而 markForCheck
可以很好地解决这个问题。
这一点在 ng-zorro 的源码中可以了解一二。
例如在 nz-anchor 组件中更改 nz-anchor-link 组件的 active 属性时,由于本身 ChangeDetectionStrategy
为 OnPush
,那么就需要激活 markForCheck 来重新启用检测。具体写法可以查看 github 中的源代码。
用动图来展示则是这样,注意观察设置了 MFC 的前后变化
这个方法如同字面意思一样很好理解,就是触发一次变更检测啦,还记得本文中的第一个例子吗,我们不手动触发 tick()
,而是触发 detechtChanges()
也是可以达到效果的。
到这里,我相信大家已经基本弄明白了 Angular 变更检测,如果有任何疑问,欢迎在评论区交流讨论~
在撰写这篇文章时,笔者参(fu)考(zhi)了大量的社区文章和讨论,一方面是感慨如此重要的概念在 Angular 中文社区中却只有零星几篇相关介绍的文章,另一方面是看到了虽然国内 Angular 开发者虽然数量远少于 React 和 Vue,却依然非常热情的贡献自己的知识和见解来为 Angular 中文社区添砖加瓦,作为已使用 Angular 半年多的开发者,深深感受到 Google 的工程美学。
大而全且不失优雅,是笔者对 Angular 这款 Web 框架的最大感受,感谢开源社区中的各位开发者们~
对于文中描述错误的地方,还望大佬们批评斧正~
原文地址:https://juejin.cn/post/6844904079878012935
作者:LangWalker
更多编程相关知识,请访问:编程入门!!
위 내용은 Angular의 변경 감지에 대해 자세히 알아보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!