前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AngularDart 4.0 高级-生命周期钩子 顶

AngularDart 4.0 高级-生命周期钩子 顶

作者头像
南郭先生
发布2018-08-14 15:31:23
6.1K0
发布2018-08-14 15:31:23
举报
文章被收录于专栏:Google DartGoogle Dart

组件有一个由Angular自己管理的生命周期。

Angular创建它,渲染它,创建和渲染它的子项,在数据绑定属性发生变化时对其进行检查,并在将它从DOM中删除之前对其进行销毁。

Angular提供生命周期挂钩,提供这些关键生命时刻的可视性以及发生时的行为能力。

指令具有相同的生命周期挂钩集,减去特定于组件内容和视图的挂钩。

组件生命周期挂钩

指令和组件实例的生命周期与Angular创建,更新和摧毁它们一样。 开发人员可以通过在Angular core库中实现一个或多个Lifecycle Hook界面来挖掘该生命周期中的关键时刻。

每个接口都有一个单一的钩子方法,其名称是以ng开头的接口名称。 例如,OnInit接口有一个名为ngOnInit的钩子方法,Angular在创建组件后立即调用:

lib/src/peek_a_boo_component.dart (ngOnInit)

代码语言:javascript
复制
class PeekABoo implements OnInit {
  final LoggerService _logger;

  PeekABoo(this._logger);

  // implement OnInit's `ngOnInit` method
  void ngOnInit() {
    _logIt('OnInit');
  }

  void _logIt(String msg) {
    // Don't tick or else
    // the AfterContentChecked and AfterViewChecked recurse.
    // Let parent call tick()
    _logger.log("#${_nextId++} $msg");
  }
}

没有指令或组件会实现所有的生命周期钩子,并且一些钩子只对组件有意义。 如果它被定义了,Angular只会调用一个指令/组件钩子方法。

生命周期序列

通过调用其构造函数创建组件/指令后,Angular在特定时刻按以下顺序调用生命周期钩子方法:

钩子

作用和时机

ngOnChanges

Angular(重新)设置数据绑定输入属性时响应。 该方法接收当前和前一个属性值的SimpleChanges对象。 在ngOnInit之前调用并且每当有一个或多个数据绑定输入属性发生变化时调用。

ngOnInit

在Angular首次显示数据绑定属性并设置指令/组件的输入属性后,初始化指令/组件。 在第一次ngOnChanges之后调用一次。

ngDoCheck

检测Angular无法或无法自行检测到的更改并采取相应措施。 在每次更改检测运行期间,立即在ngOnChanges和ngOnInit之后调用。

ngAfterContentInit

在Angular将外部内容投影到组件的视图之后进行响应。 在第一次NgDoCheck之后调用一次。 组件独有的钩子。

ngAfterContentChecked

在Angular检查投影到组件中的内容之后作出响应。 在ngAfterContentInit和后续的每次NgDoCheck之后调用。 组件独有的钩子。

ngAfterViewInit

在Angular初始化组件的视图和子视图之后进行响应,。 在第一次ngAfterContentChecked之后调用一次。 组件独有的钩子。

ngAfterViewChecked

在Angular检查组件的视图和子视图之后作出响应。 在ngAfterViewInit和后续的每次ngAfterContentChecked之后调用。 组件独有的钩子。

ngOnDestroy

在Angular摧毁指令/组件之前进行清理。 取消订阅observables并分离事件处理程序以避免内存泄漏。 在Angular摧毁指令/组件之前调用。

其他生命周期挂钩

其他Angular子系统除了这些组件钩子可能有自己的生命周期钩子。

例如,路由器也有自己的路由器生命周期挂钩,可以让我们利用路由导航中的特定时刻。 可以在ngOnInit和routerOnActivate之间绘制一个平行线。 两者的前缀都是为了避免碰撞,并且在组件初始化时都运行正确。

第三方库可能也会实现它们的钩子,以便让开发人员更好地控制这些库的使用方式。

生命周期练习

通过组件的一系列练习在根AppComponent的控制下呈现来演示生命周期挂钩。

它们遵循一种常见的模式:父组件作为一个子组件的一个或多个生命周期钩子方法的测试装备。

以下是每个练习的简要说明:

组件

描述

Peek-a-boo

演示每个生命周期的钩子。 每个挂钩方法都会写入屏幕日志。

Spy

指令也有生命周期挂钩。 SpyDirective可以使用ngOnInit和ngOnDestroy挂钩创建或销毁它探测的元素。 此示例将SpyDirective应用于由父SpyComponent管理的ngFor英雄迭代器中的<div>。

OnChanges

看看每次组件输入属性发生变化时,Angular如何用变更对象调用ngOnChanges钩子。 显示如何解释更改对象。

DoCheck

使用自定义更改检测实现ngDoCheck方法。 看看Angular多久会调用这个钩子,并在更改日志后观察它。

AfterView

通过视图显示Angular的意图。 演示ngAfterViewInit和ngAfterViewChecked挂钩。

AfterContent

演示如何将外部内容投影到组件中,以及如何区分组件的视图中的投影内容和子组件。 演示ngAfterContentInit和ngAfterContentChecked挂钩。

Counter

演示组件和指令的组合,每个组件都有自己的钩子。 在此示例中,每次父组件递增其输入计数器属性时,CounterComponent都会记录更改(通过ngOnChanges)。 同时,前面例子中的SpyDirective被应用到CounterComponent日志中,它监视正在创建和销毁的日志条目。

本章的其余部分将进一步详细讨论选定的练习

Peek-a-boo:所有钩子

PeekABooComponent演示了一个组件中的所有钩子。

如果有的话,你很少会实现像这样的所有接口。 peek-a-boo存在以显示Angular如何按预期顺序调用钩子。

此快照反映用户单击“创建...”按钮然后单击“销毁...”按钮后日志的状态。

日志消息的顺序遵循规定的钩子调用顺序:OnChanges,OnInit,DoCheck(3x),AfterContentInit,AfterContentChecked(3x),AfterViewInit,AfterViewChecked(3x)和OnDestroy。

构造函数本身不是一个Angular钩子。 日志确认输入属性(在这种情况下的name属性)在构造时没有分配的值。

如果用户点击Update Hero按钮,日志会显示另一个OnChanges和两个更多的DoCheck,AfterContentChecked和AfterViewChecked三元组。 显然这三个钩子经常发射。 尽可能保持这些钩子中的逻辑!

接下来的例子集中于钩子细节。

刺探OnInit和OnDestroy

使用这两个间谍钩进行卧底探索,以发现元素何时被初始化或销毁。

这是指令的完美渗透工作。 英雄们永远不会知道他们正在被监视。

一边开玩笑,注意两点:

  • Angular为指令和组件调用钩子方法。
  • 间谍指令可以提供对不能直接更改的DOM对象的洞察。 显然,你不能触摸本地div的实现。 您也不能修改第三方组件。 但是你可以监察一个指令。

这个偷偷摸摸的间谍指令很简单,几乎完全由ngOnInit和ngOnDestroy钩子组成,这些钩子通过注入的LoggerService将消息记录到父级。

代码语言:javascript
复制
// Spy on any element to which it is applied.
// Usage: <div mySpy>...</div>
@Directive(selector: '[mySpy]')
class SpyDirective implements OnInit, OnDestroy {
  final LoggerService _logger;

  SpyDirective(this._logger);

  ngOnInit() => _logIt('onInit');

  ngOnDestroy() => _logIt('onDestroy');

  _logIt(String msg) => _logger.log('Spy #${_nextId++} $msg');
}

您可以将间谍应用到任何本机或组件元素,并且会与该元素的同一时间进行初始化和销毁。 在这里它被附加到重复的英雄<div>

代码语言:javascript
复制
<div *ngFor="let hero of heroes" mySpy class="heroes">
  {{hero}}
</div>

每个间谍的出生和死亡标志着所附英雄<div>的出生和死亡,并在Hook Log中有一个条目,如下所示:

添加一个英雄会产生一个新的英雄<div>。 间谍的ngOnInit记录该事件。

重置按钮清除英雄列表。 Angular从DOM中移除所有英雄<div>元素并同时销毁他们的间谍指令。 间谍的ngOnDestroy方法报告其最后时刻。

ngOnInit和ngOnDestroy方法在实际应用中扮演更重要的角色。

OnInit

使用ngOnInit有两个主要原因:

  • 在施工后不久执行复杂的初始化
  • 在Angular设置输入属性后设置组件

有经验的开发人员同意组件应该便于构建且安全。

Angular团队负责人Misko Hevery解释了为什么您应该避免使用复杂的构造函数逻辑。

不要在组件构造函数中获取数据。您不应该担心当在测试下创建或决定显示之前时新组件会尝试联系远程服务器。构造函数不应仅仅将初始局部变量设置为简单值。

ngOnInit是组件获取其初始数据的好地方。 教程HTTP章节显示了如何。

还要记住,指令的数据绑定输入属性在构建之后才会设置。 如果您需要根据这些属性初始化指令,那么这是一个问题。 当ngOninit运行时,它们将被设置。

ngOnChanges方法是您第一次访问这些属性的机会。 在ngOnInit之前Angular会调用ngOnChanges ...并在此之后多次调用。 它只调用一次ngOnInit。

您可以期待Angular在创建组件后立即调用ngOnInit方法。 这就是深度初始化逻辑所属的地方。

OnDestroy

将清理逻辑放入ngOnDestroy中,在Angular销毁指令之前必须运行的逻辑。

这是通知应用程序的另一部分组件将要销毁的时间。

这是释放资源的地方,不会自动收集垃圾。 取消订阅observables和DOM事件。 停止间隔定时器。 取消注册此指令在全局或应用服务中注册的所有回调。 如果你忽视这样做,你会冒内存泄漏的风险。

OnChanges

只要检测到组件(或指令)的输入属性发生变化,Angular就会调用它的ngOnChanges方法。 这个例子监视OnChanges钩子。

lib/src/on_changes_component.dart (ngOnChanges)

代码语言:javascript
复制
ngOnChanges(Map<String, SimpleChange> changes) {
  changes.forEach((String propName, SimpleChange change) {
    String cur = JSON.encode(change.currentValue);
    String prev = change.previousValue == null
        ? "{}"
        : JSON.encode(change.previousValue);
    changeLog.add('$propName: currentValue = $cur, previousValue = $prev');
  });
}

ngOnChanges方法接受一个对象,该对象将每个已更改的属性名称映射到保存当前和前一个属性值的SimpleChange对象。 这个钩子迭代已更改的属性并记录它们。

示例组件OnChangesComponent具有两个输入属性:hero和power。

代码语言:javascript
复制
@Input()
Hero hero;
@Input()
String power;

宿主OnChangesParentComponent像这样绑定到它们:

代码语言:javascript
复制
<on-changes [hero]="hero" [power]="power"></on-changes>

以下是用户进行更改时的示例。

日志条目显示为power属性更改的字符串值。 但ngOnChanges并没有捕捉到hero.name的变化,这一开始令人惊讶。

当输入属性的值改变时,Angular只会调用钩子。 hero属性的值是对hero对象的引用。 Angular并不在意英雄自己的name属性发生了变化。 英雄对象引用没有改变,所以从Angular的角度来看,没有改变的反馈!

DoCheck

使用DoCheck钩子来检测并处理Angular自己无法捕获的更改。

使用此方法检测Angular忽略的更改。

DoCheck示例使用以下ngDoCheck钩子扩展了OnChanges示例:

lib/src/do_check_component.dart (ngDoCheck)

代码语言:javascript
复制
ngDoCheck() {
  if (hero.name != oldHeroName) {
    changeDetected = true;
    changeLog.add(
        'DoCheck: Hero name changed to "${hero.name}" from "$oldHeroName"');
    oldHeroName = hero.name;
  }

  if (power != oldPower) {
    changeDetected = true;
    changeLog.add('DoCheck: Power changed to "$power" from "$oldPower"');
    oldPower = power;
  }

  if (changeDetected) {
    noChangeCount = 0;
  } else {
    // log that hook was called when there was no relevant change.
    var count = noChangeCount += 1;
    var noChangeMsg =
        'DoCheck called ${count}x when no change to hero or power';
    if (count == 1) {
      // add new "no change" message
      changeLog.add(noChangeMsg);
    } else {
      // update last "no change" message
      changeLog[changeLog.length - 1] = noChangeMsg;
    }
  }

  changeDetected = false;
}

此代码检查某些感兴趣的值,捕获并比较其当前状态与以前的值。 当英雄或权力没有实质性变化时,它会向日志中写入特殊消息,以便您可以看到DoCheck被多次调用。 结果是高亮的:

虽然ngDoCheck挂钩可以检测到英雄的name何时发生变化,但它的成本非常可怕。 这个钩子以巨大的频率被调用 - 在每个变化检测周期之后,无论变化发生在何处。 在用户可以做任何事情之前,在这个例子中它被调用了二十次。

大部分初始检查都是由Angular在页面其他地方首次渲染(与数据无关)而触发的。 仅仅通过鼠标移动到另一个输入框就会触发一个呼叫。 相对较少的调用显示相关数据的实际变化。 很显然,我们的实施必须非常轻便,否则用户体验将受到影响。

AfterView

AfterView样本探讨了Angular在创建组件的子视图后调用的AfterViewInit和AfterViewChecked挂钩。

以下是在输入框中显示英雄名字的子视图:

lib/src/after_view_component.dart (child view)

代码语言:javascript
复制
@Component(
  selector: 'my-child-view',
  template: '<input [(ngModel)]="hero">',
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class ChildViewComponent {
  String hero = 'Magneta';
}

AfterViewComponent在其模板中显示此子视图:

lib/src/after_view_component.dart (template)

代码语言:javascript
复制
template: '''
  <div>-- child view begins --</div>
    <my-child-view></my-child-view>
  <div>-- child view ends --</div>
  <p *ngIf="comment.isNotEmpty" class="comment">{{comment}}</p>''',

以下钩子根据更改子视图内的值来执行操作,只能通过使用@ViewChild注解的属性查询子视图来实现。

lib/src/after_view_component.dart (class excerpts)

代码语言:javascript
复制
class AfterViewComponent implements AfterViewChecked, AfterViewInit {
  var _prevHero = '';

  // Query for a VIEW child of type `ChildViewComponent`
  @ViewChild(ChildViewComponent)
  ChildViewComponent viewChild;

  ngAfterViewInit() {
    // viewChild is set after the view has been initialized
    _logIt('AfterViewInit');
    _doSomething();
  }

  ngAfterViewChecked() {
    // viewChild is updated after the view has been checked
    if (_prevHero == viewChild.hero) {
      _logIt('AfterViewChecked (no change)');
    } else {
      _prevHero = viewChild.hero;
      _logIt('AfterViewChecked');
      _doSomething();
    }
  }
  // ...
}

遵守单向数据流规则

当英雄名字超过10个字符时,doSomething方法更新屏幕。

lib/src/after_view_component.dart (doSomething)

代码语言:javascript
复制
// This surrogate for real business logic sets the `comment`
void _doSomething() {
  var c = viewChild.hero.length > 10 ? "That's a long name" : '';
  if (c != comment) {
    // Wait a tick because the component's view has already been checked
    _logger.tick().then((_) {
      comment = c;
    });
  }
}

为什么doSomething方法在更新comment之前等待一个tick?

Angular的单向数据流规则禁止在视图组成之后更新视图。 组件视图组合完成后,这两个钩子都会触发。

如果钩子立即更新组件的数据绑定comment属性,Angular会抛出一个错误(尝试它!)。

LoggerService.tick()推迟了浏览器更新周期的一次日志更新......并且这足够长。

这里是AfterView的行动

请注意,经常在没有感兴趣的变化时,Angular经常调用AfterViewChecked。 编写瘦钩方法以避免性能问题。

AfterContent

AfterContent示例探索在Angular将外部内容投影到组件后的Angular调用的AfterContentInit和AfterContentChecked挂钩。

内容投影

内容投影是一种从组件外部导入HTML内容并将该内容插入组件模板中指定位置的方法。

Angular 1开发人员知道这种技术是跨越式的。

考虑以前的AfterView示例中的这种变化。 这一次,它不是在模板中包含子视图,而是从AfterContentComponent的父项导入内容。 这是父母的模板。

lib/src/after_content_component.dart (template excerpt)

代码语言:javascript
复制
template: '''
  <div class="parent">
    <h2>AfterContent</h2>

    <div *ngIf="show">
      <after-content>
        <my-child></my-child>
      </after-content>
    </div>

    <h4>-- AfterContent Logs --</h4>
    <p><button (click)="reset()">Reset</button></p>
    <div *ngFor="let msg of logs">{{msg}}</div>
  </div>
  ''',

请注意,<my-child>标签隐藏在<after-content>标签之间。 除非您打算将该内容投影到组件中,否则绝不要在组件的元素标签之间放置内容。

现在看看组件的模板:

lib/src/after_content_component.dart (template)

代码语言:javascript
复制
template: '''
  <div>-- projected content begins --</div>
    <ng-content></ng-content>
  <div>-- projected content ends --</div>
  <p *ngIf="comment.isNotEmpty" class="comment">{{comment}}</p>
  ''',

<ng-content>标记是外部内容的占位符。 它告诉Angular在哪里插入该内容。 在这种情况下,投影内容是来自父级的<my-child>。

内容投影的指示标记是(a)组件元素标签之间的HTML和(b)组件模板中存在<ng-content>标签。

AfterContent挂钩

AfterContent挂钩与AfterView挂钩类似。 关键的区别在于子组件

  • AfterView钩子涉及ViewChildren,子组件的元素标签出现在组件的模板中。
  • AfterContent挂钩涉及ContentChildren,Angular投射到组件中的子组件。

以下AfterContent挂钩根据内容子代(只能通过使用@ContentChild注解的属性查询它)中的值进行更改。

lib/src/after_content_component.dart (class excerpts)

代码语言:javascript
复制
class AfterContentComponent implements AfterContentChecked, AfterContentInit {
  String _prevHero = '';
  String comment = '';

  // Query for a CONTENT child of type `ChildComponent`
  @ContentChild(ChildComponent)
  ChildComponent contentChild;

  ngAfterContentInit() {
    // contentChild is set after the content has been initialized
    _logIt('AfterContentInit');
    _doSomething();
  }

  ngAfterContentChecked() {
    // contentChild is updated after the content has been checked
    if (_prevHero == contentChild?.hero) {
      _logIt('AfterContentChecked (no change)');
    } else {
      _prevHero = contentChild?.hero;
      _logIt('AfterContentChecked');
      _doSomething();
    }
  }

  // ...
}

AfterContent没有对单向流的担忧

该组件的doSomething方法立即更新组件的数据绑定comment属性。 不需要等

回想一下,在调用AfterView钩子之前,Angular调用了AfterContent的两个钩子。 在完成该组件的视图之前,Angular会完成投影内容的组合。 AfterContent ...和AfterView ...钩子之间有一个小窗口来修改宿主视图。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 组件生命周期挂钩
  • 生命周期序列
  • 其他生命周期挂钩
  • 生命周期练习
  • Peek-a-boo:所有钩子
  • 刺探OnInit和OnDestroy
    • OnInit
      • OnDestroy
      • OnChanges
      • DoCheck
      • AfterView
        • 遵守单向数据流规则
        • AfterContent
          • 内容投影
            • AfterContent挂钩
              • AfterContent没有对单向流的担忧
              相关产品与服务
              腾讯云代码分析
              腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档