Структуры данных view: LView / TView / TNode / LContainer
Версия 21.0.3, Ivy/render3. Здесь — «из чего физически состоит view» в рантайме. Это фундамент для 03-selector-matching (как находятся/привязываются директивы) и 04-portals-and-dynamic-views (динамические view и порталы). Контекст CD — 03-runtime.
Главная идея: два параллельных массива на каждый шаблон
У каждого шаблона компонента есть две структуры:
TView(«T» = Template) — статический blueprint, один на класс компонента. Что в какой ячейке лежит, метаданные, список директив-кандидатов, lifecycle-хуки. Не зависит от данных, шарится между всеми экземплярами.LView(«L» = Logical/Local) — рантайм-экземпляр, по одному на каждый инстанс view (каждый компонент, каждый@for-итем, каждый embedded view). Это обычный JS-массив: в ячейках — реальные DOM-узлы, значения биндингов, ссылки на дочерние view, инстансы директив.
Аналогия: TView — класс, LView — объект этого класса. Компилятор в template-функции
адресует ячейки по индексам; один и тот же индекс в TView.data даёт статику, в LView
— живое значение.
LView — экземпляр view (массив)
packages/core/src/render3/interfaces/view.ts:101. Первые 27 ячеек — фиксированный
header (именованные константы, view.ts:42), дальше идут слоты под инструкции.
HOST = 0 // нативный host-элемент этого view
TVIEW = 1 // ссылка на свой TView (blueprint)
FLAGS = 2 // LViewFlags: CheckAlways/Dirty/RefreshView/... (см. ниже)
PARENT = 3 // родительский LView или LContainer
NEXT = 4 // следующий LView/LContainer (связный список соседей)
T_HOST = 5 // TNode host-узла этого view
HYDRATION = 6
CLEANUP = 7 // функции очистки (отписки листенеров и т.п.)
CONTEXT = 8 // инстанс компонента ИЛИ context embedded-view ($implicit и пр.)
INJECTOR = 9
ENVIRONMENT = 10 // EnvironmentInjector-окружение (renderer factory, scheduler...)
RENDERER = 11
CHILD_HEAD = 12 // первый дочерний LView/LContainer
CHILD_TAIL = 13 // последний — для O(1) добавления в view tree
DECLARATION_VIEW = 14 // где view ОБЪЯВЛЕН (для embedded — важно для DI/CD)
DECLARATION_COMPONENT_VIEW = 15
DECLARATION_LCONTAINER = 16
PREORDER_HOOK_FLAGS = 17
QUERIES = 18
ID = 19 // уникальный id (используется в __ngContext__, см. ниже)
EMBEDDED_VIEW_INJECTOR = 20
ON_DESTROY_HOOKS = 21
EFFECTS_TO_SCHEDULE = 22
EFFECTS = 23
REACTIVE_TEMPLATE_CONSUMER = 24 // consumer-узел сигналов этого view (связь signal→view)
AFTER_RENDER_SEQUENCES_TO_ADD = 25
ANIMATIONS = 26
HEADER_OFFSET = 27 // <-- дальше начинаются слоты инструкций
Ключ к адресации: индекс инструкции из фазы Compile + HEADER_OFFSET = индекс в
LView. Когда компилятор генерит ɵɵdomElement(0, ...), узел ляжет в lView[27];
ɵɵadvance(1) сдвигает «текущий» указатель к слоту 28 и т.д. Поэтому decls в
defineComponent = сколько слотов после header зарезервировать.
После слотов узлов идёт binding-секция (с TView.bindingStartIndex) — туда
ɵɵtextInterpolate/ɵɵproperty пишут прошлые значения для diff'а, — и expando
(с TView.expandoStartIndex) — инстансы директив и node-injector'ы.
LViewFlags (view.ts:398)
Биты состояния экземпляра (то, чем живёт change detection — подробно в 01-cd-strategies):
CheckAlways = 1 << 4— стратегия Default. Нет бита → OnPush.Dirty = 1 << 6— view помечен грязным (срабатывает только в Global-режиме).RefreshView = 1 << 10— «рефрешни именно меня» (работает в любом режиме).HasChildViewsToRefresh = 1 << 13— «ниже есть что освежить» → нужно спуститься.- плюс
CreationMode,FirstLViewPass,Attached,IsRoot,Destroyedи др.
TView — blueprint шаблона
view.ts:606. Один на класс компонента, создаётся при первом инстансе (lazy).
type: TViewType(Root=0/Component=1/Embedded=2) —view.ts:610.blueprint: LView— заготовка, которую клонируют под новый экземпляр.data: TData— статический массив-близнецLView: по тем же индексам лежатTNode'ы,PipeDef'ы, фабрики директив.lView[i]⇄tView.data[i].firstCreatePass/firstUpdatePass: boolean— флаги «первый проход». Дорогую работу (матчинг селекторов, построениеTNode) делают только в первый раз; дальше переиспользуют готовыйTView.bindingStartIndex,expandoStartIndex— границы секций в LView (см. выше).directiveRegistry,pipeRegistry— список директив/пайпов-кандидатов для матчинга (приходит изdependenciesкомпонента). Heart of 03-selector-matching.hostBindingOpCodes— компактная программа host-биндингов директив.preOrderHooks/contentHooks/viewHooks/destroyHooks— отсортированные массивы lifecycle-хуков.
TNode — статический узел шаблона
packages/core/src/render3/interfaces/node.ts:30. Описывает один узел шаблона (элемент,
текст, контейнер, ng-content...). Лежит в TView.data[index]. Узлы связаны в дерево
(parent/child/next) — это «теневое» статическое дерево поверх DOM.
TNodeType (node.ts:30) — битовая маска:
Text=1 Element=2 Container=4 ElementContainer=8
Projection=16 Icu=32 Placeholder=64 LetDeclaration=128
Container — это <ng-template> (точка для embedded-view). ElementContainer —
<ng-container>.
Ключевые поля:
index— позиция вTView.data/LView.type,value(имя тега),attrs: TAttributes— статические атрибуты в плоском формате с маркерами (AttributeMarker.Classes/Styles/Bindings/Template/...). Именно поattrsматчатся селекторы директив.directiveStart/directiveEnd— диапазон слотов в expando, где лежат директивы этого узла.componentOffset— смещение к директиве-компоненту (или-1).flags: TNodeFlags(node.ts:132):isDirectiveHost=1<<0,isProjected=1<<1,hasContentQuery=1<<2,hasHostBindings=1<<6,isControlFlowStart=1<<8и др.providerIndexes— упаковка индекса первого провайдера + счётчик view-провайдеров (для DI на этом узле).next/child/parent— навигация по статическому дереву.projection— карта контент-проекции (что куда уехало по<ng-content>).
LContainer — якорь для динамических view
packages/core/src/render3/interfaces/container.ts:20. Создаётся там, где может
появляться/исчезать контент: <ng-template>, @if, @for, ViewContainerRef. Это
тоже массив; первые ячейки переиспользуют часть LView-header (FLAGS/PARENT/NEXT/T_HOST
= индексы 2–5), дальше своё:
TYPE = 1 // маркер «это LContainer, а не LView»
DEHYDRATED_VIEWS = 6
NATIVE = 7 // якорный DOM-узел (обычно comment-нода — «anchor»)
VIEW_REFS = 8 // массив публичных ViewRef
MOVED_VIEWS = 9 // view, созданные тут, но вставленные в другом месте (transplanted)
CONTAINER_HEADER_OFFSET = 10 // дальше — сами вставленные LView по порядку
С CONTAINER_HEADER_OFFSET идут вставленные дочерние LView в порядке отображения —
их порядок в массиве = порядок в DOM. Добавление/удаление/перемещение embedded-view —
это сплайс этого массива + перенос DOM-узлов (см. 04-portals-and-dynamic-views).
LContainerFlags.HasTransplantedViews = 1 << 1 (container.ts:121) — флаг для view,
объявленных в одном компоненте, а вставленных в другом (важно для CD).
Как DOM-узел находит свой LView: __ngContext__
packages/core/src/render3/context_discovery.ts. Реальные DOM-элементы
monkey-patch'атся скрытым свойством __ngContext__, в котором лежит либо LView[ID],
либо готовый объект LContext (interfaces/context.ts:23):
component— инстанс компонента на узле;directives— инстансы директив на узле;localRefs— карта#ref;lViewId,nodeIndex,native.
getLContext() (context_discovery.ts:42) разворачивает это обратно — так работают
DevTools, ng.getComponent($0) и DI по DOM-узлу. attachPatchData() ставит метку при
создании узла/директивы.
Карта связей (мини-схема)
TView (1 на класс) LView (1 на экземпляр)
data[i] = TNode ───────────► lView[i] = RNode (DOM) / value
directiveRegistry lView[dirIdx] = directive instance
hostBindingOpCodes lView[CONTEXT] = component instance
blueprint ──clone──────────► (новый LView)
lView[PARENT] → LView | LContainer
LContainer[10+] → [embedded LView, ...]
DOM node.__ngContext__ ──────► LContext → {lViewId, nodeIndex, component}
Привязка к исходникам
render3/interfaces/view.ts:42— header-константы LView;:101—LView;:398—LViewFlags;:606—TView;:610—TViewType.render3/interfaces/node.ts:30—TNodeType;:132—TNodeFlags;:274+— поляTNode.render3/interfaces/container.ts:20— headerLContainer;:121—LContainerFlags.render3/interfaces/context.ts:23—LContext;render3/context_discovery.ts:42—getLContext/__ngContext__.