1.什么是页源源码 Angular Ahead-of-time (AOT) compilation
2.angular8ï¼
3.angular如何集成monaco-editor
4.Angular 应用实现 Lazy Load(懒加载)的项目实战经验分享
5.[Angular 组件库 NG-ZORRO 基础入门] - 源码初窥: core
6.[译] 关于 Angular 的变化检测,你需要知道的页源源码一切
什么是 Angular Ahead-of-time (AOT) compilation
Ahead-of-time (AOT) 编译是 Angular 框架的关键特性,它在构建阶段将 TypeScript 和模板转换为高效的页源源码 JavaScript 和 HTML,显著提升性能。页源源码AOT 编译在构建过程中的页源源码完成避免了运行时模板解析,从而加速启动和提高性能。页源源码quake 3源码
AOT 编译通过在构建阶段解析、页源源码类型检查和优化源代码实现。页源源码此过程在应用程序部署前进行,页源源码确保在浏览器加载时无需进行模板解析和编译,页源源码带来更快的页源源码启动速度和更好的性能。
AOT 编译的页源源码核心步骤包括模板解析、类型检查和代码优化。页源源码它通过静态编译模板,页源源码加快了启动时间,页源源码减小了应用程序体积,提供了更早的错误检测和更高安全性,以及更好的性能。
与 Just-in-time (JIT) 编译相比,AOT 编译优势明显。JIT 编译在运行时动态编译模板,导致启动延迟,增加应用程序体积,易受潜在模板注入攻击,并影响性能,因为它需要在运行时执行模板编译和解析。
在 Angular 项目中采用 AOT 编译相对简单。基本步骤包括配置构建过程以触发 AOT 编译,并在 dist 目录生成编译后的文件。
使用 AOT 编译时需注意几点,包括避免模板注入、确保组件与服务正确连接、注意错误处理和优化代码。示例中,一个简单的 Angular 组件在 AOT 编译中被转换为静态可执行的 JavaScript 代码,显示用户名字和电子邮件地址,这表明模板解析和编译已提前完成。
总结而言,AOT 编译是优化 Angular 项目的关键工具,通过提高性能、减小体积、增强安全性和提供更早错误检测,谷粒商城2020源码特别适用于大型和移动应用程序。理解和实施 AOT 编译,能够显著提升用户体验,无论开发的企业级应用还是轻量级移动应用。
angular8ï¼
angular8项ç®å¤ç¯å¢é ç½®æ¹æ¡
项ç®å¨æå åå¸çæ¶åå¾å¾éè¦é ç½®ä¸åçserverçå°åï¼å½ç¶ï¼æäºåå¦ç¨çæ¯nginx转åï¼å æ¤ä¸éè¦ã
ä½ä¸ä» ä» å±éäºè¯·æ±å°åï¼æå¯è½æäºåéå¯é¥ççï¼æ¯å¦å¾®ä¿¡çappidï¼æµè¯ç¯å¢åæ£å¼ç¯å¢ç¨çå¯è½æ¯ä¸ä¸è´çã
为äºæ¹ä¾¿æå ï¼angularå·¥ç¨åä¸ä¸ºå¼åè åäºä¸äºæ¹è¿ãæå æ¶åï¼å¼å ¥environmentç设计ã
注æä¸ä¸å ç¹ï¼
1environmentsä¸å¯ä»¥é ç½®å¤å¥ä¸åçç¯å¢
2é»è®¤å¼å ¥çé½æ¯environment.tsï¼è¿ä¸ç¹å¿ é¡»ä¸è´çè ¢ã
3ä¿®æ¹angular.json
4æå æå®åæ°----configuration=dev
jsonä¸çprojects-项ç®å-architect-build-configurations-productionãå¢å ä¸åçç¯å¢é ç½®ï¼å¤å¶æ¯è¾å®¹æãngbuild--prod-c=devï¼
ç¶å设置jsonä¸çprojects-项ç®å-architect-serveï¼è¿æå°éªæ ·å°±å¯ä»¥å¨ngserveä¸å¢å æ´å¤çå°ºæºç¯å¢é ç½®ï¼ngserve-c=dev
æ¯ä¸æ¯å¾ç®åå¢ï¼æä»»ä½é®é¢å°±å¨ä¸é¢çè¨å§ï¼
Angular8å¼å ¥ngx-echartsæ¶æ¥éCannotreadproperty'init'ofnull
å¨é¡¹ç®ä¸ä½¿ç¨ngx-echartsæ¶éå°äºé®é¢ï¼å¨æ¬å°ç¯å¢ä¸ææçå¾åè½æ¾ç¤ºåºæ¥æ¸æ¯ï¼å¨æå åææçå¾åæ æ³æ¾ç¤ºå¹¶æ¥é
å¨æ¥çäºæºç ååç°é®é¢åºå¨echartsä¸ï¼echarts为null导ä¸éæ´è´äºè¯¥é®é¢çåºç°ã
èµ·åæ以为åå æ¯åºå¨çå½å¨æé©åä¸ï¼å°è¯·æ±æ°æ®çæ¹æ³åå¨äºngAfterViewInitå ï¼è¯¥é®é¢ä»ç¶æ²¡æå¾å°è§£å³ã
ç»è¿æ¥æ¾ç¸å ³èµæï¼å¨ngx-echartsçgitåºissueä¸æ¾å°äºè¯¥é®é¢ç解å³æ¹æ³ï¼
å¨è¿å²æ¨¡éçechartsåå¢å { init:echats.init}ï¼å³å¯è§£å³echartsæ¾ä¸å°çé®é¢ã
éä¸å®æ¹issueçå°åï¼
Angular8èµ·æ¥æç¨è¿å°±æ¯è¿ä¸ªç¤ºä¾æç¨çæç»ææã
ä¸é¢ä¸ä¸ªå¯¼èªæ¡ï¼ç¶åæ¯2个页é¢ã
å建å®æåï¼è¿è¡ï¼
æå¼/src/app/app.component.htmlï¼å é¤å 容ï¼æ·»å ï¼
æå¼/app/styles.scssï¼æ·»å ï¼
å建2个ç»ä»¶ï¼
æå¼/src/app/app-routing.module.tsæ·»å ï¼
æå¼/src/app/home/home.component.htmlï¼æ¿æ¢å 容为ï¼
home.component.tsä¸æ·»å ï¼
home.component.scssä¸æ·»å ï¼
home.component.htmlä¸æ·»å ï¼
/src/app/app.module.tsä¸æ·»å ï¼
home.component.tsä¸å®ä¹nameå±æ§ï¼
home.component.htmlä¸æ·»å ï¼
[ngIf]ç»å®ä¸ä¸ªè¡¨è¾¾å¼*clickCounter4*ã
å¦æ表达å¼ä¸ºfalseï¼å°ä¼è°ç¨ngIfElseæå®çå为noneç模æ¿ã
å¦æ表达å¼ä¸ºtrueï¼å°ä¼æ¾ç¤ºng-templateåä¸çHTMLå 容ã
æå¼home.component.htmlï¼ä¿®æ¹æåä¸ä¸ªplay-containerclassï¼
clickCounter4ä¹åï¼èæ¯è²å°±ä¼å为é»è²ã
è¿å¯ä»¥æå®å¤ä¸ªCSSå±æ§ï¼
å¦æä½ æ³æ·»å æè 移é¤å®ä¹å¨CSSä¸çclassï¼å¯ä»¥ä½¿ç¨classç»å®ã
ä¿®æ¹é¢¤éªå½åplay-containerï¼
home.component.scssä¸æ·»å ï¼
å¯ä»¥ä½¿ç¨ngClass设置å¤ä¸ªclassï¼
home.component.tsä¸æ·»å ï¼
home.component.scssä¸æ·»å ï¼
Serviceå¯ä»¥å¤ç¨ï¼æ¥ä¸æ¥æ们å建ä¸ä¸ªserviceï¼ç¨æ¥è°ç¨apiè·åæ°æ®ï¼å¹¶æ¾ç¤ºå¨list页é¢ã
gsæ¯generateserviceç缩åãèå¯è ¢
æ们ç»è¿ä¸ªserviceå½å为ponent.tsä¸æ·»å è°ç¨myMethodï¼
ngOnInit()ä¼å¨ç»ä»¶å è½½æ¶è§¦åã
ponent.tsä¸æ·»å ï¼
list.component.htmlä¸æ·»å ï¼
list.component.scssä¸æ·»å ï¼
æç»ææï¼
è®°å½angular8ä¸ä½¿ç¨inputæ¡è¾å ¥æ¯ä¸ä¸ªå符é½ä¼å¤±å»ç¦ç¹é®é¢å端æ¡æ¶angular8.0
uiç»ä»¶NG-ZORRO
åºæ¯ï¼è¡¨åè¾å ¥:å端å¨ææ·»å inputæ¡
æ°æ®ï¼egï¼['','','xxx']æ°ç»æ¯é¡¹ä¸ºstringç±»åï¼?["..1./",".2.1.",".2.1.-..1.
div?*ngFor="letitemofipPoolData['_ipAddress']indexasi;trackBy:trackByFn"?style="height:px;position:relative;"
inputname="{ { '_ipAddress'+i}}"nz-inputtype="text"placeholder="请è¾å ¥IP/åç½IP/åç½èå´"required[(ngModel)]="ipPoolData['_ipAddress'][i]"(ngModelChange)="checkIpRangeVal($event)"
span*ngIf="error['iprange']"class="text-error"IPè¾å ¥ä¸åæ³/spandivclass="btn-handle-item"buttonnz-buttonnzType="danger"[nzSize]="'small'"
(click)="deleteIPCollectionField(i)"*ngIf="ipPoolData['_ipAddress'].length1"
inz-iconnzType="minus"nzTheme="outline"/i
/button
button*ngIf="itemipPoolData['_ipAddress'].length5"
nz-buttonnzType="æºåé¦primary"[nzSize]="'small'"[disabled]="ipPoolData['_ipAddress'].length-1i"(click)="addIPCollectionField()"
inz-iconnzType="plus"nzTheme="outline"/i
/button
/div/div
å1ï¼
é®é¢ï¼?å½æ°ç»æ¯é¡¹ä¸ºstringç±»åæ¶ï¼å¾ªç¯åinputå ngmodelç´æ¥ç¨itemç»å®ï¼ä¼åºç°ngmodleæ æ³èµå¼é®é¢
解å³ï¼æ°ç»ngfor循ç¯åæ¯é¡¹å 容ngmodelç»å®éè ä»ç¨ipPoolData['_ipAdress'][i]ï¼è¥ç´æ¥ç¨itemåæ æ³ç»å®é¹æ°æ°æ®ï¼ngmodelä¸è¬éè¦item.valueç±»åï¼
å2ï¼
é®é¢ï¼inputæ¯è¾å ¥ä¸ä¸ªå符ï¼é¼ æ å°±ä¼å¤±ç¦é®é¢ï¼
åå ï¼ngmodelç¨ipPoolData['_ipAdress'][i]ç»å®åï¼inputæ¯æ¬¡è¾å ¥åï¼angularä¼éæ°æ¥è¯¢æå¡å¨å¯è½ä¼éç½®å å«æææ°æ¡ç®å¯¹è±¡çå表ï¼å³ä½¿å åå·²æ¾ç¤ºè¿äºæ¡ç®ä¹æ¯å¦æ¤ï¼å¨è¿ç§æ åµä¸ï¼Angularåªè½çå°ç±æ°ç对象å¼ç¨ç»æçæ°å表ï¼å®å«æ éæ©ï¼åªè½ç¨æææ°çDOMå ç´ æ¿æ¢æ§çDOMå ç´ ãå æ¤ä¼åºç°inputæ¯è¾å ¥ä¸ä¸ªå符ï¼é¼ æ å°±ä¼å¤±ç¦é®é¢ï¼
解å³æ¹æ¡ï¼
div*ngFor="letitemofipPoolData['_ipAddress']indexasi;trackBy:trackByFn"/div
ngFor循ç¯å使ç¨trackByï¼trackByFnï¼å该ç»ä»¶æ·»å ä¸ä¸ªæ¹æ³ï¼è¯¥æ¹æ³è¿åNgForåºè¯¥è·è¸ªçå¼ãè¿ä¸ªä¾åä¸ï¼è¯¥å¼æ¯ipPoolData['_ipAdress']çi项ï¼å¦æipPoolData['_ipAdress']çindex项已ç»è¢«æ¸²æï¼Angularå°±ä¼è·è¸ªå®ï¼èä¸ä¼éæ°åæå¡å¨æ¥è¯¢ç¸åçipPoolData['_ipAdress']çindex项ã
trackByFn(index:any,item:any){
returnindex;?}
angular8å¦ä¹ æ»ç»checkedRowIndex=-1;
checkedRowData:any;
pageInfo:PageInfoCompanyModalModel=newPageInfoCompanyModalModel();
orgName:string;
constructor(
privatemodal:ModalHelper,
privatecompanyConfig2Service:CompanyConfig2Service,
){ }
ngOnInit(){
this.getCompany();
}
select(data,i){
this.checkedRowChange(true,data,i);
}
getCompany(){
this.pageInfo.loading=true;
this.companyConfig2Service.getCompany({ pageInfo:{ pageNum:this.pageInfo.pageNum,pageSize:this.pageInfo.pageSize},orgName:this.orgName}).subscribe(data={
if(data){
this.pageInfo=data.data;
}
this.pageInfo.loading=false;
});
}
/
**/
**checkedRowChange(event,data,index){
this.checkedRowIndex=event?index:-1;
this.checkedRowData=data;
}
Angular8å®æï¼åä¸ï¼è½®æå¾ç»ä»¶æ¬ç« 主è¦å 容æ¯å®æè¿ä¸ªè½®æå¾ç»ä»¶~
ä½æ¯ä»ç¤ºä¾ä¸å¯ä»¥çåºï¼è½®æå°æåä¸å¼ å¾ä¹åå°±åæ¢äºï¼ä¸åæ»å¨äºãå¦ä½å¤çè¿ä¸ªé®é¢å¢ï¼ç®åçæ¹æ³æ¬ç©ºå°±æ¯ï¼åä½
åæ¥çä¸ä¸ææ
ä½æ¯æ¤æ¶å¦ææå¨åæ¢ï¼å¾çå¯è½ä¼äº®å¯çåå¨ä¸é´æé ãå¦ä½å¤çå¢?
æ·»å å¸éææçä¸ä¸
æ¤æ¶å¢ï¼è¿æä¸ä¸ªé®é¢ï¼å设ç®åå¨ç¬¬äºå¼ å¾çä¸ï¼å¦ææå¨å¾åæ»ä¸ä¸ï¼åºè¯¥è·³å第ä¸å¼ å¾çï¼ä½ç®åä¹ä¼ç´æ¥è·³å°ç¬¬ä¸å¼ å¾çãæ以æ们éè¦æ§å¶scrolläºä»¶ï¼åæ¶éè¦èèæ°ç»è¶ççå¤çã
6.indicator
indicatoråºè¯¥éçå¾ççè½®æä¹ä¼è½¬æ¢ï¼å¹¶ä¸æ们å¸æå¨å½åç´¢å¼æ¶ï¼indicatoræ¯çº¢è²çã
7.æåå¤çä¸ä¸å åæ³æ¼é®é¢
å½ä½¿ç¨setTimeoutï¼setIntervalçè¿äºæ¹æ³åï¼éè¦æ³¨æå åæ³æ¼çé®é¢ã
è³æ¤ï¼è½®æå¾å°±å ¨é¨å®æäºã
angular如何集成monaco-editor
在进行前端开发时,如果需要集成高级代码编辑器,monaco-editor是一个理想的选择。本文将分享如何在Angular环境中成功集成monaco-editor,并解释其背后的原理和不同集成方式。基础配置
通常,第三方库通过npm安装并导入即可使用。然而,monaco-editor因其动态加载模块支持多种语言的特性,需要特殊处理。monaco-editor提供AMD和ESM两种包格式,它们的区别如下:AMD:异步加载模块标准,适合浏览器和异步加载场景,用define和require实现,可能需要额外的RequireJS库支持。
ESM:ECMAScript模块标准,静态加载,import和export操作,现代浏览器和Node.js原生支持,无需额外库。
Angular集成
根据monaco-editor的包类型,Angular的集成方式分为两种:AMD方式:在angular.json中配置资源路径,通过service管理模块加载。可以使用@monaco-editor/loader简化这一过程,它能从CDN获取或配置为本地资源。
ESM方式:使用webpack进行配置,推荐使用@angular-builders/custom-webpack或ngx-build-plus,官方提供了monaco-editor-webpack-plugin,但个人建议避免直接操作webpack配置。
另一种选择是使用封装好的组件库,如nz-zorro-antd的实验性code editor组件,它内含monaco-editor的实现,无需自己编写代码,可以直接查看其源码。总结
本文分享了将monaco-editor融入Angular项目的详细步骤,无论是AMD还是ESM方式,都涉及了必要的鲁班大叔源码阅读配置和最佳实践。希望这些信息对您的项目集成有所帮助。Angular 应用实现 Lazy Load(懒加载)的项目实战经验分享
Angular 应用的 Lazy Load 是一种优化策略,它能动态加载特定部分的代码,以提升用户体验。在企业级Angular应用中,将代码按照业务逻辑拆分为多个Module,并使用Lazy Load机制按需加载,能显著减少应用程序的初始加载时间,降低整体大小。Angular的代码拆分技术与Lazy Load相辅相成,使得应用程序可以更快启动,避免长时间等待。
在开发Spartacus电商Storefront项目时,Angular团队在语义化版本迭代过程中,对应用代码进行了拆分,并引入了Lazy Load支持。这一策略直观地展示了Lazy Load的效果。打开Spartacus首页,加载的资源文件包括Spartacus核心功能的实现,而点击购物车图标进入购物车页面时,以Lazy Load方式加载了一系列以feature-libs前缀开头的资源文件。这表明,用户在访问首页时仅浏览商品陈列,与购物车显示逻辑无关,因此将购物车UI和服务拆分为独立模块,并应用Lazy Load,是合理的设计思路。
要判断Angular module是否已启用Lazy Load,最直接的方法是在开发模式下执行yarn start命令,观察module构建情况。Initial Chunk Files列表显示使用Eager Load加载策略的Angular module,而Lazy Chunk Files列列出启用了Lazy Load的module清单。在点击Cart图标后的Network面板中出现的JavaScript文件,将出现在Lazy Chunk Files列下。
要启用Lazy Load,首先选择在Network面板中被Lazy Load加载的JavaScript文件,如feature-libs_cart_quick-order_public_api_ts.js,然后找到对应的module实现源代码,其中的关键逻辑在于导入QuickOrderModule的代码行。通过在@NgModule注解修饰的代码块中导入所有需要的customizations,可以实现对已有Lazy Load模块的毕设源码要求定制化开发。
对于Spartacus项目中的标准Module,如果已启用Lazy Load,客户希望对其进行定制,可以通过创建自定义Feature Module,在其中静态导入所需Spartacus标准Module,并在@NgModule注解的providers区域导入所有需要的customizations。在Storefront应用的app.module.ts文件中,使用动态导入功能将自定义Module进行Lazy Load。
综上所述,Angular应用的Lazy Load策略能优化用户体验,通过代码拆分和动态加载机制,实现应用程序的高效启动。在实际开发过程中,理解和实现Lazy Load不仅能够提升性能,还能提供更大的灵活性,允许对已有模块进行定制化开发,以满足特定业务需求。
[Angular 组件库 NG-ZORRO 基础入门] - 源码初窥: core
在探索和了解了典型组件的源码之后,我们进一步深入 NG-ZORRO 组件库的核心结构,发现了一个关键的策略来解决组件间共用属性、功能导致的重复编写问题。NG-ZORRO 支持近 种组件,为避免每种组件都需要重复定义相同的属性或功能,开发团队采用了将公共方法和定义抽离至 `core` 文件夹的策略。
当处理组件的通用属性时,我们发现像 `nzSize` 这样的属性在多个组件如 `Input` 和 `Button` 中被广泛使用。解决这一问题的方法在于引入 `types` 文件夹,这个文件夹记录了哪些组件支持特定属性,便于我们查询和重复利用。
动画效果是 Angular 开发中常见的元素,Angular 官方文档提供了详尽的指南。NG-ZORRO 提供了多样化的动画,使页面元素呈现丰富的动态变化。例如在 `Collapse` 折叠面板组件中,通过 `nzActive` 属性操控动画状态,实现元素的展开与收起效果。这一功能在实际开发中非常实用,使用动画使页面交互更加直观。
某些组件,如 `Tag`,在其动态删除操作中应用了淡入淡出动画,app源码二开该动画机制相较于需要单独配置的状态传递更为简便,直接提升视觉效果和用户体验。NG-ZORRO 内含多种动画类型,如 `moveUpMotion` 和 `slideMotion`,通过探索源码可以轻易找到使用方式。
对于不希望使用动画的场景,NG-ZORRO 提供了 `NzNoAnimationDirective`,允许开发者在模板层面对特定元素禁用动画效果。通过替换 `BrowserAnimationsModule` 为 `NoopAnimationsModule`,可实现全局禁用动画。
总结这一系列核心文件夹——`core` 包含了如 `types` 和 `animations` 等内容,对于项目开发而言,应考虑抽离公共部分,实现跨组件复用,以减少代码冗余和提高开发效率。通过借鉴 NG-ZORRO 的实践,开发者可以优化代码结构,提升组件复用性,同时保持代码的简洁性和易维护性。
[译] 关于 Angular 的变化检测,你需要知道的一切
关于 Angular 的变化检测,你需要知道的一切
探究内部实现和具体用例
要全面了解 Angular 的变化检测机制,查看源码是不可或缺的步骤,因为网络上相关文章少之又少。大部分文章只涉及每个组件自身的变化检测器、不可变变量(immutable)和变化检测策略(change detection strategy)的概念,但缺乏深入剖析。本文旨在深入探讨不可变变量如何触发变化检测,以及变化监测策略对检测的影响。同时,你将能将这些知识应用到优化性能的场景中。
本文分为两部分,第一部分侧重于技术细节,包含大量源码链接。我们基于 Angular 4.0.1 版本来解释变化检测机制的工作原理。该版本与 Angular 2.4.1 版本有所差异。若想了解更多,可参考 Stack Overflow 上的相关讨论。
第二部分将展示如何运用变化检测。值得注意的是,尽管两个版本的 API 有所不同,但第二部分的内容对两个版本都适用。
核心概念:视图(view)
Angular 的教程常提到应用是一颗组件树,但在 Angular 内部,视图(view)是一种较低级的抽象概念。视图与组件之间存在直接关联,每个视图都通过 component 属性与对应的组件类关联。所有操作在视图中执行,包括属性检查和 DOM 更新。因此,从技术角度来看,更准确的说法是 Angular 应用是一颗视图树。组件可以视为视图的更高阶概念。源码中对视图有如下描述:
视图是应用 UI 的基本组成元素,由一组在创建和销毁时共同存在的最小集合构成。视图的属性可以改变,但元素结构(数量和顺序)不能改变。想要改变元素结构,只能通过 ViewContainerRef 插入、移动或移除嵌入的视图。每个视图可以包含多个视图容器。
在本文中,我们将交替使用“组件视图”和“组件”概念。
值得注意的是,网络上有关变化检测的文章和 StackOverflow 回答中的“视图”常被称作变化检测器对象(Change Detector Object)或 ChangeDetectorRef。实际上,变化检测并非独立对象,而是在视图上运行的。
每个视图通过 nodes 属性关联子视图,以便对子视图进行操作。
视图的状态
每个视图都有一个 state 属性,这是极其重要的属性,决定了是否对视图及其所有子视图执行变化检测。state 属性有多种可能的值,与本文相关的有:
如果ChecksEnabled 为 false 或视图状态为 Errored 或者 Destroyed,变化检测将跳过此视图及其所有子视图。默认情况下,所有视图均以 ChecksEnabled 作为初始值,除非使用了 ChangeDetectionStrategy.OnPush。
Angular 中有许多高级概念用于操作视图。在文章中已提及部分概念,如 ViewRef。它封装了组件视图并提供了一个名为 detectChanges 的方法,该方法会在异步事件触发时在最顶层的 ViewRef 上执行变化检测。最顶层的 ViewRef 执行变化检测后,会递归地对其子视图执行检测。
通过 ChangeDetectorRef 令牌将 viewRef 注入组件构造函数中,可以实现此操作:
从其定义可知这一点:
变化检测操作
执行变化检测的主要逻辑在 checkAndUpdateView 方法中,该方法主要针对子组件视图执行操作,并递归地调用此方法以遍历从宿主组件到所有组件的所有视图。这意味着,下一次递归中,子组件就成为了新的父组件。
执行变化检测的主要步骤如下:
有几个关键点需要注意:
首先,子组件在子视图被检测之前会触发onChanges 生命周期钩子,即使子视图的变化检测被跳过了。这一点至关重要,后续部分将展示如何利用这一点。
第二,当检测视图时,更新视图的 DOM 是变化检测机制的一部分。因此,如果组件未被检测,DOM 将不会更新,模板中的组件属性发生改变时也是如此。在第一次检测之前,模板已被渲染。这里所说的更新 DOM 指的是更新插值。例如,some { { name}},在第一次检测之前,已经将 DOM 元素 span 渲染好。检测过程中,只会渲染 { { name}} 部分。
另一个有趣的是,子组件视图的状态可以在变化检测过程中改变。所有组件视图默认初始化为 ChecksEnabled。但是,使用 OnPush 策略的组件在第一次检测后不再执行变化检测(步骤第 9 步)。OnPush 文档说明,只有绑定发生变化时才会执行检测。因此,需要将 ChecksEnabled 位设置为启用检测(步骤第 2 步操作):
只有当父视图绑定发生变化,且子组件视图初始化为 ChangeDetectionStrategy.OnPush 时,才会更新状态。
最后,当前视图的变化检测负责启动子视图的变化检测(步骤第 8 步)。以下是相关的代码:
现在你知道了视图状态控制了是否对此视图及其子视图进行变化检测。那么问题来了——我们能控制这个状态吗?答案是可以,这也是本文第二部分将探讨的内容。
有些生命周期钩子在更新 DOM 前调用(步骤 3、4、5),有些在之后(步骤 9)。例如,组件结构为 A -> B -> C,它们的生命周期钩子调用和更新绑定的顺序是:
总结
假设我们有如图所示的组件树,根据前面所述,每个组件都有一个与之关联的视图。每个视图初始化为 ViewState.ChecksEnabled,这意味着 Angular 进行变化检测时,树中的每个组件都会被检测。
如果我们希望禁用 AComponent 及其子组件的变化检测,只需将 ViewState.ChecksEnabled 设置为 false。由于状态操作是低级操作,Angular 提供了许多视图的公共方法。每个组件都可以通过 ChangeDetectorRef 令牌获取关联的视图。Angular 文档定义了该类的公共接口:
接下来,我们将探讨如何使用这些接口。
detach
detach 允许我们操作状态,它可以对当前视图禁用检查:
在代码中实现如下:
这确保了接下来的变化检测中,从 AComponent 开始,左子树的所有组件都会被跳过(橙色的组件不会被检测):
需要注意的是,改变的是 AComponent 的状态,其所有子组件都不会被检测。第二点是,由于整个左子树的组件都不执行变化检测,它们模板中的 DOM 也不会更新。以下例子简要描述了这种情况:
当组件首次被检测时,span 将被渲染为“See if I change: false”。两秒后,changed 属性变为 true,但 span 中的文字不会更新。然而,如果去掉了 this.cd.detach(),就会按照预期更新。
reattach
如第一部分所述,如果 AComponent 的输入绑定 aProp 发生变化,AComponent 的 Onchanges 生命周期钩子就会被触发。这意味着一旦得知输入属性发生变化,即可启动当前组件的变化检测器来检测变化,然后在下一个周期将其分离。以下是实现此功能的代码片段:
由于reattach 只是简单地设置 ViewState.ChecksEnabled 位:
这与将 ChangeDetectionStrategy 设置为 OnPush 的效果相似:在第一次变化检测后禁用检测,当父组件绑定的属性发生变化时启用,检测完之后再次禁用。
需要注意的是,OnChanges 钩子仅在禁用检查的子树的最顶端组件触发,子树中的其他组件不会触发。然而,我们可以通过这个钩子执行自定义逻辑,然后将组件标记为可以执行一次变化检测。由于 Angular 只检测对象引用,我们在此可以检查对象的属性:
markForCheck
reattach 方法仅对当前组件启用检测,如果父组件未启用变化检测,效果有限。我们需要一个能够检测所有父组件直到根组件的方法,这个方法就是 markForCheck:
从代码中可以看出,它仅向上迭代直至根节点,使所有父组件都启用检查。
何时使用这个方法?与 ngOnChanges 一样,在使用 OnPush 策略时也会触发 ngDoCheck 生命周期钩子。再次强调,只有禁用检查的子树的最顶端组件会触发,子树中的其他组件都不会触发。但是,我们可以通过这个钩子执行一些自定义逻辑,然后将组件标记为可以执行一次变化检测。由于 Angular 只检测对象引用,我们在此可以检查对象的属性:
detectChanges
有一种方法可以对当前组件和所有子组件执行一次变化检测,这就是 detectChanges 方法。此方法会对当前组件视图执行变化检测,不管组件的状态如何。也就是说,视图仍会禁用检测,并且在接下来常规的变化检测中,不会检测此组件。例如:
尽管变化检测器引用仍保持分离,但 DOM 元素仍会随着输入绑定的变化而变化。
checkNoChanges
这是变化检测器的最后一个方法,主要作用是确保当前执行的变化检测中,没有变化发生。简单来说,它执行本文第一部分提到的列表中的第 1、7、8 步。如果发现绑定发生变化或 DOM 需要更新,会抛出异常。
还有疑问?
若对本文有任何疑问,欢迎在 Stack Overflow 上提问,并在本文评论区贴上链接。这样整个社区都能受益。感谢。
关注我以获取更多资讯
若发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。本文永久链接即为本文在 GitHub 上的 MarkDown 链接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、 iOS、 前端、 后端、 区块链、 产品、 设计、 人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、 官方微博、 知乎专栏。
2024-11-17 20:09
2024-11-17 19:04
2024-11-17 18:49
2024-11-17 18:42
2024-11-17 18:15
2024-11-17 18:07
2024-11-17 17:56
2024-11-17 17:53