Структуры данных 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'а, — и expandoTView.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; :101LView; :398LViewFlags; :606TView; :610TViewType.
  • render3/interfaces/node.ts:30TNodeType; :132TNodeFlags; :274+ — поля TNode.
  • render3/interfaces/container.ts:20 — header LContainer; :121LContainerFlags.
  • render3/interfaces/context.ts:23LContext; render3/context_discovery.ts:42getLContext / __ngContext__.