关于组件的概念,现在使用已经很广泛了,我们今天就来深入的了解一下 Web Components
与 Angular
当中的 Component
Web Components
W3C
为统一组件化标准方式,提出 Web Components
的标准,它允许我们创建可重用的定制元素(它们的功能封装在代码之外)并且在 Web
应用中使用它们,Web Components
标准主要包括以下几个重要的概念
Custom elements
(自定义元素)- 可以创建自定义的
HTML
标记和元素
- 可以创建自定义的
Shadow DOM
(影子DOM
)- 用于将封装的
Shadow DOM
树附加到元素(与主文档DOM
分开呈现)并控制其关联的功能 - 通过这种方式,可以保持元素的功能私有,这样就可以被脚本化和样式化的同时而不用担心与文档的其他部分发生冲突
- 用于将封装的
HTML templates
(HTML
模板)- 简单来说就是使用
<template>
和<slot>
标签去预定义一些内容,但并不加载至页面,而是将来使用JavaScript
代码去初始化它 - 可以作为自定义元素结构的基础被多次重用
- 简单来说就是使用
下面我们就通过一个简单的示例来看看 Web Components
到底是怎么使用的,例子摘取自 mdn/web-components-examples,但是稍微调整了一下,使用方式很简单,直接在页面当中使用我们自定义的组件即可,如下
1 | <!-- 使用 --> |
具体实现如下
1 | // 定义组件 A 和 B |
很简单的一个示例,就算没有了解过 Web Components
相关知识也可以看懂大概,我们来简单的梳理一下
window.customElements
,简单来说就是用来定义一个自定义标签(custom elements)attachShadow
,给指定的元素挂载一个Shadow DOM
,并且返回它的ShadowRoot
,简单来说就是返回指定Shadow DOM
封装模式,有下面两种方式open
,指定为开放的封装模式closed
,指定为关闭的封装模式,会让该ShadowRoot
的内部实现无法被JavaScript
访问及修改,也就是说将该实现不公开(比如<video>
标签)
另外,在 Web Components
当中也是有生命周期回调函数存在的,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用,主要有下面四个
connectedCallback
,当customElements
首次被插入文档DOM
时,被调用disconnectedCallback
,当customElements
从文档DOM
中删除时,被调用adoptedCallback
,当customElements
被移动到新的文档时,被调用attributeChangedCallback
,当customElements
增加、删除、修改自身属性时,被调用
上面就是一个简单的 Web Components
示例,这里也就只简单的介绍一下,如果想了解更多,可以参考 Web Components
在了解了 Web Components
的基本概念以后,我们就来看看 Angular
当中的 Component
Angular Component
在 Angular
当中,Component
属于指令的一种,即组件继承于指令(详细可见 packages/core/src/metadata/directives.ts),所以我们可以简单的将其理解为拥有模板的指令(其它两种是属性型指令和结构型指令),基本组成如下
1 | @Component({ |
主要分为以下几部分
- 组件装饰器,每个组件类必须用
@component
进行装饰才能成为Angular
组件 - 组件元数据,指的是
selector
、template
这一系列的属性 - 组件模板,每个组件都会关联一个模板,这个模板最终会渲染到页面上,页面上这个
DOM
元素就是此组件实例的宿主元素- 一般来说有两种引入方式
templateUrl
和template
,区别就是内联和外链
- 一般来说有两种引入方式
- 组件类,组件实际上也是一个普通的类,组件的逻辑都在组件类里定义并实现
- 组件接口,组件可以定义内部需要实现的接口(比如上面的
OnInit
对应着组件的生命周期钩子ngOnInit()
)
组件元数据
主要分为两种,自身元数据属性和从 core/Directive
上继承过来的,先来看自身元数据属性
自身元数据属性
名称 | 类型 | 作用 |
---|---|---|
animations |
AnimationEntryMetadata[] |
设置组件的动画 |
changeDetection |
ChangeDetectionStrategy |
设置组件的变化监测策略 |
encapsulation |
ViewEncapsulation |
设置组件的视图包装选项 |
entryComponents |
any[] |
设置将被动态插入到该组件视图中的组件列表 |
interpolation |
[string, string] |
自定义组件的插值标记,默认是双大括号 |
moduleId |
string |
设置该组件在 ES/CommonJS 规范下的模块 id ,它被用于解析模板样式的相对路径 |
styleUrls |
string[] |
设置组件引用的外部样式文件 |
styles |
string[] |
设置组件使用的内联样式 |
template |
string |
设置组件的内联模板 |
templateUrl |
string |
设置组件模板所在路径(外链) |
viewProviders |
Provider[] |
设置组件及其所有子组件(不含 ContentChildren )可用的服务 |
从 core/Directive 继承
名称 | 类型 | 作用 |
---|---|---|
exportAs |
string |
设置组件实例在模板中的别名,使得可以在模板中调用 |
host |
{[key: string]: string} |
设置组件的事件、动作和属性等 |
inputs |
string[] |
设置组件的输入属性 |
outputs |
string[] |
设置组件的输出属性 |
providers |
Provider[] |
设置组件及其所有子组件(含 ContentChildren )可用的服务(依赖注入) |
queries |
{[key: string]: any} |
设置需要被注入到组件的查询 |
selector |
string |
设置用于在模板中识别该组件的 CSS 选择器(组件的自定义标签) |
下面我们就来看看一些比较常用的元数据的具体含义
inputs
有两种写法,第一种方式不太推荐使用,用的比较多的是下面那种,推荐在组件当中使用 @Input()
来进行接收
1 | @Component({ |
等价于下面这种
1 | @Component({ |
outputs
同上,和 inputs
类似
1 | @Component({ |
等价于
1 | @Component({ |
host
host
主要用来绑定事件,同上面一样,还是推荐使用 @HostBinding
来进行绑定
1 | @Component({ |
等价于
1 | @Component({ |
queries
主要用来视图查询,就是 @ViewChild
另外一种写法,推荐使用 @ViewChild
装饰器
1 | @Component({ |
等价于
1 | @Component({ |
queries
这个主要用来内容查询使用的,也就是 @ContentChild
装饰器,不过一般情况下使用较少,模版如下
1 | <my-list> |
1 | @Directive({ |
等价于
1 | @Component({ |
styleUrls 和 styles
这两个元数据一般是用来设置样式,styleUrls
和 styles
是允许同时指定的,不过两者之间存在优先级的关系,如下
1 | 模板内联样式 > styleUrls > styles |
不过一般还是建议使用 styleUrls
引用外部样式表文件,这样代码结构相比 styles
更清晰、更易于管理
changeDetection
这个参数主要用来设置组件的变换检测机制,有两种取值方式 Default
和 OnPush
,默认为 Default
ChangeDetectionStrategy.Default
- 组件的每次变化监测都会检查其内部的所有数据(引用对象也会深度遍历),以此得到前后的数据变化
ChangeDetectionStrategy.OnPush
- 组件的变化监测只检查输入属性(即
@Input
修饰的变量)的值是否发生变化,当这个值为引用类型(Object
,Array
等)时,则只对比该值的引用
- 组件的变化监测只检查输入属性(即
显然,OnPush
策略相比 Default
降低了变化监测的复杂度,很好地提升了变化监测的性能,如果组件的更新只依赖输入属性的值,那么在该组件上使用 OnPush
策略是一个很好的选择
encapsulation
关于这个属性的详细介绍可以参考 :host 和 ::ng-deep,简单来说就是控制视图的封装模式,有三种模式,原生(Native
)、仿真(Emulated
)和无(None
)
生命周期
当 Angular
使用构造函数新建组件后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法
生命周期钩子 | 调用时机 |
---|---|
ngOnChanges |
在 ngOnInit 之前调用,或者当组件输入数据(通过 @Input 装饰器显式指定的那些变量)变化时调用 |
ngOnInit |
第一次 ngOnChanges 之后调用,建议此时获取数据,不要在构造函数中获取 |
ngDoCheck |
每次变化监测发生时被调用 |
ngAfterContentInit |
使用将外部内容嵌入到组件视图后被调用,第一次 ngDoCheck 之后调用且只执行一次(只适用组件) |
ngAfterContentChecked |
ngAfterContentInit 后被调用,或者每次变化监测发生时被调用(只适用组件) |
ngAfterViewInit |
创建了组件的视图及其子视图之后被调用(只适用组件) |
ngAfterViewChecked |
ngAfterViewInit ,或者每次子组件变化监测时被调用(只适用组件) |
ngOnDestroy |
销毁指令或者组件之前触发,此时应将不会被垃圾回收器自动回收的资源(比如已订阅的观察者事件、绑定过的 DOM 事件、通过 setTimeout 或 setInterval 设置过的计时器等等)手动销毁掉 |