其实是装了个逼,Angular Reactive Extention 其实就是 NgRx。之前一直嘲笑,NgRx 代表着 Angular Redux, 而不是官方的 Reative Extension。直到看到了 NgRx/Component,才开始有点信服。最近看到了, NgRx/componet-store, 特别是 effect 函数,简直感觉忽然解决了我所有的问题。
之前我们提过,Angular 虽然很早就已经引入了 RxJS,但是,有些本身就是一个 obserble 的东西却迟迟没有好的支持。比如:@input, 和 event.
如果我们把 component 看作是一个 HTML 生成函数的话,它的变化因素只有两个,input 和 event,input 我们一直没有一个很优雅的 change detect 方式,而 event, 我们不得不写一堆,onEvent 函数将逻辑支离破碎的拼装到一起,我们来看下下面的例子:
@Input() userId: string;
ngOnInit(): void {
this.users$ = this.service.getUser(this.userId);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.userId) {
this.users$ = this.service.getUser(this.userId);
}
}
我们可以看到,这种基于周期函数的方法,使得我们不得不把逻辑拆分到不同的周期函数里,大量的相同的操作需要按照周期函数调用的方式,重新思考。造成冗余。
有没有可能,把 input 变成一个 observable 呢?可以的:
private userId$ = new SubjectReplay(1);
@Input()
set userId(val: string) {
this.userId$.next(val);
}
ngOnInit(): void {
this.users$ = this.userId$.pipe(
switchMap(userId => this.service.getUser(userId))
);
}
这样写,就有效的避免了相同的逻辑,需要在不同的周期函数里重复调用的问题,有效的将相同的概念,组合到了一起。
但是还是有两个问题:
- 我们不得不定义两个含义重复的 property,
userId$,userId, 甚至发现,名字都已经不太好取。 - 本质上我们没有办法阻止,
userId$.next在其他地方调用,造成他实现了超出原本含义的操作。
对于 @Input 函数来说,本质上,每一次的修改都是一次 set 函数的调用,有没有有效的将,set 函数,转换为 observable 的方法呢。其实说白了也很简单,因为,每一次 observable 的封装都是类似的,我们是否可以通过一个公共方法来实现,这样既减少了额外 obserble 的定义,又可以有效的避免恶意调用。将函数的每一次调用,封装成一个 observable,很容易想到闭包:
function createEffect() {
const mySubject = new Subject();
return function(val: any) {
mySubject.next(val);
}
}
使用起来呢,也很方便:
set userId(val: string) {
this.updateUser(userId);
}
updateUserId = createEffect();
这样一来,userId 的每次变化都会有效的记录在 updateUserId 中对应的 observable 里面,但是聪明的你可能发现了,虽然变化被记录了,但是,我们却没有办法做相应的操作,这里就有两个思路:
- 把 observable 暴露出来
- 传入 callback 函数
我们先来看第二种思路:
function createEffect(callback: (obse: observable<any> => void)) {
const mySubject = new Subject();
callback(mySubject.asObservbale());
return function(val: any) {
mySubject.next(val);
}
}
//.....
//.....
//.....
set userId(val: string) {
this.updateUser(userId);
}
updateUserId = createEffect((userId$) => {
this.userId$.pipe(
switchMap(userId => this.service.getUser(userId)),
tap(user => {
// xxxxxxxx
}),
);
});
假设,这时候,洁面上还有一个按钮可以修改 userId, 并且 call service:
<button (click)="updateUserId(userId)"></button>
这样机遇 feature 而不是 event 或者 lifecycle event 的函数就可以最大可能的实现逻辑复用。
还有第一种思路我们还没说,我们接下来一起看一下:
function createEffect(defaultValue) {
const mySubject = new BehaviorSubject(defaultValue);
const update = function(val: any) {
mySubject.next(val);
}
return ([
mySubject.getValue(),
update,
]);
}
const [userId, updateUserId] = createEffect('zhangsan');
不用我说,你也知道,这是啥了。。。
不管怎么样,你会发现,React,Vue, Angular 可能都在往类似的 observable 的方向上走。只是方式不同罢了。