【问题标题】:Angular - *ngIf vs simple function calls in templateAngular - *ngIf 与模板中的简单函数调用
【发布时间】:2020-01-17 09:48:25
【问题描述】:

很抱歉,如果这里已经回答了这个问题,但我找不到适合我们特定场景的任何匹配项,所以就这样吧!

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

假设我们有一个封装在 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;
}

所以总结一下这个问题,最后一个选项是否有任何显着的性能成本?

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

【问题讨论】:

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

标签: angular typescript


【解决方案1】:

我也尽量避免在模板中调用函数,但你的问题启发了我做一个快速的研究:

我添加了另一个缓存 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)肯定会有不同。

【讨论】:

    【解决方案2】:

    这是一个相当自以为是的答案。

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

    我还认为,无论您在模板中使用什么表达式,更改检测机制都会将其评估为函数,因此无论您是在模板中还是在组件逻辑中都有它都没有关系。

    只需将函数内部的逻辑保持在最低限度。但是,如果您对此类功能可能产生的任何性能影响持谨慎态度,我强烈建议您将ChangeDetectionStrategy 设置为OnPush,这无论如何都被认为是最佳实践。有了这个,函数不会在每个周期都被调用,只有当 Input 发生变化,模板内部发生某些事件等时。

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


    我个人认为使用 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,如果组件的所有因变量都是输入,并且您有很多逻辑要计算某个模板变量(您展示的情况并非如此):

    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 如果组件执行了一个 get 请求,那么你已经有了一个 Observable 流,这将使它成为我展示的第二个选项的完美候选者。不管怎样,很高兴我能给你一些见解
    【解决方案3】:

    不推荐校长的原因很多:

    为了判断是否需要重新渲染 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。

    【讨论】:

    • 嗨,我只是想指出,我发现使用变量的建议具有严重的误导性。当任何组合值发生变化时,变量不会更新为值
    • 无论表达式是直接在模板中,还是由函数返回,都必须在每次更改检测时对其进行评估。
    • 是的,真的很抱歉会因为不做坏事而编辑
    • 这取决于您的情况,您在中型帖子中有一些选择。但我认为你正在使用 observables。将使用示例编辑帖子以减少条件。如果你能告诉我你从哪里得到条件。
    • 让它工作,让它正确,让它快速。始终首先优化下一个将阅读您的代码的人,而不是将运行它的机器。你真的有性能问题吗?不?使用函数,它们使您的代码更简单,更具表现力。是的?查看此处提出的一些替代方案。
    【解决方案4】:

    我认为创建 JavaScript 的目的是让开发人员不会注意到表达式和函数调用在性能方面的区别。

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

    inline bool userCheck()
    {
        return isAuthorized;
    }
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-04
      • 2016-09-03
      相关资源
      最近更新 更多