Angular-* ngIf与模板中的简单函数调用


14

抱歉,如果在这里已经回答了这个问题,但是我找不到适合我们特定情况的匹配项,那就去吧!

我们在开发团队中就角度模板中的函数调用进行了讨论。现在,作为一般经验法则,我们同意您不应执行这些操作。但是,我们试图讨论什么时候可以。让我给你一个场景。

假设我们有一个包装在ngIf中的模板块,它检查多个参数,例如:

<ng-template *ngIf="user && user.name && isAuthorized">
 ...
</ng-template>

与以下内容相比,性能会有明显差异:

模板:

<ng-template *ngIf="userCheck()">
 ...
</ng-template>

打字稿:

userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

因此,总而言之,最后一个选择是否会产生重大的性能成本?

在需要检查两个以上条件的情况下,我们宁愿使用第二种方法,但是在线上有许多文章说函数调用在模板中总是不好的,但是在这种情况下真的有问题吗?


7
不,不会。而且它也更干净,因为它使模板更具可读性,条件更易于测试和重用,并且您可以使用更多工具(整个TypeScript语言)来使其尽可能地可读和高效。我会选择比“ userCheck”更清晰的名称。
JB Nizet

非常感谢您的输入:)
Jesper

Answers:


8

我还尝试尽可能避免模板中的函数调用,但是您的问题启发了我进行快速研究:

我在缓存userCheck()结果中添加了另一种情况

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

在这里准备了一个演示:https : //stackblitz.com/edit/angular-9qgsm9

令人惊讶的是,两者之间似乎没有区别

*ngIf="user && user.name && isAuthorized"

*ngIf="userCheck()"

...
// .ts
userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

看起来对简单的属性检查是有效的,但是如果涉及到任何async操作(例如,正在等待某些api的getter),则肯定会有区别。


10

这是一个很自以为是的答案。

这样的功能的使用是完全可以接受的。这将使模板更加清晰,并且不会造成任何重大开销。就像JB之前说过的那样,它将为单元测试奠定更好的基础。

我还认为,模板中包含的任何表达式都将被更改检测机制评估为一个函数,因此,无论模板中或组件逻辑中是否包含表达式,都没有关系。

只需将函数内部的逻辑保持在最低限度即可。但是,如果您对这种功能可能会对性能造成的影响保持警惕,我强烈建议您将ChangeDetectionStrategy设为OnPush,这被认为是最佳做法。有了这个,该函数将不会在每个周期都被调用,只有在Input模板内部发生更改,某些事件等时才被调用。

(使用etc,因为我不知道其他原因了)


就个人而言,我认为使用Observables模式更好,然后可以使用async管道,并且仅当发出新值时,才对模板进行重新评估:

userIsAuthorized$ = combineLatest([
  this.user$,
  this.isAuthorized$
]).pipe(
  map(([ user, authorized ]) => !!user && !!user.name && authorized),
  shareReplay({ refCount: true, bufferSize: 1 })
);

然后,您可以像这样在模板中使用:

<ng-template *ngIf="userIsAuthorized$ | async">
 ...
</ng-template>

ngOnChanges如果组件的所有因变量都是Inputs,并且还有很多逻辑要计算某个模板变量(这不是您所显示的情况),则可以使用另一种选择:

export class UserComponent implements ngOnChanges {
  userIsAuthorized: boolean = false;

  @Input()
  user?: any;

  @Input()
  isAuthorized?: boolean;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.user || changes.isAuthorized) {
      this.userIsAuthorized = this.userCheck();
    }
  }

  userCheck(): boolean {
    return this.user && this.user.name && this.isAuthorized || false;
  }
}

您可以在模板中使用以下代码:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>

感谢您的回复,非常有见地。但是,对于我们的特定情况,更改检测策略不是一种选择,因为有问题的组件执行获取请求,因此更改与特定输入无关,而与获取请求有关。尽管如此,这对于开发未来组件非常有用的信息,因为这些组件的变化取决于输入变量
Jesper

1
@Jesper如果组件执行get请求,那么您已经有了一个Observable流,这将使其成为我展示的第二个选项的理想选择。无论哪种方式,很高兴我能为您提供一些见解
Poul Kruijt

6

出于多种原因,不建议使用以下原则:

为了确定是否需要重新渲染userCheck(),Angular需要执行userCheck()表达式以检查其返回值是否已更改。

由于Angular无法预测userCheck()的返回值是否已更改,因此每次运行更改检测时都需要执行该函数。

因此,如果更改检测运行300次,则该函数将被调用300次,即使其返回值从不更改。

扩展说明和更多问题https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

如果您的组成部分很大并且参加了许多更改事件,而您的组成部分很少并且仅参加一些事件,那么问题就不成问题了。

可观察的例子

user$;
isAuth$
userCheck$;

userCheck$ = user$.pipe(
switchMap((user) => {
    return forkJoin([of(user), isAuth$]);
 }
)
.map(([user, isAuthenticated])=>{
   if(user && user.name && isAuthenticated){
     return true;
   } else {
     return false;
   }
})
);

然后,可以在代码中使用带异步管道的observable。


2
嗨,我只想指出,我发现使用变量的建议会严重误导人。.当任何组合值发生变化时,变量将不会更新为值
nsndvd

1
无论表达式是直接在模板中还是由函数返回,都必须在每次更改检测时对其进行评估。
JB Nizet

是的,它的真正抱歉将编辑为没有不良作法
安东尼

@anthonywillismuñoz那么,您将如何应对这种情况?只是在* ngIf?中承受着多个难以理解的条件?
Jesper

1
这取决于您的情况,您在中间帖子中可以有一些选择。但是我认为您正在使用可观察的东西。将用示例来编辑帖子以减少条件。如果您可以告诉我您从何处获得条件。
安东尼

0

我认为JavaScript的创建是有目标的,以便开发人员不会注意到关于性能的表达式和函数调用之间的区别。

在C ++中,有一个关键字inline来标记函数。例如:

inline bool userCheck()
{
    return isAuthorized;
}

这样做是为了消除函数调用。结果,编译器将所有调用替换userCheck为该函数的主体。创新的原因inline?性能提升。

因此,我认为使用一个表达式进行函数调用的执行时间可能比仅执行表达式要慢。但是,我也认为,如果函数中只有一个表达式,您不会注意到性能的差异。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.