angular撸官网(三₁) | 组件-生命周期
前言
阅读本章只办四件事: 组件、组件、还是他娘的组件!
组件
组件包含:
- HTML模板
- ts定义行为
- css选择器
- (可选)模板上css样式
创建组件
使用ng generate component xxx
来创建,会自动生成四个文件:
- xxx.component.html
- xxx.component.ts
- xxx.component.css
- xxx.component.spec.ts(测试文件)
xxx为组件名。
Cli默认行为如此。 改变则阅读ng generate component。 通过--参数等改变配置。
手动创建: 自己手动来创建配置一个组件。(其实也就是拆分分析cli创建的步骤)。
首先(最好是创建组件名同名文件夹)创建.ts文件。后:
/* 引入Component */
import { Component } from '@angular/core';
# 添加一个@component装饰器
@Component({
selector: 'app-cart', # 给组件定义一个选择器 找到此标签来实例化组件
templateUrl: './cart.component.html', # 模板内容 也可以直接在这里写入
styleUrls: ['./cart.component.css'] # 给一个Css文件来定义样式等。
// styles: ['h1 {color: red}'] # 也可以直接使用行内联
})
export class CartComponent {
constructor(
){}
}
生命周期
个人理解的话 content
相关的所有术语是插槽(投影)(通过<ng-content />
占位符实现),而view
相关术语就是嵌套子组件。
钩子方法 | 用途 | 时机 |
---|---|---|
ngOnChanges() | 设置或重新设置数据绑定的输入属性的时候响应。(接受当前和上一属性值的SimpleChanges对象) | 如果组件有输入属性。那么在ngOnInit() 及输入属性值变化时候都会调用(没有输入属性、没有提供任何输入属性,都不会调用) |
ngOnInit() | 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件 | 在第一轮ngOnChanges() 或者没有ngOnChanges() 之后,都会调用,且调用一次 |
ngDoCheck() | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应 | 紧跟在每次ngOnChanges() 及首次ngOnInit() 后 执行 |
ngAfterContentInit() | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 ngDoCheck() 之后调用,只调用一次 |
ngAfterContentChecked() | 每当 Angular 检查完被投影到组件或指令中的内容之后调用。 | ngAfterContentInit() 和每次 ngDoCheck() 之后调用。 |
ngAfterViewInit() | 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用。 | 首次ngAfterContentChecked() 之后调用,只调用一次。 |
ngAfterViewChecked() | 每当做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。 |
ngOnDestroy() | 每次销毁指令/组件之前调用并清扫。在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏 | 销毁指令或组件之前立即调用。 |
初始化(onInit())
使用ngOnInit()
需要完成以下任务:
- 在构造函数外执行复杂的初始化。 组件构造器应该是便宜且安全,不该在该组件使用前,突然考虑到构造函数尝试去链接服务器。
- 输入属性设置好后设置组件。 构造函数只会把变量设置为简单的初始值。 在初始化完成后才会设置指令绑定的输入属性,如果要初始化,那么就在
ngOnInit()
内完成。
销毁处理
在ngOnDestory()
内 进行一些清除逻辑来避免内存泄漏。如:
- 取消订阅可观察对象和dom事件
- 清除interval
- 反注册 该指令在全局或应用服务的回调函数
demo示例
所有生命周期的顺序和频率
export class HeroDetailComponent implements OnInit,
OnChanges, DoCheck,
AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
@Input() hero!: Ihero
constructor() { }
ngOnChanges(): void {
console.log("ngOnChanges ~ ngOnChanges:")
}
ngOnInit(): void {
console.log("ngOnInit ~ ngOnInit:")
}
ngDoCheck(): void {
console.log("ngDoCheck ~ ngDoCheck:")
}
ngAfterContentInit(): void {
console.log("ngAfterContentInit ~ ngAfterContentInit:")
}
ngAfterContentChecked(): void {
console.log("ngAfterContentChecked ~ ngAfterContentChecked:")
}
ngAfterViewInit(): void {
console.log("ngAfterViewInit ~ ngAfterViewInit:")
}
ngAfterViewChecked(): void {
console.log("ngAfterViewChecked ~ ngAfterViewChecked:")
}
ngOnDestroy(): void {
console.log("ngOnDestroy ~ ngOnDestroy:")
}
}
打印日志顺序为:
ngOnChanges ~ ngOnChanges:
ngOnInit ~ ngOnInit:
ngDoCheck ~ ngDoCheck:
ngAfterContentInit ~ ngAfterContentInit:
ngAfterContentChecked ~ ngAfterContentChecked:
ngAfterViewInit ~ ngAfterViewInit:
ngAfterViewChecked ~ ngAfterViewChecked:
ngDoCheck ~ ngDoCheck:
ngAfterContentChecked ~ ngAfterContentChecked:
ngAfterViewChecked ~ ngAfterViewChecked:
每一次的修改都会触发一组onChanges()
,以及两组DoCheck()
、AfterContentChecked()
、AfterViewChecked()
。
指令监测生命周期
我们可以生成一个指令,通过指令实现OnInit()
和OnDestroy()
,后写入到任何组件、模板元素上,就完成了监测。
首先生成一个指令:
ng generate directive spy
ng g d spy
spy.directive.ts
实现两个生命周期:
import { Directive, OnInit, OnDestroy } from '@angular/core';
import { LoggerService } from './logger.service';
let nextId = 1
@Directive({
selector: '[appSpy]'
})
export class SpyDirective implements OnInit, OnDestroy {
private id = nextId++
constructor(
//就是一个普通服务类,写入一个console.log(message)即可
// 这里不赘述
private logger: LoggerService
) { }
ngOnInit(): void {
this.logger.log(`Spy #${this.id} onInit`)
}
ngOnDestroy(): void {
this.logger.log(`Spy #${this.id} OnDestroy`)
}
}
组件/元素使用指令:
<app-hero-detail appSpy *ngIf="selectedHero" [hero]="selectedHero">
</app-hero-detail>
使用变更检测钩子
ngOnChanges()
检测到输入属性变化时, 则会触发。
得到一个参数为,映射到了simpleChange
对象,包含了属性的oldVal
和newVal
两个,以供使用。
ngOnChanges(changes: SimpleChanges) {
for (const propName in changes) {
const chng = changes[propName];
const cur = JSON.stringify(chng.currentValue);
const prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
响应视图、响应被投影
虽然没太能完全理解这三个官网文档,但是可以理解的是: content Children(AfterContentInit()
、AfterContentChecked()
)和view Children(AfterViewInit()
、AfterViewChecked()
)一个 是子组件,一个是插槽。
插槽为 父组件写入子组件<>标签内的内容</>来进行投影,使用<ng-content/>
来占位符渲染。
例如content Children (插槽):
# parent.component.html
<child-component>
<content-children></content-children>
</child-component>
# child-component.component.html
<div>
<ng-content></ng-content>
</div>
这种投影(插槽)就是触发的AfterContent**()
相关的两个生命周期。这种必须通过一个带装饰器的属性才可以访问到子组件的视图,那就是@ContentChild()
假设子组件内有name属性,那么父组件简单操作如下:
export class HeroListComponent implements AfterContentChecked, AfterContentInit {
private prevHero = ''
@ContentChild(HeroDetailComponent) contentChilld!: HeroDetailComList()
ngAfterContentChecked(): void {
if (this.prevHero === this.contentChild.hero.name) {
console.log('AfterViewChecked (no change)');
} else {
this.prevHero = this.contentChild.hero.name;
console.log('AfterViewChecked');
// xxx do something
}
}
ngAfterContentInit(): void {
}
}
而view children 子组件则是以下:
```html
# xxx.component.html
<div>
<view-child></view-child>
</div>
这种嵌套在自己模板内容内的 称之为 view Children
。触发的是 AfterView*()
两个生命周期相关的函数。
而要想获取到投影组件的视图(属性等)则需要一个带着装饰器的属性: @ViewChild()
,从而基于子级内容 来采取相应行动。代码如下:
export class HeroListComponent implements AfterViewChecked, AfterViewInit {
private prevHero = ''
@ViewChild(HeroDetailComponent) viewChild!: HeroDetailComponent
ngAfterViewChecked(): void {
if (this.prevHero === this.viewChild.hero.name) {
console.log('AfterViewChecked (no change)');
} else {
this.prevHero = this.viewChild.hero.name;
console.log('AfterViewChecked');
// xxx do something
}
}
ngAfterViewInit(): void {
}
}
angular在调用你AfterView之前,就已经调用完了所有的AfterContent钩子,在完成组件试图的合成之前,就已经完成了所有的投影。
AfterContent....
和AfterView...
之间有一个小的时间窗,可以修改宿主的视图。
自定义(!!!!!)
转载自:https://juejin.cn/post/7248890696819032125