Angular4总结(五)—— 父子组件通信,钩子方法

组件和组件之间一定得是松耦合的这样可重用性才高,想象一下这样的场景,A和B两个兄弟组件,但是A调用B组件的一个方法,那么这两个组件就紧密的联系在了一起,这其实是并不可取的。

组件我们应该看作是一个黑盒,组件只需要"输入"和"输出"的功能就行,组件并不关心到底是谁给他输入的,也不关心他会输出给谁。

父向子传递数据

首先我们需要在子组件中,定义我们想要接受的参数,如下:

@Input()
private stockCode: string;

@Input()
private amount: number;

@Input()是输入属性的关键所在,而且需要注意的是使用注解的时候,一定要注意后面的小括号,不要落了,之前我就犯过这样的错误。

我们这里只是接受的,那么入口在哪里呢,当然在父组件中了,如下:

<app-order [stockCode] = "test" [amount] = "5"></app-order>

这两个参数的用法,和我们之前说过的"属性绑定"一样,都是通过"[ ]"扩起来的,之前是又html到ts文件,这次是由父组件到子组件。

子向父传递数据

子组件中我们需要向父组件发射事件,核心代码如下:

  @Output()
  lastChangesPrice: EventEmitter<PriceQuote> = new EventEmitter();

    this.lastChangesPrice.emit(new PriceQuote('IBM', price));

父组件接受事件的html代码如下:

<app-price-quote (lastChangesPrice) = "lastChanged($event)"></app-price-quote>

和之前父向子传递不同,这里需要事件绑定(我们可以这样想,因为刚刚我们发送了一个事件,所以这里需要一个事件进行承接)。小括号中的名称需要与@Output对应上,后面的方法是需要在ts文件中进行具体的实现。

js代码如下:

public lastChanged(event: PriceQuote) {
    //TODO
  }

Tips: 相信看到这里,你已经对@Input和@Output()有了一定的了解,在这里我要说一个实用的技巧,这个技巧将会打通输入与输出的任督二脉:一个组件,我们有一个输入的属性name。这个组件作为一个子组件被父组件调用,父组件可以将一个默认值传递给子组件。但是现在我们希望子组件可以把实时变化的数据传递出来,这个时候就需要@Output了,定义一个EventEmitter,把值emit出来,父组件接收后,再把值赋给初始化的变量这个过程其实相当的麻烦,那么我们应该如何避免呢?就拿刚刚举的例子说,输入属性为name那么我们将输出属性定义为nameChange,没错就是在输入属性后面加上一个Change。然后父组件调用的地方写成双向绑定,angular就会帮我们做自动的绑定了,是不是很简单呢?

中间人模式

A组件和B组件之间是兄弟组件,他们同样属于C组件。

如果我们直接让A和B之间相互调用方法,那么他们之间形成紧密的耦合,所以我们引入中间人模式来实现松耦合:A需要调用B做的事情我们通过输出事件,先让C知道,然后C会马不停蹄的去通知B去执行。这个C可以理解为一个中间商衔接了A和B组件。

组件生命周期

  • 组件初始化

    • constructor1⃣️
    • ngOnChanges
    • ngOnInit1⃣️
    • ngDoCheck
    • ngAfterContentInit1⃣️
    • ngAfterContentChecked
    • ngAfterViewInit1⃣️
    • ngAfterViewChecked
  • 变化监测

    • ngOnChanges
    • ngDoCheck
    • ngAfterContentChecked
    • ngAfterViewChecked
  • 组件销毁

    • ngOnDestroy1⃣️

Tips:带有1⃣️标示的是只能执行一次的

Changes钩子

这里重点说一下ngOnChanges:

发生在ngOnInit之前,并且是只有在有输入属性的情况下,会被调用

但是又了输入属性的情况之后,其实也是未必会调用的,这里要从可变对象不可变对象说起

可变对象:

对象的地址直接指向了一块内存,例如说定义一个string,然后将string的值进行修改,string对象的地址就发生了改变,这个时候是会触发ngOnChanges的。

不可变对象:

修改了对象中的属性,只是修改了对象中属性的地址,但是对象本身指向的地址不发生改变,例如说一个object对象,我们修改这个object中的一个id属性,那么其实是不会触发ngOnChanges的,但是子组件,依然可以捕获到这次改变,然后将值进行改变,主要根据的就是angular的变更检测机制

变更检测机制

因为有zone.js所以才有了对原生事件监测的机制。

变更检测机制可以分为两种:

  1. 默认策略
  2. onPush策略

他俩的差别就在于,父组件发生了变化,默认策略会一直向子组件,及其孙子组件传递,而如果子组件采用onPush的策略,那么传递将会停止。

那我们应该如何使用变更检测机制呢?angular为我们提供了DoCheck钩子,这个钩子,就可以在刚刚说过的不可变对象发生变化之后,得到响应。

Tips: 当我们使用带有Checked的钩子函数时,一定有极其小心,因为这类函数,会被极其频繁的调用,所以在这种函数中实现一些逻辑的时候,一定要轻量级,否则会造成性能问题。

View钩子

在某些场景上,我们还需要使用父组件调用子组件的方法,这个时候我们可以在ts文件中使用@ViewChild("子组件别名"),这个子组件别名是什么呢?首先看一下如下代码

<app-a #aComponent></app-a>
<app-b #bComponent></app-b>

这里的app-a和app-b是两个子组件,aComponent和bComponent就是子组件的别名。

我们还可以在模版中使用子组件的方法,看下代码:

<app-a #aComponent></app-a>
<app-b #bComponent></app-b>
<button (click)="aComponent.test()"></button>

在按钮中使用了一个点击事件,去触发test方法。

View相关的一共有两个钩子,这两个钩子都是在组件加载完之后执行的 :

  1. ngAfterViewInit
  2. ngAfterViewChanged

如果一个父组件有两个子组件,他们都实现了这两个钩子,那么执行顺序会是

  • 子组件A ngAfterViewInit
  • 子组件A ngAfterViewChanged
  • 子组件B ngAfterViewInit
  • 子组件B ngAfterViewChanged
  • 父组件 ngAfterViewInit
  • 父组件 ngAfterViewChanged

这就说明父组件在声明之前,一定是先对子组件进行组装,等着组装好了之后,才会组装父组件。

Content钩子

首先介绍一个投影的概念,什么是投影呢,投影相当于子组件给父组件掏了个壁橱,也就是说这篇区域,子组件不知道父组件们都会用来做什么,所以暂时就空在那了。这种思想是不是很像angular的路由。有些人就好想了,为什么在这个时候不用路由呢?因为这种方法比路由更简单一些,在真正的业务中自己需要权衡一下。

content钩子的使用具体来看下代码:

子组件:

<div>
  <h1>
    Test Test
  </h1>
  <ng-contnet></ng-contnet>
</div>

子组件的ng-contnet就相当于一个占位符

父组件:

<app-child>
    <p>
    这里就是传说中掏的壁橱
  </p>
</app-child>

在调用子组件的标签中写上我们需要的代码。

这样做的好处就是,子组件可以被重用的更灵活,不会一点不同,就重新构建一个组件。

那么让我们在思考另一个问题,如果在子组件我想掏两个壁橱怎么办?

别慌,angular早都为我们都想好了。看代码:

子组件:

<div>
  <h1>
    Test Test
  </h1>
  <ng-contnet select=".classA"></ng-contnet>
  <ng-contnet select=".classB"></ng-contnet>
</div>

使用select属性,决定了到底哪一个代码块属于第一个content,哪一个属于第二个。

父组件:

<app-child>
    <p class="classA">
    这里就是传说中掏的壁橱A,{{testValue}}
  </p>
  <div class="classB">
    这里就是传说中的壁橱B
  </div>
</app-child>

这样就能很好的区分出来了。

Tips:

注意我在第一个壁橱中添加了一个插值表达式,这个插值表达式的值,只可以在使用当前父组件的值。

然后引出今天的两个主角:

  • ngAfterContentInit 当投影初始化完毕
  • ngAfterContentChanged 当投影变更检测完毕
上一篇:Mybatis之discriminator(鉴别器)详解


下一篇:浅谈JS的闭包