Кутовий - * 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;
}

Отже, підсумовуючи питання, чи мав би останній варіант суттєву вартість продуктивності?

Ми вважаємо за краще використовувати другий підхід у ситуаціях, коли нам потрібно перевірити більше ніж 2 умови, але багато статей в Інтернеті кажуть, що виклики функцій ЗАВЖДИ погані в шаблонах, але чи справді це проблема в цьому випадку?


7
Ні, не буде. І він ще більш чистий, оскільки він робить шаблон більш читабельним, стан легше перевіряти і використовувати багаторазово, і у вас є більше інструментів (уся мова TypeScript), щоб зробити його максимально зручним для читання та ефективності. Я хотів би вибрати набагато чіткіше ім'я, ніж "userCheck".
JB Nizet

Дякую за ваш внесок :)
Jesper

Відповіді:


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, якщо всі залежні змінні компонента є 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>

Дякую за вашу відповідь, дуже проникливий. У нашому конкретному випадку зміна стратегії виявлення не є варіантом, оскільки відповідний компонент виконує запит на отримання, а отже, зміна пов'язана не з конкретним входом, а швидше із запитом get. Тим не менш, це дуже корисна інформація для розробки майбутніх компонентів, коли зміна залежить від вхідних змінних
Jesper

1
@Jesper, якщо компонент виконує запит на отримання, тоді у вас вже є 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;
   }
})
);

Тоді ви можете використовувати це спостережуване з асинхронною трубкою у вашому коді.


2
Привіт, я просто хотів зазначити, що я вважаю, що пропозиція використання змінної є серйозною оманливою .. Змінна не буде оновлена ​​- це значення, коли зміниться будь-яке з об'єднаних значень
nsndvd

1
І чи вираз знаходиться безпосередньо в шаблоні, чи повертається функцією, його потрібно буде оцінювати при кожному виявленні змін.
JB Nizet

Так, його справжнє вибачення буде відредаговано за те, що він не зробив поганих практик
Ентоні willis muñoz

@ anthonywillismuñoz Тож як би ви підходили до такої ситуації? Просто жити з кількома, важко читаються умовами у * ngIf?
Джеспер

1
це залежить від вашої ситуації, у вас є деякі варіанти середньої посади. Але я думаю, що ви використовуєте спостережні дані. Відредагуйте публікацію на прикладі, щоб зменшити умову. якщо ти можеш показати мені, звідки ти отримуєш умови.
Anthony willis muñoz

0

Я думаю, що JavaScript був створений з ціллю, щоб розробник не помічав різниці між експресією та викликом функції щодо продуктивності.

У C ++ є ключове слово inlineдля позначення функції. Наприклад:

inline bool userCheck()
{
    return isAuthorized;
}

Це було зроблено для усунення виклику функції. В результаті компілятор замінює всі виклики userCheckз тілом функції. Причина інновацій inline? Підвищення продуктивності.

Тому я думаю, що час виконання виклику функції з одним виразом, ймовірно, повільніше, ніж виконання лише експресії. Але я також думаю, що ви не помітите різниці в продуктивності, якщо у вас є лише один вираз у функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.