函数调用模板还是预先计算所需的值



我读到模板中的函数调用效率不高,因为Angular的默认更改检测每次都会执行该函数。如果使用OnPush策略?我使用NGXS进行状态管理,并使用哑/智能组件/容器的概念。我应该对对象进行预处理,使它们已经具有所需的值吗?这里有一个例子:

<div *ngFor="let object of objects">
<input type="checkbox" [checked]="isObjectChecked(object)">
</div>
isObjectChecked(object){
// if object is in the array, then check the checkbox
return selectedObjects.some(obj => obj.id === object.id)
}

由于我使用的是NGXS,所以我使用@Input()objects从容器传递到哑组件。这种方法更好吗:

// in container 
ngOnInit(){
this.objectsWithIsCheckedProperty$ =  combineLatest([this.store.select(ObjectsState.objects), this.store.select(ObjectsState.selectedObjects)])
.pipe(
map(data => {
let objects = data[0];
let selectedObjects = data[1];
objects.forEach(obj => {
obj.isChecked = selectedObjects.some(object => obj.id === object.id);
});
return objects;
})
)
}

然后使用模板中的CCD_ 4值

<div *ngFor="let object of objects">
<input type="checkbox" [checked]="object.isChecked">
</div>

如果我在模板中的函数调用中使用OnPush更改检测策略,会有什么不同吗?因为我必须明确告诉Angular应该检查变量是否发生了更改?

备选方案1:模板中的函数

我强烈建议在绑定到模板时不要使用函数。即使您使用ChangeDetectionStrategy.OnPush,只要组件上触发了更改检测周期,这些函数就会被重新评估(例如,当任何@Input更改时,组件上触发您列出的任何事件,组件上的异步任务完成时(。

例如,假设您有以下组件:

@Component({
template: `
<span>{{ myBoundFunc() }}</span>
<button (click)="doSomething()"
`
})
export class MyComponent {
myBoundFunc() {
console.log('Bound function called');
}
doSomething() {}
}

现在,如果你点击按钮,myBoundFunc也会被调用,因为点击按钮会触发一个变化检测周期——myBoundFunc的结果可能会因为你的点击而改变。

在本例中,这在性能方面可能还可以,但这是一种糟糕的做法,如果您在该函数中执行要求更高的操作,则会导致性能问题。

备选方案2:预先计算Observable中的值

这是一个很好的解决方案,因为只有当底层可观察对象接收到新值时,您的逻辑才会运行。就性能而言,这比备选方案1要好得多。我仍然建议用一种不可变的方式来做这件事,因为这与Angular的更改检测(只检查引用相等(更有效。

我会把你的示例解决方案改成这样,使其不可变:

ngOnInit() {
this.objectsWithIsCheckedProperty$ = combineLatest([
this.store.select(ObjectsState.objects),
this.store.select(ObjectsState.selectedObjects)
]).pipe(
map(([objects, selectedObjects]) => 
// Use .map and spread the obj into a new object, adding the isChecked property in the process
objects.map(obj => ({
...obj,
isChecked: selectedObjects.some(({id}) => obj.id === id)
}))
)
)
}

备选方案3:在Selector中预先计算NGXS中的值

另一种预先计算值的方法是使用NGXS@Selector,它还将逻辑传输到您的状态,使其在应用程序中可重复使用。就性能而言,这应该与备选方案2大致相同。

状态:

@Injectable()
@State<ObjectStateModel>({
name: 'object'
})
export class ObjectState {
// These 2 already seem to exist in your state
@Selector() public static objects(state: ObjectStateModel) {}
@Selector() public static selectedObjects(state: ObjectStateModel) {}
// Or with "selectorOptions.injectContainerState = false"
@Selector([ObjectState]) public static objects(state: ObjectStateModel) {}
@Selector([ObjectState]) public static selectedObjects(state: ObjectStateModel) {}
// The new selector that does the precalculation. Depending on if you have
// "selectorOptions.injectContainerState" set to true or false, the first
// parameter will always be the state or not. I'll add both here.
// With "selectorOptions.injectContainerState = true" (default)
@Selector([ObjectState.objects, ObjectState.selectedObjects])
public static objectsWithIsCheckedProperty(state, objects, selectedObjects) {
return objects.map(obj => ({
...obj,
isChecked: selectedObjects.some(({id}) => obj.id === id)
}))
}
// With "selectorOptions.injectContainerState = false" (recommended)
@Selector([ObjectState.objects, ObjectState.selectedObjects])
// Notice that "state" is not provided by default anymore
public static objectsWithIsCheckedProperty(objects, selectedObjects) {
return objects.map(obj => ({
...obj,
isChecked: selectedObjects.some(({id}) => obj.id === id)
}))
} 
}

在您的组件中:

ngOnInit() {
this.objectsWithIsCheckedProperty$ = this.store.select(ObjectState.objectsWithIsCheckedProperty);
}

最新更新