Angular4-在线竞拍应用-组件的生命周期

这里写图片描述

红色的被调用一次,绿色的会被调用多次。

这里分为了三个阶段,组件初始化阶段,变化检测,组件销毁。

会在组件初始化后看到组件,在变化检测阶段让属性值和页面展示保持一致。

变化检测中的四个方法和组件初始化中的四个方法是一样的。

一共只有9个方法。

生命周期的例子

新建一个项目demo3

新建组件ng g component life

export class LifeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

在生产的组件中已经实现了OnInit接口,每一个钩子都是@angular/core中的一个接口,每一个接口都有一个唯一的钩子方法,比如OnInit接口的钩子方法是ngOnInit()。

从纯技术来讲,接口对于JavaScript和TypeScript来说都是可选的,因为JavaScript没有接口,angular在运行是看不到TypeScript接口,因为在编译成JavaScript的时候已经消失了。

接口不是必须的,我们不添加生命周期接口,也可以用相关方法。

但还是强烈建议添加上接口,这样可以获得IDE的支持,以及强类型的检查。。。

现在把所有的钩子接口都实现一下。

import {
  Component,
  OnInit,
  OnChanges,
  DoCheck,
  AfterContentInit,
  AfterContentChecked,
  AfterViewInit,
  AfterViewChecked,
  OnDestroy,
  SimpleChanges,
  Input
} from '@angular/core';

let logIndex:number=1;

@Component({
  selector: 'app-life',
  templateUrl: './life.component.html',
  styleUrls: ['./life.component.css']
})
export class LifeComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {

  @Input()
  name:string;

  logIt(msg:string){
    console.log(`#${logIndex++} ${msg}`);
  }


  constructor() {
    this.logIt("name属性在constructor里的值是:"+name);
  }


  ngOnChanges(changes: SimpleChanges): void {

    let name=changes['name'].currentValue;
    this.logIt("name属性在ngOnChanges里的值是:"+name);

  }

  ngOnInit() {
    this.logIt("ngOnInit");
  }

  ngDoCheck(): void {
    this.logIt("ngDoCheck");
  }

  ngAfterContentInit(): void {
    this.logIt("ngAfterContentInit");
  }

  ngAfterContentChecked(): void {
    this.logIt("ngAfterContentChecked");
  }

  ngAfterViewInit(): void {
    this.logIt("ngAfterViewInit");
  }


  ngAfterViewChecked(): void {
    this.logIt("ngAfterViewChecked");

  }

  ngOnDestroy(): void {
    this.logIt("ngOnDestroy");
  }

}

修改app.component.html

<app-life [name]="title"></app-life>

修改app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tom';
}

启动项目

界面显示life works!

控制台显示信息为

#1 name属性在constructor里的值是:
#2 name属性在ngOnChanges里的值是:Tom
#3 ngOnInit
#4 ngDoCheck
#5 ngAfterContentInit
#6 ngAfterContentChecked
#7 ngAfterViewInit
#8 ngAfterViewChecked
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
#9 ngDoCheck
#10 ngAfterContentChecked
#11 ngAfterViewChecked

首先会调用构造函数,

  • ngOnChanges:当一个父组件修改或初始化一个子组件的输入属性的时候被调用
  • ngOnInit:初始化(如果初始化的逻辑需要依赖输入属性,那就一定要写在ngOnInit中,而不要写在构造函数中
  • ngDoCheck:用来检测
  • ngAfterContentInit:
  • ngAfterContentChecked:
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngDoCheck
  • ngAfterContentChecked
  • ngAfterViewChecked

ngOnchanges

当父组件初始化或修改子组件的输入参数时会被调用。

可变对象,不可变对象

字符串是不可变的

对象的值是可变的

新建组件ng g component child

修改child.component.ts

import {Component, OnInit,Input,OnChanges,SimpleChanges} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit,OnChanges {

  ngOnChanges(changes: SimpleChanges): void {
    console.log(JSON.stringify(changes,null,2));
  }

  @Input()
  greeting: string;

  @Input()
  user:{name:string};

  message:string="初始化消息";

  constructor() {
  }

  ngOnInit() {
  }

}

修改child.component.html

<div class="child">
  <h2>我是子组件</h2>
  <div>问候语:{{greeting}}</div>
  <div>姓名:{{user.name}}</div>
  <div>消息:<input [(ngModel)]="message"></div>
</div>

修改child.component.css

.child{
  background:lightgreen;
}

修改app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  greeting:string="Hello";

  user:{name:string}={name:"Tom"};

  constructor(){

  }

}

修改app.component.html

<div class="parent">
  <h2>我是父组件</h2>
  <div>
    问候语:<input type="text" [(ngModel)]="greeting">
  </div>
  <div>
    姓名:<inpu type="text" [(ngModel)]="user.name"></inpu>
  </div>
  <app-child [greeting]="greeting" [user]="user"></app-child>
</div>

修改app.component.css

.parent{
  background:cyan;
}

启动项目

报了个错,因为用到的双向绑定需要在app.module.ts

import{FormsModule} from '@angular/forms';

imports: [
    BrowserModule,
    FormsModule
  ],

控制台的打印结果为

{
  "greeting": {
    "currentValue": "Hello",
    "firstChange": true
  },
  "user": {
    "currentValue": {
      "name": "Tom"
    },
    "firstChange": true
  }
}

当我改变页面上父组件中问候语的值的时候,会打印出

{
  "greeting": {
    "previousValue": "Hello",
    "currentValue": "Hello1",
    "firstChange": false
  }
}

但当我改变页面上父组件中姓名的值的时候,不会打印出新东西。

因为greeting是字符串是不可变对象(每次值改变的时候都会创建一个新的字符串,然后把引用指向新的字符串),而user是可变对象,修改姓名的值的时候并没有改变user对象的引用。那么怎么监控可变对象呢,用doCheck

当我改变页面中子组件的消息时,也不会打印出新东西,因为子组件中的message属性没有被@Input()标记不是输入属性。

变更检测机制

查看package.json文件中的dependencies的zone.js

就是zone.js来实现变更检测机制的,主要作用是保证属性的变化和页面的变化是一致的,浏览器中发生的任何异步事件都会触发变更检测,比如点击按钮,输入数据。。。

这里写图片描述

默认的是Default策略,当父组件变化时会检测整个组件树

如果在子组件中设置了OnPush,当父组件变化时就只会检测父组件。

这里写图片描述

当孙子组件1发生变化后,红色的部分都会被检测一遍,也就是调DoCheck方法。并且是从父组件开始检查

修改child.component.ts

import {Component, OnInit,Input,OnChanges,SimpleChanges,DoCheck} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit,OnChanges,DoCheck{

  @Input()
  greeting: string;

  @Input()
  user:{name:string};

  message:string="初始化消息";

  oldUsername:string;

  changeDetected:boolean=false;

  noChangeCount:number=0;

  constructor() {
  }

  ngOnInit() {
  }


  ngOnChanges(changes: SimpleChanges): void {
    console.log(JSON.stringify(changes,null,2));
  }

  ngDoCheck(): void {

    if(this.user.name!==this.oldUsername){
      this.changeDetected=true;
      console.log("DoCheck:user.name从"+this.oldUsername+"变为"+this.user.name)
      this.oldUsername=this.user.name;
    }

    if(this.changeDetected){
      this.noChangeCount=0;
    }else{
      this.noChangeCount=this.noChangeCount+1;
      console.log("DoCheck:user.name没变化时ngDoCheck方法已经被调用"+this.noChangeCount)
    }

    this.changeDetected=false;
  }

}

这里写图片描述

当我在问候语的输入框和姓名的输入框中来回切换点击的时候,就会触发docheck方法。当我改变姓名的值的时候也会触发。之后我再点击输入框的时候,调用次数重置为1。这就是上一段代码要实现的效果。

虽然当我修改姓名的时候这个钩子会被调用,但是我们必须要小心ngdocheck这个钩子会非常频繁的被调用,每一次变化都会被调用,在这个例子中,我还没做任何操作呢,只是在页面随便点点就会被调用好几次,只有很少的调用次数是修改数据的时候触发的。

所以对ngDoCheck这个方法的实现一定要非常高效,非常轻量级,不然会引起性能问题。不光是这个方法,变更检测中的那些带Check的方法都应该这样

view钩子

如何在父组件中调用子组件中的方法

新建一个项目demo5

新建一个组件ng g component child

修改child.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }


  greeting(name:string){
    console.log("hello"+name);
  }

}

修改app.component.html

<app-child #child1></app-child>
<app-child #child2></app-child>
<button (click)="child2.greeting('Jerry')"></button>

修改app.component.ts

import { Component,ViewChild } from '@angular/core';
import {ChildComponent} from "./child/child.component";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  @ViewChild("child1")
  child1:ChildComponent //获得子组件之后就可以调用子组件中的方法了

  constructor(){}

  ngOnInit():void{
    this.child1.greeting("Tom");//调用方法
  }
}

启动项目

会在控制台打印出helloTom

然后我点击按钮,会打印出helloJerry

这样就实现了在父组件中调用子组件方法。

现在学习那两个钩子AfterViewInit,AfterViewChecked

修改child.component.ts

import { Component, OnInit ,AfterViewInit,AfterViewChecked} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit,AfterViewInit,AfterViewChecked {

  ngAfterViewInit(): void {
    console.log("子组件的视图初始化完毕");
  }

  ngAfterViewChecked(): void {
    console.log("子组件的视图变更检测完毕");
  }

  constructor() { }

  ngOnInit() {
  }


  greeting(name:string){
    console.log("hello"+name);
  }

}

修改app.component.ts

import { Component,ViewChild,OnInit,AfterViewInit,AfterViewChecked} from '@angular/core';
import {ChildComponent} from "./child/child.component";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit,AfterViewInit,AfterViewChecked{

  //在组件模板的内容都已经呈现给用户看之后,会调用这两个方法
  ngAfterViewInit(): void {
    console.log("父组件的视图初始化完毕");
  }

  ngAfterViewChecked(): void {
    console.log("父组件的视图变更检测完毕");
  }

  @ViewChild("child1")
  child1:ChildComponent

  constructor(){}

  ngOnInit():void{

    setInterval(()=>{
      this.child1.greeting("Tom");
    },5000);

  }
}

启动项目,会在控制台打印出

子组件的视图初始化完毕
子组件的视图变更检测完毕
子组件的视图初始化完毕
子组件的视图变更检测完毕
父组件的视图初始化完毕
父组件的视图变更检测完毕
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
  • 初始化的方法会在变更检测方法前面被调用。都是在视图组装完毕之后被调用的
  • 不要在这两个方法中去改变视图中绑定的东西,如果想改变也要写在一个setTimeout里边
  • 初始化方法只会被调用一次
  • 如果有子组件,会先弄完子组件的然后再弄父组件的。(这里弄了两个子组件所以子组件调用了两次)
  • 如果想实现ngAfterViewChecked这个钩子,方法一定要非常高效,非常轻量级,不然会引起性能问题

修改app.component.ts,

  message:string;


  ngAfterViewInit(): void {
    console.log("父组件的视图初始化完毕");
    this.message="Hello";
  }

修改app.component.html

<app-child #child1></app-child>
<app-child #child2></app-child>
<button (click)="child2.greeting('Jerry')">调用child2的greeting方法</button>
{{message}}

启动项目,这时候会报一个错

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Hello'.

因为Angular规定一个视图在组装好之后再去更新这个视图

而ngAfterViewInit就是在视图组装好之后被触发的

解决办法

  ngAfterViewInit(): void {
    console.log("父组件的视图初始化完毕");
    setTimeout(()=>{
      this.message="Hello";
    },0);

  }

让其在JavaScript的另一个运行周期中去运行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值