06 — Что такое view, LView и TView
Цель: понять, из чего «сделан» компонент в памяти, когда он работает. Без этого
непонятно, как устроены @for, порталы и change detection. Точная версия —
02-view-data-structures в соседнем vault'е.
Слово «view»
View — это один кусок экрана, которым управляет Angular: компонент, или тело
@if, или одна итерация @for. У каждого <li> из нашего @for — свой view.
Аналогия: бланк и заполненный бланк
Представь бумажный бланк анкеты: пустые поля «Имя: ___», «Возраст: ___».
- Сам бланк (шаблон) — один на всех. Его печатают один раз.
- Каждый человек заполняет свою копию бланка своими данными.
В Angular это ровно две структуры:
- TView («T» = Template, шаблон) — это бланк. Один на класс компонента. Хранит «где какое поле», какие тут есть директивы, какие хуки. Не зависит от данных.
- LView («L» = Logical, экземпляр) — это заполненная копия. По одной на каждый экземпляр. Хранит реальные DOM-узлы и текущие значения.
TView (бланк, 1 шт) LView (копии, много)
поле 0: <p> ┌─→ копия для App: [<p>Счётчик:1</p>, 1, ...]
поле 1: число │ копия для <li>1: [<li>1</li>, ...]
поле 2: <button> │ копия для <li>2: [<li>2</li>, ...]
... └─ копия для <li>3: [<li>3</li>, ...]
Один бланк TView, три заполненные копии LView — по одной на каждый <li>.
LView — это просто массив
Чтобы было быстро, LView — обычный JavaScript-массив. В ячейках лежат:
- ссылка на свой бланк (
TView); - флаги состояния (грязный/чистый, OnPush/Default);
- реальные DOM-узлы;
- значения биндингов (чтобы сравнивать старое с новым);
- инстанс самого класса компонента;
- ссылки на детей.
Первые 27 ячеек — служебные («шапка»). Дальше идут слоты под узлы шаблона. Когда
компилятор в главе 01 выдал element('p') со слотом 0 — этот
<p> ляжет в ячейку 27 + 0. Вот и вся «магия адресации».
TNode — описание одного узла
В бланке TView каждый узел описан объектом TNode (Template Node). Это «паспорт
узла»: какого он типа (элемент / текст / контейнер), как называется тег, какие у него
статические атрибуты, какие директивы на нём висят.
Важно: TNode — статика, общая для всех копий. У всех трёх <li> один и тот же
TNode-паспорт «это <li> с текстом внутри», а конкретное число (1, 2, 3) лежит уже
в LView каждой копии.
LContainer — «карман» для динамики
Там, где контент может появляться и исчезать (@if, @for, <ng-template>),
Angular ставит LContainer — это «карман-якорь». Технически это невидимый
комментарий в DOM (<!-- container -->), рядом с которым Angular вставляет и удаляет
копии view.
<ul>
<!-- LContainer (якорь @for) -->
<li>1</li> ← LView-копия 1, вставлена в карман
<li>2</li> ← LView-копия 2
<li>3</li> ← LView-копия 3
</ul>
Добавить элемент в @for = создать новую LView-копию и положить её в этот карман.
Удалить = вынуть и уничтожить. Как это работает в деталях — 07 и
08.
Как DOM-элемент находит свой компонент
Когда ты в DevTools делаешь ng.getComponent($0) — как Angular по <p> понимает,
к какому компоненту он относится?
Очень просто: при создании Angular приклеивает к DOM-элементу скрытое свойство
__ngContext__, в котором лежит ссылка на его LView. По ней и находится и компонент,
и директивы, и #ссылки.
Зачем тебе это знать
- Понятно, почему
@forдёшев: добавляется одна копия LView, а не перерисовывается весь список. - Понятно, почему change detection точечный: каждый LView помечается грязным отдельно.
- Понятно, что «компонент» в рантайме — это не класс, а класс + его LView + его DOM, связанные вместе.
Дальше: как Angular понимает, какой компонент/директиву ставить на какой элемент → 07 — Селекторы.