angular框架中的一些原理

使用angular开发项目许久,几乎没有深究过一些框架中的基本原理,记录一下。

实现数据双向绑定的原理

使用的时候是在模板中通过[()],进行绑定(分别绑定事件和属性)。数据变化后,视图会自动更新,无需手动操作dom。但没有了解过机制。
angularJs 的数据双向绑定,是通过脏检查来比较新旧状态。
但当项目很大的时候,维护的变量过多会有严重的性能问题
vue 通过 数据劫持 + 发布订阅模式。
angular 通过 change detection。 (相较于angularJS的循环脏检查,CD的单项数据流要高效许多)

检测的时机?
在一些异步操作的情况中, 比如
用户的鼠标键盘事件、定时器的回调、XHR的请求
这些异步的操作都很有可能会导致Model变化。
angular 里的zone.js, 经过封装后叫ngZone,
它会自动的对这些任务进行追踪更新,当数据变更时,调用tick()
通知angular:
哪些model变了,哪里的view需要更新。

每个组件都有自己的变化检测器,即一个 Angular 应用程序由一个组件树组成,
所以我们也有一个变化检测器树。
变化检测总是从上到下,对每一个单独的组件进行,单向数据流比循环脏检查更高效,并且可预测,
总是可以知道视图中使用的数据来自哪里。避免一些难以察觉到的bug。

变更策略的优化?
Onpush
默认情况下(ChangeDetectionStrategy.Default),也就是父组件的 CD 会触发子组件的 CD。
有时可以自行判断出: 在父组件 CD 时, 某些子组件并不用触发。
ng-zorro 就使用了 OnPush 策略。
这也是 Angular 性能优化的方法之一

MVVM的理解

M表示: Model代表数据模型,在Model中定义数据修改和操作的业务逻辑。
V表示:View 代表template,即用户看到的视图
VM表示:
ViewModel监听模型数据的改变和控制视图行为,是一个同步View 和 Model的对象,连接Model和View
在MVVM架构下,View和 Model 之间并没有直接的联系,而是通过ViewModel进行交互
ViewModel 通过双向数据绑定把 View 层和 Model层连接了起来,
而View和 Model 之间的同步工作完全是自动的,无需人为干涉,
因此只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态如何同步,复杂的数据状态维护交给框架处理

状态管理

angular提供的service,以依赖注入的方式注册在providers数组中,我们使用service的时候,不需要通过New 来实例化,只需要通过依赖关系来注入。
在应用中所有组件操作的是同一个service实例,所以其中的数据是共享的。
在项目中,根据业务场景,大多的需求是: 编辑产品后,在订单页面、产品列表及交易记录这几个页面中。
所以使用的是 rxjs + service 来进行 状态管理(处理异步数据流 和 数据共享)。

NGRX

avatar

更复杂更大型的状态管理可使用ngrx。
视图层通过dispatch发起一个行为(action)=> Reducer接收action,根据action.type类型来判断执行、改变状态 =>返回一个新的状态给store => 由store更新state。

State(状态) 是状态,通过store 来访问

Action(行为) 描述状态的变化,它发送数据到reducer,然后reducer更新store。
Action({type:xx,payload:xx})
这也是store能接受数据的唯一方式。

Reducer(归约器)
规定了action对应的具体状态变化,是纯函数。通过接收先前状态和当前action,返回新对象作为下一个状态。
新对象通常用Object.assign和扩展语法来实现。

store 是state的observable(可观察对象),以及action的observer(观察者)。

实际开发过程中,会涉及 API 请求、浏览器存储等异步操作,
需要 effects 和 services.
effects 由 action 触发,进行一些逻辑后,发出一个或者多个需要添加到队列的 action,再由 reducers 处理。

rxjs

在项目中用来管理异步数据流的Rxjs库中,经常使用到Subject。
普通的 Observable 只是数据生产者,发送数据。
而 Subject 是Observable(产生数据的一方)又是Observer(接收数据的一方)。

tips:
我们经常用 Subject 在多个 component 中共享数据。
例如,在两个 component 中通过定义在 ServiceA 的 Subject 共享数据,通过 next() 和 asObservable() 实现。

Observable Subject BehaviorSubject AsyncSubject ReplaySubject
just 数据产生者 both both both both
单播 多播 多播 多播 多播
每次从头开始把值发给观察者 将值多播给订阅该 Subject 的观察者们 把当前值发送给观察者(需要初始值) 执行的最后一个值发送给观察者相当于last() 可以把之前的值发送给观察者(错过的值)

用来处理异步数据流的Observable 和 Promise 适用场景

Promise: 返回单个值; 不可取消
Observable: 返回0 或 多个; 可以取消订阅; 支持map,filter,reduce等很多操作符

如果服务器的HTTP请求结果或其它一些异步操作不再需要,
Observable的订阅者可以取消订阅,
unsubscribe()
如 NgRx Effects可以从程序中删除最后的显式 .subscribe()调用
是独立实现的,由库自动订阅。

而Promise将最终调用成功或失败的回调.

Observable通过 toPromise() convert to Promise

综上,Observable 进行 数据流管理更加方便

针对angular 项目的性能优化思路

懒加载策略

路由配置loadChildren。
不要把所有的组件一股脑的、全部都在appModule里import和declare
而是在自己的ngModule中declarations并exports,谁需要使用它,就引入这个组件的对应的xxMoule

AOT(ahead of time)编译

采用运行前编译,可以避免运行时编译造成的性能消耗和内存消耗,显著加快程序的启动

webpack打包时进行 uglify和tree shaking

即时移除不再使用的第三方库

清理不必要的 import 语句

dependencies 和 dev-dependencies 需要明确分离

angualr 为service 注册provider 的三种方式

一、 在服务自己实现代码里使用注解 @Injectable()

全局单例
@Injectable({"providedIn": "root"})
特点: 打包时会进行tree shaking

二、 在 @NgModule() 里注册

在NgModule 注册提供者时,
通过 @NgModule() 装饰器中的 providers 属性来注册
该服务的同一个实例将会对该 NgModule 中的所有组件可用。
特点:
一旦创建,服务的实例就会存在于应用的全部生存期中。
可注入到应用中的很多地方,且每次注入的都是同一个服务实例。

1
2
3
4
5
6
@ngModule({
providers: [
itemService
]
})

三、在 @Component() 里注册

多级注入器:

@Component({
    selector: '<demo></demo>',
    templateUrl:"xx",
    providers: [itemService]
})

访问权限定: 该服务只在 当前组件 及其子组件树中可用。 访问范围内仍然是单例。
但每个组件的实例都有它自己的注入器。
在组件级提供服务,可以确保组件的每个实例都得到一个私有的服务实例。
可用于场景: 多重编辑会话; 服务隔离

特点: 组件的每个实例都会有它自己的服务实例。当组件实例被销毁的时候,服务的实例也同样会被销毁。

查看评论