[Перевод] Механизм обновления Angular DOM

внешний интерфейс JavaScript TypeScript Angular.js

Оригинальная ссылка:The mechanics of DOM updates in Angular

DOM Update

Обновления DOM, вызванные изменениями модели, являются важной функцией всех интерфейсных фреймворков (примечание: синхронизация модели и представления), и, конечно же, Angular не является исключением. Определите выражение шаблона следующим образом:

<span>Hello {{name}}</span>

Или имущественное обязательство, как следующее (Примечание: это эквивалентно коду выше):

<span [textContent]="'Hello ' + name"></span>

когда каждый разnameКогда значение изменится, Angular волшебным образом автоматически обновит элемент DOM (примечание: верхний код — это код обновления).Текстовый узел DOM, приведенный выше код обновляетсяузел элемента DOM, это не одно и то же, поясняется ниже). Это выглядит просто на первый взгляд, но его внутренняя работа довольно сложна. Кроме того, обновления DOM — это просто Angular.Механизм обнаружения измененийЧасть механизма обнаружения изменений состоит из следующих трех шагов:

  • Обновления DOM (примечание: это то, что будет объяснено в этой статье)
  • child components Input bindings updates
  • query list updates

В этой статье в основном исследуется часть рендеринга механизма обнаружения изменений (т. е. часть обновления DOM). Если вам раньше был интересен этот вопрос, вы можете продолжить чтение, это определенно поможет вам начать.

При ссылке на соответствующий исходный код предполагается, что программа работает в производственном режиме. Давайте начнем!

Внутренняя структура программы

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

Посмотреть

Из этой моей статьиHere is what you need to know about dynamic components in AngularЗнайте, что компилятор Angular компилирует компоненты, используемые в программе, в фабричный класс. Например, следующий код показывает, как Angular создает компонент из фабричного класса. и не требует от разработчика ничего.вещи для автоматизации; а следующий код показывает, как разработчикруководствопройти черезComponentFactoryдля создания экземпляра компонента. Короче говоря, он пытался сказать, как создаются экземпляры компонентов):

const factory = r.resolveComponentFactory(AComponent);
componentRef: ComponentRef<AComponent> = factory.create(injector);

Angular использует этот фабричный класс для создания экземпляраView Definition, затем используйтеviewDefфункционировать, чтобыСоздать представление. Angular внутренне рассматривает программу как дерево представлений.Хотя программа имеет много компонентов, у нее есть общий интерфейс определения представления для определения структуры представления, созданной компонентами (Примечание: то есть,ViewDefinition Interface), конечно, Angular использует каждый объект компонента для создания соответствующего представления, таким образом формируя дерево представлений из нескольких представлений. (Примечание: одна из основных концепций здесь заключается в том, чтоПосмотреть, структура которогоViewDefinition Interface)

завод компонентов

Большая часть кода фабрики компонентов состоит из различных узлов представления, сгенерированных компилятором.Эти узлы представления генерируются посредством синтаксического анализа шаблона (Примечание: фабрика компонентов, сгенерированная компилятором, представляет собой функцию, которая возвращает функцию, ComponentFactory выше — это предоставленный класс Angular. для ручного вызова.Конечно, они указывают на одно и то же, но выражение другое). Предположим, что шаблон, определяющий компонент, выглядит следующим образом:

<span>I am {{name}}</span>

Компилятор проанализирует этот шаблон для создания кода фабрики компонентов, подобного следующему (примечание: это только самая важная часть кода):

function View_AComponent_0(l) {
    return jit_viewDef1(0,
        [
          jit_elementDef2(0,null,null,1,'span',...),
          jit_textDef3(null,['I am ',...])
        ], 
        null,
        function(_ck,_v) {
            var _co = _v.component;
            var currVal_0 = _co.name;
            _ck(_v,1,0,currVal_0);

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

 (function(jit_createRendererType2_0,jit_viewDef_1,jit_elementDef_2,jit_textDef_3) {
     var styles_AppComponent = [''];
     var RenderType_AppComponent = jit_createRendererType2_0({encapsulation:0,styles:styles_AppComponent,data:{}});
     function View_AppComponent_0(_l) {
         return jit_viewDef_1(0,
            [
                (_l()(),jit_elementDef_2(0,0,null,null,1,'span',[],null,null,null,null,null)),
                (_l()(),jit_textDef_3(1,null,['I am ','']))
            ],
            null,
            function(_ck,_v) {
    	        var _co = _v.component;
    	        var currVal_0 = _co.name;
    	        _ck(_v,1,0,currVal_0);
           });
    }
 return {RenderType_AppComponent:RenderType_AppComponent,View_AppComponent_0:View_AppComponent_0};})

Приведенный выше код описывает структуру представления и вызывается при создании экземпляра компонента.jit_viewDef_1На самом деле этоviewDefфункция для создания представления (Примечание:viewDefФункция важна, потому что представление создается путем ее вызова, а результирующая структура представленияViewDefinition).

viewDefвторой параметр функцииnodesЧем-то похоже на значение узлов в html, но не только это. Второй параметр в приведенном выше коде — это массив, первый элемент которогоjit_elementDef_2это определение узла элемента, второй элемент массиваjit_textDef_3является текстовым определением узла. Компилятор Angular генерирует множество различных определений узлов, тип узла определяетсяNodeFlagsзадавать. Позже мы увидим, как Angular обновляет DOM на основе разных типов узлов.

В этой статье интересны только элементы и текстовые узлы:

export const enum NodeFlags {
    TypeElement = 1 << 0, 
    TypeText = 1 << 1

Давайте кратко рассмотрим это.

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

Структурное определение узлов элементов

Структура узла элементаЭто структура узла, сгенерированная Angular при компиляции каждого HTML-элемента. Она также используется для генерации компонентов. Если вам это интересно, вы можете просмотреть ее.Here is why you will not find components inside Angular. Узел элемента также может содержать другие узлы элементов и текстовые узлы в качестве дочерних узлов, а количество дочерних узлов определяетсяchildCountзадавать.

Все определения элементов определяютсяelementRefсгенерированная функция, в то время как в фабричной функцииjit_elementDef_2()это функция.elementRef()Есть следующие общие параметры:

Name Description
childCount specifies how many children the current element have
namespaceAndName имя элемента html (примечание: например, «span»)
fixedAttrs attributes defined on the element

Есть несколько других параметров со специфическими свойствами:

Name Description
matchedQueriesDsl used when querying child nodes
ngContentIndex used for node projection
bindings used for dom and bound properties update
outputs, handleEvent used for event propagation

Эта статья в основном посвященаbindingsинтересно.

Примечание: Из вышеизложенного мы знаем, что представление (view) состоит из разных типов узлов (узлов), а узлы элементов (элементные узлы) состоят изelementRefсгенерированная функция, структура узла элемента задаетсяElementDefОпределенный.

Структурное определение текстовых узлов

структура текстового узлазаключается в том, что Angular компилирует каждыйHTML-текстСгенерированная структура узла. Обычно это дочерний элемент узла определения элемента, как в нашем примере в этой статье (примечание:<span>I am {{name}}</span>,spanэто узел элемента,I am {{name}}также является текстовым узломspanдочерние узлы). Этот текстовый узел созданtextDefсгенерированная функция. Его второй параметр передается в виде массива строк (примечание: Angular v5.* — это третий параметр). Например, следующий текст:

<h1>Hello {{name}} and another {{prop}}</h1>

будет проанализирован как массив:

["Hello ", " and another ", ""]

затем используется для создания правильных привязок:

{
  text: 'Hello',
  bindings: [
    {
      name: 'name',
      suffix: ' and another '
    },
    {
      name: 'prop',
      suffix: ''
    }
  ]
}

Это используется для генерации текста на этапе грязной проверки (примечание: обнаружение изменений):

text
+ context[bindings[0][property]] + context[bindings[0][suffix]]
+ context[bindings[1][property]] + context[bindings[1][suffix]]

Примечание. Как и выше, текстовые узлы создаютсяtextDefфункция сгенерирована, структура сгенерированаTextDefОпределенный. Теперь, когда известны определение и генерация двух узлов, как Angular обрабатывает привязку атрибутов к узлу?

привязка узла

Угловое использованиеBindingDefдля определения зависимостей привязки каждого узла, и эти зависимости привязки обычно являются свойствами класса компонента. Angular использует эти привязки, чтобы решить, как обновлять узлы и предоставлять контекстную информацию во время обнаружения изменений. Какую операцию выполняетBindingFlagsОпределено, в следующем списке показаны конкретные типы манипуляций с DOM:

Name Construction in template
TypeElementAttribute attr.name
TypeElementClass class.name
TypeElementStyle style.name

Определения элементов и текста внутренне создают эти зависимости привязки на основе этих распознаваемых компилятором флагов привязки. Каждый тип узла имеет различную логику генерации привязки (Примечание: это означает, что Angular будет генерировать соответствующий BindingDef в соответствии с BindingFlags).

Обновите средство визуализации

Нас больше всего интересуетjit_viewDef_1Последний параметр в:

function(_ck,_v) {
   var _co = _v.component;
   var currVal_0 = _co.name;
   _ck(_v,1,0,currVal_0);
});

Эта функция называетсяupdateRenderer. Он принимает два параметра:_ckа также_v._ckдаcheckсокращение дляprodCheckAndUpdateNodeфункция, в то время как_vявляется текущим объектом просмотра.updateRendererфункция будет вКаждый раз, когда обнаруживается изменениевызывается со своими аргументами_ckа также_vОн также был введен в это время.

updateRendererЛогика функции в основном состоит в том, чтобы получить текущее значение из связанного свойства объекта компонента и вызвать_ckфункция, передавая объект представления, индекс узла представления и текущее значение связанного свойства. Важным моментом является то, что Angular будет выполнять операцию обновления DOM для каждого представления, поэтому необходимо передать параметр индекса узла представления (Примечание: это легко понять, как упоминалось выше, Angular будет выполнять процесс синхронизации модели и представления для каждого представления). смотреть по очереди). ты можешь ясно видеть_ckсписок параметров:

function prodCheckAndUpdateNode(
    view: ViewData, 
    nodeIndex: number, 
    argStyle: ArgumentType, 
    v0?: any, 
    v1?: any, 
    v2?: any,

nodeIndexявляется индексом узла представления, если в шаблоне есть несколько выражений:

<h1>Hello {{name}}</h1>
<h1>Hello {{age}}</h1>

сгенерированный компиляторомupdateRendererФункция выглядит следующим образом:

var _co = _v.component;

// here node index is 1 and property is `name`
var currVal_0 = _co.name;
_ck(_v,1,0,currVal_0);

// here node index is 4 and bound property is `age`
var currVal_1 = _co.age;
_ck(_v,4,0,currVal_1);

обновить модель документа

Теперь, когда мы знаем все объекты, сгенерированные компилятором Angular (примечание: у нас уже есть представление, узел элемента, текстовый узел и реквизиты updateRenderer), мы можем изучить, как использовать эти объекты для обновления DOM.

Из вышеизложенного мы знаем, что во время обнаружения измененийupdateRendererОдин параметр, передаваемый в функцию,_ckфункция, и эта функцияprodCheckAndUpdateNode. После того, как эта функция продолжит выполняться, она в конечном итоге вызоветcheckAndUpdateNodeInline, если количество связанных свойств превышает 10, Angular также предоставляетcheckAndUpdateNodeDynamicЭта функция (примечание: две функции по существу одинаковы).

checkAndUpdateNodeInlineФункция выполнит соответствующую функцию проверки и обновления в соответствии с различными типами узлов представления:

case NodeFlags.TypeElement   -> checkAndUpdateElementInline
case NodeFlags.TypeText      -> checkAndUpdateTextInline
case NodeFlags.TypeDirective -> checkAndUpdateDirectiveInline

Давайте посмотрим, что делают эти функции, а дляNodeFlags.TypeDirectiveВы можете просмотреть мои статьиThe mechanics of property bindings update in Angular.

Примечание. Поскольку эта статья посвящена толькоelement node 和 text node.

Узел элемента

Для узлов элементов функция вызываетсяcheckAndUpdateElementInlineтак же какcheckAndUpdateElementValue,checkAndUpdateElementValueФункция проверяет, является ли форма привязки[attr.name, class.name, style.some]Или форма привязки свойства:

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;

Затем используйте соответствующий метод средства визуализации, чтобы выполнить соответствующую операцию на узле, например, используяsetElementClassк текущему узлуspanдобавить одинclass.

текстовый узел

Для типов текстовых узлов он вызоветcheckAndUpdateTextInline, вот основная часть:

if (checkAndUpdateBinding(view, nodeDef, bindingIndex, newValue)) {
    value = text + _addInterpolationPart(...);
    view.renderer.setValue(DOMNode, value);
}

он получитupdateRendererТекущее значение, переданное функцией (Примечание: приведенное выше_ck(_v,4,0,currVal_1);) по сравнению со значением при последнем обнаружении изменений. Просмотр данных содержитoldValuesсвойство, если значение свойства похоже наnameизменений, Angular использует последниеnameзначение для синтеза последнего строкового литерала, такого какHello New World, а затем используйте средство визуализации для обновления соответствующего текста в модели DOM.

Примечание: как узлы элемента обновления, так и узлы текста относятся к средству визуализации, что также является важной концепцией. Каждый объект представления имеетrendererСобственность, то естьRenderer2, который является средством визуализации компонентов, фактическая операция обновления DOM выполняется им. Поскольку Angular является кроссплатформенным, этот Renderer2 является интерфейсом, поэтому разные средства визуализации выбираются в соответствии с разными платформами. Например, Renderer в браузере — DOMRenderer, на сервере — ServerRenderer и так далее.Из этого видно, что в дизайне фреймворка Angular реализована хорошая абстракция.

В заключение

Я знаю, что есть много сложной информации для усвоения, но как только вы поймете эти знания, вы сможете лучше разрабатывать программы или отлаживать проблемы, связанные с обновлением DOM. Я предлагаю вам следовать исходной логике, упомянутой в этой статье, использовать отладчик илиdebugger 语句Шаг за шагом для отладки исходного кода.