组件基础
一个组件的组成和生命周期
组件从它们初始化开始就有了生命周期,直到他们从应用中被销毁或移除为止。组件设计的好坏直接影响应用的质量。组件包含各种互相区别的组成部分来创建最终展示的用户交互的UI
通过CLI创建组件时,他创建了在渲染过程中被组合到一起的asset:
- Component Metadata Decorator -- 所有的组件必须被
@Component()
注解,这样可以将组件正确地注册到Angular上。元数据包含很多属性来帮助修改要被渲染的组件改变其行为 - Controller -- 通过上面注解的类本身就是controller,它包含组件的属性和方法,大多数逻辑都写在这里
- Template -- 组件不能脱离模板而存在。这些标记语言定义了UI的布局和内容,渲染后的模板会通过controller来查看绑定的事件和数据
除了这三个必要部份外,还有一些其它的内容来加强组件的功能。包括向组件注入内容,修改组件最终的行为和样式还有跟其它组件的交互
- Providers and hosts -- 服务可以被直接注入到组件,如果它们没有在根模块中被引入。你可以控制这些服务如何被发现和在哪些模块可见
- Inputs -- 组件可以接收通过组件input传给它们的数据,这使得父组件可以通过这种方式直接将数据绑定到子组件上
- Styles and encapsulation -- 组件可以包含一系列CSS样式(可选),这些样式只作用于该组件。这提供了对组件设计的一个操作层,即所有样式不需要全局应用。组件可以配置样式被注入的方式
- Animations -- Angular提供了一个动画库,帮助实现个性化组件模板的过渡效果、设置关键帧等
- Outputs -- 和事件相关的属性,可以用来监听数据改变,或者其他父组件可能感兴趣的事件,也可以用来在组件树中共享数据
- Lifecycle hooks -- 在一个组件的渲染和生命周期中,你可以使用不同的钩子函数来触发应用逻辑。你也可以在这些钩子函数中将数据带进组件,所以钩子函数应用于输入和输出(数据)之中
组件的生命周期
当构建工具不同时,组件的生命周期行为有些许的不同,大部分情况下,使用的构建工具都是Angular CLI
当组件被注册到模块中,模块会创建一个组件工厂类将其存储起来以便后续使用。在应用的生命周期中,一些东西会请求该组件,通常是因为模板中包含该组件和编译器需要这个组件,但有时也会手动请求组件,这种情况下,一个组件的实例需要被加载,那么属于此模块的组件的registry会查找组件并拿到它的工厂类,这个工厂类是CLI生成的(应用运行以前),这个类知道如何实例化组件
在组件实例化之后,元数据被读取,构造函数调用,任何构造器中的逻辑会在组件生命早期运行,你应该注意不要把依赖子组件的东西放在这里运行。组件元数据之后会被Angular完全处理,包括转换模板、样式和数据绑定,如果模板包含任何子组件,那么会开始新的子组件的生命周期,但是它并不会阻塞当前组件的渲染
此时组件初始化完成,子组件完全渲染的生命周期开始,应用状态改变和组件更新,在这个循环中,生命周期钩子函数会运行,包含一些重要的时间点。例如告诉你什么时候input发生变化,另一些告诉你什么时候所有子组件处理完成。有时当组件不再被需要,Angular将会销毁这个组件(及其所有子组件),任何需要的新组件的实例会被重新通过工厂类创建出来
生命周期钩子函数
当你在执行某些代码之前需要知道特定条件的时候,钩子函数变得十分重要。Angular只会运行定义在组件中的钩子函数。它们并不像事件监听器,它们只在对应的生命周期的节点上运行(特殊命名),在controller中定义这些钩子函数需要加上前缀ng
OnInit/OnChanges/OnDestroy
-- 最常用DoCheck/AfterContentChecked/AfterViewChecked
-- 追踪在change detection过程中需要运行的逻辑OnInit/AfterContentInit/AfterViewInit
-- 在组件开始渲染之前运行逻辑,每个都保证不同程度上组件的集成(保证已经准备就绪)
嵌套组件
通常,组件通过声明在另一个组件的模板中来嵌套。任何嵌套在另一个模板中的组件都叫做View Child
,如此命名是因为模板代表的是组件的view。有时一个组件接收内容(content)插入进它的模板,这被称为Content Child
,如此命名是因为这些组件被作为内容插入进了组件而不是直接在模板中声明
<!-- 这是一个 view child -->
<user-avatar [avatar]="avatar"></user-avatar>
<!-- 这是一个 content child -->
<ng-content></ng-content>
<!-- 插入进去的user-details是 content child,因为它不是直接声明在模板中,而是插入进了模板,即为显示内容 -->
<user-profile [avatar]="user.avatar">
<user-details [user]="user"></user-details>
</user-profile>
组件的类型
组件就是一个类型,此处作者按照角色将组件分为四类
- App component -- root app组件,每个应用应该只有一个
- Display component -- 无状态组件,只是显示传进去的值,应该使其具有可重用性
- Data component -- 帮助数据从外部源引入应用
- Route component -- 每个路由会渲染一个组件,这使得组件在内部和路由联系起来
App component
App组件是一个特殊组件。每个Angular应用通过渲染一个组件开始,使用CLI,那么这个组件是AppComponent
,这里作者提供了几个建议
- Keep it simple -- 尽可能不在这里放任何逻辑,把它当作一个容器,易于重用并且可以优化其余部分
- Use for application layout scaffolding -- 模板是这个组件的重要部分
- Avoid loading data -- 通常不在这里加载数据,而是在需要数据的地方再加载数据
Display component
这些组件通常是用来渲染内容并展示的。很多第三方组件都是这个角色因为它是最容易解耦的组件。这里作者提了几个建议
- Decouple -- 保证这个组件不和其它组件耦合,除非有输入请求其它的数据
- Make it only as flexible as neccessary -- 避免让这些组件过于复杂,而是添加很多开箱即用的配置和选项
- Don't load data -- 总是通过input绑定来接收数据而不是动态加载数据(通过service)
- Have a clean API -- 接收input绑定的数据,发出需要绑定的事件(给其它组件)
- Optionally use a service for configuration -- 可以通过服务来设置应用默认配置
Data component
数据组件负责加载和管理数据。它主要是关于处理,拉取和接收数据的。通常它们需要一个服务来处理加载的数据。这里作者提了几个建议
- Use appropriate lifecycle hooks -- 要做初始化数据加载,总是利用最恰当的生命周期钩子函数
- Don't worry about reusability -- 这些组件不太可能重用,因为它们的目的比较特殊
- Set up display components -- 考虑加载的数据如何为display component所用,并且处理所有的用户输入
- Isolate business logic inside -- 存储应用的业务逻辑,因为管理数据就是要通过数据来实现某些特殊目的
Route component
路由组件时所有直接和路由联系到一起的组件。这些组件可重用性不强,通常是专为路由而创建。因为路由通常需要为新的view加载很多数据,所以它们遵循大多数data component的设计原则。作者区分两者的原因是,一个单一路由可以渲染出多个数据组件,这里作者提了几个建议
- Template scaffolding for the route -- 路由会渲染这个组件,所以这个路由逻辑会放在模板中
- Load data or rely on data components -- 根据路由的复杂度,路由组件可能会从路由中加载数据或者依赖一个或多个数据组件来获取数据。如果不确定该怎么做,那么建议从Route组件加载初始化数据,当你的view变得复杂时再解耦出来
- Handles route parameters -- 在你导航时,可能会有路由参数,这个component是处理这些参数最好的地方,它决定了后端要加载哪些数据
每个你能导航到的路由都关联着组件,所以路由组件的数量和与其关联的组件数量相同,你也可以通过不同的路由导航到相同的组件(并不常见)