ide*_*xer 8 observable rxjs ngrx angular2-observables angular
以下两个可观察映射之间有什么区别?
(如果以下代码中的某些内容对您来说很奇怪:它源于一个边做边学的爱好项目;我仍然学习RxJS)
我有一个带有getter和构造函数的组件.两者都从应用程序的ngrx存储中读取信息并提取字符串(name).
getter和构造函数之间的唯一区别: getter在HTML中使用,它返回的observable通过async管道发送,而构造函数中的observable映射由订阅使用完成subscribe.我希望它们都能像新的值一样频繁发射name.
但是相反,只有getter以这种方式工作,并async在HTML中提供管道,在其中使用新的name值(console.log('A')每个名称更改都会调用).该subscribe认购的回调只被调用一次console.log('B'),并console.log('B!')都调用一次,并永远不再.
如何解释这种行为差异?
我的组件的片段:
// getter works exactly as expected:
get name$(): Observable<string> {
console.log('getter called')
return this.store
.select(this.tableName, 'columns')
.do(_ => console.log('DO (A)', _))
.filter(_ => !!_)
.map(_ => _.find(_ => _.name === this.initialName))
.filter(_ => !!_)
.map(_ => {
console.log('A', _.name)
return _.name
})
}
// code in constructor seems to lose the subscription after the subscription's first call:
constructor(
@Inject(TablesStoreInjectionToken) readonly store: Store<TablesState>
) {
setTimeout(() => {
this.store
.select(this.tableName, 'columns')
.do(_ => console.log('DO (B)', _))
.filter(_ => !!_)
.map(_ => _.find(_ => _.name === this.initialName))
.filter(_ => !!_)
.map(_ => {
console.log('B', _.name)
return _.name
})
.subscribe(_ => console.log('B!', _))
})
}
Run Code Online (Sandbox Code Playgroud)
附加信息:如果我添加ngOnInit,在整个测试期间,这个生命周期钩子只被调用一次.如果我将订阅从构造函数移动到ngOnInit生命周期钩子,它不会比在构造函数中更好地工作.完全相同(意外)的行为.这同样适用于ngAfterViewInit和进一步的生命周期钩子.
名称输出更改'some-name' -> 'some-other-name' -> 'some-third-name' -> 'some-fourth-name' -> 'some-fifth-name':
[更新]正如Pace在评论中所建议的,我添加了getter通话记录
[更新] do按照佩斯的建议添加
getter called
DO (A) (3) [{…}, {…}, {…}]
A some-name
DO (B) (3) [{…}, {…}, {…}]
B some-name
B! some-name
getter called
DO (A) (3) [{…}, {…}, {…}]
A some-other-name
getter called
DO (A) (3) [{…}, {…}, {…}]
A some-third-name
getter called
DO (A) (3) [{…}, {…}, {…}]
A some-fourth-name
getter called
DO (A) (3) [{…}, {…}, {…}]
A some-fifth-name
Run Code Online (Sandbox Code Playgroud)
console.logs中dos 输出的输出示例内容:
[
{
"name": "some-name"
},
{
"name": "some-other-name"
},
{
"name": "some-third-name"
}
]
Run Code Online (Sandbox Code Playgroud)
看起来好像subscribe订阅在第一次调用后丢失了.但为什么?
max*_*992 10
你永远不应该使用这样的吸气剂.难道不是从一个getter返回可观察到的.
每次发生变化检测周期时,Angular都会一次又一次取消订阅/订阅(这种情况会发生很多).
就目前而言,我会为"变化检测"写"CD"
简单演示:
拿一个非常简单的组件:
// only here to mock a part of the store
const _obsSubject$ = new BehaviorSubject('name 1');
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
get obs$() {
return _obsSubject$
.asObservable()
.pipe(tap(x => console.log('getting a new value')));
}
randomFunction() {
// we don't care about that, it's just
// to trigger CD from the HTML template
}
}
Run Code Online (Sandbox Code Playgroud)
您将getting a new value在控制台中看到,每次单击"单击以触发更改检测"按钮(其中已(click)注册事件),它将触发新的CD周期.
而且,当你点击那个按钮时,你会看到你得到两次getting a new value.(两次是因为我们不处于生产模式,Angular执行2个CD周期以确保变量在第一次和第二次更改检测之间没有变化,这可能会导致问题,但这是另一个故事).
可观察的一点是,它可以长时间保持开放,你应该利用它.为了重构前面的代码以保持订阅打开并避免再次取消订阅/订阅,我们可以摆脱getter并声明一个公共变量(可由模板访问):
// only here to mock a part of the store
const _obsSubject$ = new BehaviorSubject('name 1');
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
obs$ = _obsSubject$
.asObservable()
.pipe(tap(x => console.log('getting a new value')));
randomFunction() {
// we don't care about that, it's just
// to trigger CD from the HTML template
}
}
Run Code Online (Sandbox Code Playgroud)
现在,无论您点击按钮多少次,您都会看到一个且只有一个getting a new value(直到observable发出新值),但更改检测不会触发新订阅.
这是Stackblitz的现场演示,所以你可以玩,看看console.log发生的事情=)
https://stackblitz.com/edit/angular-e42ilu
编辑:
A getter是一个函数,因此,Angular必须在每张CD上调用它来检查是否有来自它的新值应该在视图中更新.这花费了很多,但它是框架的原理和"魔力".这也是为什么你应该避免在可能在每张CD上触发的功能中运行密集CPU任务的原因.如果它是一个纯函数(相同的输入相同输出并且没有副作用),请使用管道,因为默认情况下它们被认为是"纯"并缓存结果.对于相同的参数,它们只在管道中运行一次函数,缓存结果,然后立即返回结果而不再运行该函数.
ngrx.select()仅当存储中的数据发生更改时,返回的 Observable才会触发。
如果您希望 Observable 在initialName更改时触发,那么我建议将 initialName 转换为 RXJSSubject并使用combineLatest:
initialNameSubject = new BehaviorSubject<string>('some-name');
constructor(
@Inject(TablesStoreInjectionToken) readonly store: Store<TablesState>
) {
setTimeout(() => {
this.store
.select(this.tableName, 'columns')
.combineLatest(this.initialNameSubject)
.map(([items, initialName]) => items.find(_ => _.name === initialName))
.filter(_ => !!_)
.map(_ => {
console.log('B', _.name)
return _.name
})
.subscribe(_ => console.log('B!', _))
})
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1457 次 |
| 最近记录: |