Месяц назад друг спросил меня, почему метод в представлении компонента Angular 2 запускается так много раз.

1- История:

Хорошо, давайте начнем с самого начала, я один из главных ответственных за встречу Angular и ROR в Мадриде (я часть Angular… У меня нет опыта работы с ROR). В этой группе мы обычно собираемся каждую пятницу, куда приходят люди, которые хотят изучить Angularjs или Angular 2, чтобы поделиться своими знаниями. Один из этих людей пытался написать новую библиотеку интернационализации для своего приложения. Он хорошо поработал, я имею в виду, он создал службу, которая получает ключ и возвращает перевод на языке буквального значения, связанного с этим ключом. Кроме того, его служба принимает переменные и может быть настроена на перевод слов во множественном числе с одним и тем же ключом. Я знаю, что в Angular 2 есть несколько библиотек для интернационализации, но мой друг этого не знал, и для новичка в Angular 2 я считаю, что он проделал замечательную работу. Вот ссылка на репозиторий github:

Https://github.com/carlospalacin/ng-trans

Проблема в том, что он вызывает методы этой службы в представлении компонента и понимает, что каждый метод запускается много раз при визуализации представления.

Пойдем, узнаем подробности. Так выглядит класс главного компонента:

@Component({
    moduleId: module.id,
    selector: 'pulsar-app',
    templateUrl: 'app.component.html'
})
export class AppComponent {
private var01: string = "default value";
    
constructor( private trans: TranslatorService ){}
testRender() {
   console.log('trace 01');
   return 'This is a test that runs a console log'
 }
 (...)
}

Сосредоточимся на методе testRender. Это очень простой метод. Он только печатает трассировку в консоли браузера и возвращает строку. Этот метод вызывается в представлении следующим образом:

<h2>Traces:</h2>
{{ testRender()}}

Вопрос в том, сколько раз запускается метод, когда мы посещаем веб-страницу, на которой отображается компонент? Ответ 12 раз:

2- Расследование

Я уверен, что многие из вас спрашивают то же самое, что я спрашивал в первый раз, когда увидел его код: почему вы не использовали каналы вместо методов? Как я уже сказал, этот парень новичок в Angular 2. Совершать ошибки, когда вы что-то изучаете, - это нормально, но факт в том, что, согласно документации Angular 2, использование методов в представлении - неплохая практика:

Https://angular.io/docs/ts/latest/guide/template-syntax.html

Очевидно, мой друг не делает ничего плохого, но есть проблема, слишком много ненужного выполнения метода. Что здесь происходит? В то время я не знал, но подозревал, что ответ заключается в жизненном цикле компонента, поэтому я сделал форк его репозитория и реализовал метод для каждого крючка жизненного цикла компонента. В этих методах я помещаю console.log, чтобы увидеть, на каком этапе печатается трассировка «testRender». Так выглядит новый компонент:

export class AppComponent implements 
OnChanges,
OnInit,
DoCheck,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked,
OnDestroy
{
  private var01: string = "default value";
  constructor( private trans: TranslatorService ){}
  ngOnChanges (){
     console.log('Trace OnChanges');
  }
  ngOnInit (){
     console.log('Trace onInit');
  }
  ngDoCheck (){
     console.log('Trace doCheck');
  }
  ngAfterContentInit(){
     console.log('Trace After Content Init');
  }
  ngAfterContentChecked(){
     console.log('Trace after contente checked');
  }
  ngAfterViewInit(){
     console.log('Trace after view init');
  }
  ngAfterViewChecked(){
     console.log('Trace after view checked');
  }
  ngOnDestroy(){
     console.log('Trace on destroy');
  }
  testRender() {
    console.log('trace 01');
    return 'This is a test that runs a console log'
  }
  (...)
}

И журналы консоли, которые я получил в браузере, следующие:

3- Объяснение

Чтобы глубже понять, что здесь происходит, я рекомендую прочитать официальную документацию Angular 2 о жизненном цикле (https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html). Однако, не теряя своей аргументации, я попытаюсь резюмировать ее.

По сути, метод запускается в хуке doCheck. Я знаю, что в журналах говорится, что выполнение выполняется в хуках afterContentChecked и afterViewChecked, но эти два зависят от doCheck, как показано на диаграмме официальной документации:

Таким образом, мы можем сделать вывод, что метод doCheck запускается 12 раз.

Что делает проверка? Что ж, этот хук проверяет, обновляется ли представление при изменении некоторого значения. Так делает ли он то же самое, что и onChanges? Нет, это не так. OnChanges обновляет значение в представлении каждый раз, когда оно изменяется. Позвольте мне объяснить:

Представьте, что у вас есть что-то вроде этого в представлении:

{{имя}}

Каждый раз, когда значение name, которое находится между усами, изменяется, ловушка onChanges обновляет представление новым значением, поскольку обнаруживает изменение. Тем не менее, если мы поместим в представление атрибут объекта вроде этого:

{{user.name}}

OnChanges не может определить, когда значение user.name изменяется, потому что значение между усами является адресом памяти. Неважно, сколько раз содержимое памяти изменяется, ссылка на нее остается прежней. Итак, это причина doCheck; в каждом событии в представлении, потому что именно в событиях, когда происходят изменения, проверяет все ссылки на память на всякий случай, их содержимое необходимо обновить.

Теперь вернемся к нашему примеру, что такое метод в JavaScript? Это объект, адрес памяти. Это тот же случай, что и в предыдущем примере.

5- Заключение

Так это хорошая практика - вызывать методы в представлениях? Я бы сказал нет, но я не эксперт по Angular 2 (я работаю с Angular 2 только с января 2016 года). Давайте в другой раз проверим официальную документацию; где мы можем найти подобное поведение в Angular 2? В нечистых трубах.

Если я напишу трубку вот так:

@Pipe({name: 'message', pure:false})
export class MessagePipe implements PipeTransform {
    transform(msg:string):string {
        console.log('Run the pipe');
        return msg;
    }
}

И я изменяю вызов метода testRender в представлении для этого канала:

<h2>Traces:</h2>
{{'this is a test that run a console.log’| message }}

Результаты очень похожи:

И в этом случае в документации Angular 2 есть хорошее предупреждение об использовании нечистых каналов:

Https://angular.io/docs/ts/latest/guide/pipes.html

В заключение: использование методов в представлении аналогично использованию нечистых труб. Этот код будет выполняться в каждом событии в представлении, что может быть много раз. В нашем примере метод testRender возвращает только строку, поэтому его можно предположить для запуска в представлении, но если он сделает что-то более сложное, это может стать большой проблемой для производительности. Способ убедиться, что код в представлении запускается только один раз, - это чистые каналы:

@Pipe({name: 'message'})
export class MessagePipe implements PipeTransform {
transform(msg:string):string {
    console.log('Run the pipe');
    return msg;
 }
}