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 Низет

Большое спасибо за ваш вклад :)
Джеспер

Ответы:


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.


10

Это довольно самоуверенный ответ.

Использование таких функций вполне приемлемо. Это сделает шаблоны намного понятнее и не вызовет значительных накладных расходов. Как сказал ранее 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>

Спасибо за ваш ответ, очень проницательный. Однако в нашем конкретном случае изменение стратегии обнаружения не является вариантом, так как рассматриваемый компонент выполняет запрос get, и, следовательно, изменение связано не с конкретным входом, а с запросом get. Тем не менее, это очень полезная информация для разработки будущих компонентов, где изменение зависит от входных переменных
Jesper

1
@Jesper, если компонент выполняет запрос get, то у вас уже есть Observableпоток, что сделает его идеальным кандидатом для второго варианта, который я показал. В любом случае, рад, что я мог дать вам некоторые идеи
Пол 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;
   }
})
);

Затем вы можете использовать это в наблюдаемом асинхронном канале в вашем коде.


2
Привет, просто хотел отметить, что я считаю, что предложение об использовании переменной серьезно вводит в заблуждение. Переменная не будет обновлять значение при изменении любого из комбинированных значений
nsndvd

1
И независимо от того, находится ли выражение непосредственно в шаблоне или возвращено функцией, оно должно быть оценено при каждом обнаружении изменений.
JB Низет

Да, это правда извините за то, что не делает плохих практик
Энтони Уиллис Муньос

@ anthonywillismuñoz Итак, как бы вы подошли к такой ситуации? Просто жить с множеством, трудно читаемых условий в * ngIf?
Джеспер

1
это зависит от вашей ситуации, у вас есть несколько вариантов в средней должности. Но я думаю, что вы используете наблюдаемые. Будет редактировать пост с примером, чтобы уменьшить условие. если вы можете показать мне, откуда у вас условия.
Энтони Уиллис Муньос

0

Я думаю, что JavaScript был создан с целью, чтобы разработчик не заметил разницу между выражением и вызовом функции в отношении производительности.

В C ++ есть ключевое слово inlineдля обозначения функции. Например:

inline bool userCheck()
{
    return isAuthorized;
}

Это было сделано для того, чтобы исключить вызов функции. В результате компилятор заменяет все вызовы userCheckна тело функции. Причина для инноваций inline? Повышение производительности.

Поэтому я думаю, что время выполнения вызова функции с одним выражением, вероятно, медленнее, чем выполнение только выражения. Но я также думаю, что вы не заметите разницы в производительности, если у вас есть только одно выражение в функции.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.