Как стать автором
Обновить

Комментарии 37

А можно глупый вопрос? Зачем брать такую технологию, где архитектурно заложено такое огромное число граблей, что по каждой нужно отдельное исследование проводить, писать статьи, вставлять костыли и, конечно, рефакторить половину приложения, когда эти проблемы становятся совсем уж неприемлемыми?

А можно глупый ответ? Чтобы не использовать $mol)

Собственно, при написании интерфейса нужно понимать, что паттерн MVC, предполагает, что View как-то должно обновиться при изменении Model.

При стратегии OnPush в Angular View узнает об изменениях Model через паттерн Observable. Никакого волшебства в ChangeDetection. Если хочешь чтобы что-то обновилось, дерни ручку – subject.next(), markForCheck(), setState() (ой, это уже React).

Изначально Angular снимали с разработчика обязанность по логике оповещения об изменениях через zone.js, но решили больше так не делать.

Насколько я понимаю, $mol решает этот вопрос под капотом, не предоставляя разработчику ручного управления процессом ChangeDetection? Это ваше архитектурное решение)

В $mol и нет ChangeDetection, там просто все изменяемые свойства помечаются декоратором и всё, не надо никаких subject.next(), markForCheck(), setState().

В Angular Default тоже было не надо. Но не обманывайте ребятишек, под капотом же как-то View узнает про изменение? Видимо, декоратор и вызывает ручку для перерисовки, когда декорированная функция вызывается?)

Этот декоратор ничего про перерисовку не знает - он уведомляет производные свойства, что они устарели и всё.

Справедливости ради, подход по умолчанию в $mol и в Ангуляре сильно различается, и вовсе не в пользу Ангуляра: в то время когда Ангуляр на каждый чих пересчитывает все привязки в поддереве, $mol проходит по автоматически определённому ациклическому орграфу зависимостей и точечно применяет обновления.


Однако, вы тоже правы, потому что даже такой хороший алгоритм иногда нужно отключать. В сложных анимациях, например.

А что за сложные анимации такие, где нужно отключать реактивность?

Любые, которые нагружают процессор на заметную величину.

И чем тут может помочь выключение реактивности?

Снижением накладных расходов на никому не интересные действия, конечно же.


Когда некоторая величина нетривиально анимируется, она зависит от времени и заведомо меняется (почти) каждый кадр. При этом нас перестают волновать изменения зависимостей между кардами.

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

Выходит такая анимация за пределы видимой области или вырождается в изменения меньше пикселя и кэш внезапно начинает работать, а отслеживание зависимостей очень даже начинают интересовать.

Ну да, для тривиальной анимации (т.е. анимации которая ничего не делает) выходной кеш и правда немного поможет. Но не делать такую анимацию вовсе будет ещё дешевле.


А вот для невидимой анимации выходной кеш ничего не исправит. И, опять-таки, решение "не делать такую анимацию" тоже работает.


Что же до динамического отслеживания зависимостей — оно тут лишнее в любом случае, что с выходным кешем, что без него.

Реактивный кеш как раз таки поможет выключать анимацию автоматически, а не вручную. При выходе из видимой области произойдёт отписка от таймера, и при возвращении - подписка.

Не важно лишнее или нет. В любом случае будут происходить лишние вычисления. Важно какое влияние они оказывают на общую картину.

Причём тут реактивный кеш?

При том, что в $mol именно он.

Может потому что там все Google Developer Expert по Angular. А если серьезно, то присоединяюсь к вопросу. Все прошлые статьи у TInkoff были про React, про общую библиотеку компонентов в едином стиле и т.д., зачем разводить зоопарк?

Ну вот представьте. У вас есть только шило и мыло, а вам нужно гвозди забивать. Берёте мыло, насаживаете его на шило и у вас получается импровизированный микросервисный портал для забивания гвоздей.

У нас в компании используются и React, и Angular. Но почти все статьи, в том числе и про библиотеку компонентов, это был Angular (1389 против 231 местных очков вклада в хаб). Так что не знаю, откуда вы взяли про "все прошлые статьи были про React" :)

Отдельное спасибо хочу выразить ребятам из Тинькофф: в частности Саше Инкину и Роме Седову. На их статьях по Angular я вырос как разработчик: изучая исходный код в материалах и репозиториях, пытаясь разобраться, как и почему он работает. 

Не стоит недооценивать важность статьи, ведь ещё сам буквально недавно я боялся писать компоненты с OnPush, тыкая везде Default. Когда я попытался перевести уже готовое приложение на OnPush, я опускал руки, потому что казалось, что нужен вагон времени. Эта статья сэкономит часы или даже дни времени будущем читателям, здесь выжимка того, что автор сам изучал не за один день. Попадись мне такая в своё время – она точно бы сэкономила пару дней времени, и явно бы внесла больше ясности.

На OnPush проще переходить одновременно с переходом на какой-нибудь MobX, который сам будет дёргать ререндер компонента.

Не всегда понимаю, когда стоит использовть runOutsideAngular. Можно чуть больше примеров помимо анимации?

События, которые стреляют очень часто, например scroll, mousemove, dragmove могут быть хорошими кандидатами на запуск вне зоны. Но чаще всего это всё же requestAnimationFrame.

А что значит "если вы не закручиваете код в узлы?

Иногда в проектах можно встретить очень странный RxJS код, приправленный сайдэффектами, tap`ами и перебрасываниями туда сюда в стор чего-либо, что иногда может сказаться. Я не имел ввиду какой-то термин :)

Было бы отлично добавить два пример на StackBlitz, с рендерингом 200 компонентов на onPush и без него, тогда каждый поставит его по умолчанию в схематикс =)

Статья огонь, спасибо ребятам из TINKOFF, очень клевые статьи про angular и не только)

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

тоже столкнулся с такой проблемой, в итоге было найдено (неидеальное, но рабочее) решение

type ArgumentsType<F> = F extends (...args: infer A) => any ? A : never;
type ObjectLike<O extends object, P extends keyof O = keyof O> = Pick<O, P>;


extractTouchChanges(control: ObjectLike<AbstractControl, 'markAsTouched' | 'markAsUntouched'>): Observable<boolean> => {

const prevMarkAsTouched = control.markAsTouched;
const prevMarkAsUntouched = control.markAsUntouched;
const touchedChanges$ = new Subject<boolean>();

function nextMarkAsTouched(...args: ArgumentsType<AbstractControl['markAsTouched']>): void {
prevMarkAsTouched.bind(control)(...args);
touchedChanges$.next(true);
}

function nextMarkAsUntouched(...args: ArgumentsType<AbstractControl['markAsUntouched']>): void { prevMarkAsUntouched.bind(control)(...args); touchedChanges$.next(false);
}

control.markAsTouched = nextMarkAsTouched;
control.markAsUntouched = nextMarkAsUntouched;

return touchedChanges$;
};


Дальше, соответственно:
touched$ = extractTouchChanges(formControl)
.pipe(startWith(formControl.touched));

Почему вы используете bind вместо apply?

вот поэтому я и не люблю оставлять комментарии
всегда найдется какой-нибудь умник, обративший внимание на не относящуюся к основному вопросу вещь

Прочитал статью внимательно, но для меня OnPush так и остался способом для выборочного решения проблем с производительностью, а не дефолтным подходом. И вот почему.

Не считаете ли вы что использование данного подхода излишне усложняет проект? Появляется большое кол-во технического (не решающего бизнес-логику) кода только для того что бы по сути отказаться от одной из основных фишек фреймворка и поменять дефолтную стратегию (согласен, не всегда оптимальную) на собственный велосипед (тоже не оптимальный).
Есть ощущение что накладные расходы (особенной на сложных проектах) связанные с необходимостью поддержки подобного кода будут превышать профит.
Редизайн большого проекта с использованием подобного подхода вряд ли возможен. По сути, нам нужно разбить интерфейс на кучу несвязанных осколков и самостоятельно заботиться об их обновлении. Особенно тех что находятся в параллельных ветках.

OnPush, по сути, что-то меняет только для асинхронных операций. Если они написаны через RxJS, без императивных подписок и ручного изменения внутренних состояний, то OnPush ничего не добавит. Мне комфортно так писать и поддерживать код. RxJS требует особой подстроки мышления. Если её нет, то может возникать много непонимания, фрустрации и неприязни. Тогда с OnPush будет тяжело. Самостоятельно заботиться об обновлении мне не приходится. Практически всегда я не подстраиваюсь под OnPush, а просто пишу так, что при его включении всё будет работать и так. Разбиение интерфейса, если это вдумчивая декомпозиция, приложению только на пользу. В какой-то степени OnPush похож на strict в TypeScript. Можно сказать, что без него проще что-то накидать и не нужен лишний код проверок, но кодовая база с ним становится более предсказуемая, более аккуратная и надёжная. Ну и мигрировать приложение на него, если он сразу не был включен тоже будет довольно трудно 🙂

Декомпозиция это само собой разумеющиеся вещь. Без этого вообще невозможно создание более мене сложного приложения. Я про то другое.
Как вы сами же пишете в статье при OnPush стратегии не обновляются параллельные ветки.
Таким образом скажем клик на кнопку в компоненте А в шапке страницы не будет автоматически обновлять компонент Б где нибудь в подвале поскольку они не находятся в одной ветке и вообще не связаны между собой напрямую. Соответсвенно нам нужно будет добавлять какую то дополнительную логику которая бы сообщила компоненту Б что ему требуется обновиться.
Именно это меня и смущает в подобном подходе. Поскольку очевидно ведет к усложнению и повышению хрупкости кода. Это накладные расходы которые нам требуется заплатить за предполагаемое повышение производительности. Причем не важно есть ли с ней проблемы или нет.
Поэтому подход, решать проблемы по мере возникновения, мне все же кажется более оптимальным.

Но ведь если компонент А никак не связан с компонентом Б — почему он должен обновляться? Связь сверху вниз можно осуществить через инпуты и OnPush это подхватит. Связь параллельных вьюх надо осуществлять через сервис. Уж точно не надо строить своё приложение, полагаясь на волшебную связь всего со всем.

Он должен обновляться потому что того например требует бизнес-логика. Я это привел просто как пример для более сложного случая.
Ладно, на этом пожалуй откланяюсь. Свою точку зрения я высказал. Возможно кому-то она покажется разумной.

Еще дополню для ясности.
Речь о том что все имеет свою цену и предлагаемый вами подход тоже. Вопрос в том если стоимость решения не превышает пользу. Я не уверен, что это оправдано для всего множества случаев. Что если мы просто одни проблемы меняем на другие. Особенно в контексте того что данный подход предлагается использовать как дефолтный.

Прошу прощения, почему то Хабра вместо продолжения, делает новые ветки и нет возможности это поправить.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий