perf(ivy): initialise TNode inputs / outputs on the first creation pass (#32608)
This perf-focused refactoring moves the TNode's input / output initialization logic to the first template pass - close to the place where directives are matched and resolved. This code change makes it possible to update-mode checks for both property bindings and listeners registration. PR Close #32608
This commit is contained in:
parent
adeee0fa7f
commit
ad178c55fd
|
@ -23,7 +23,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
|
|||
import {setUpAttributes} from '../util/attrs_utils';
|
||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
|
||||
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
|
||||
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
|
||||
|
||||
|
||||
|
||||
|
@ -84,13 +84,14 @@ export function ɵɵelementStart(
|
|||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||
|
||||
const inputData = initializeTNodeInputs(tView, tNode);
|
||||
if (inputData && inputData.hasOwnProperty('class')) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
|
||||
if (inputData && inputData.hasOwnProperty('style')) {
|
||||
tNode.flags |= TNodeFlags.hasStyleInput;
|
||||
const inputData = tNode.inputs;
|
||||
if (inputData != null) {
|
||||
if (inputData.hasOwnProperty('class')) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
if (inputData.hasOwnProperty('style')) {
|
||||
tNode.flags |= TNodeFlags.hasStyleInput;
|
||||
}
|
||||
}
|
||||
|
||||
if (tView.queries !== null) {
|
||||
|
|
|
@ -17,8 +17,7 @@ import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TVIEW} from '../interfaces/
|
|||
import {assertNodeOfPossibleTypes} from '../node_assert';
|
||||
import {getLView, getPreviousOrParentTNode} from '../state';
|
||||
import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils';
|
||||
|
||||
import {BindingDirection, generatePropertyAliases, getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';
|
||||
import {getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';
|
||||
|
||||
/**
|
||||
* Adds an event listener to the current node.
|
||||
|
@ -186,36 +185,28 @@ function listenerInternal(
|
|||
}
|
||||
|
||||
// subscribe to directive outputs
|
||||
if (isTNodeDirectiveHost && processOutputs) {
|
||||
let outputs = tNode.outputs;
|
||||
if (outputs === undefined) {
|
||||
// if we create TNode here, inputs must be undefined so we know they still need to be
|
||||
// checked
|
||||
outputs = tNode.outputs = generatePropertyAliases(tView, tNode, BindingDirection.Output);
|
||||
}
|
||||
const outputs = tNode.outputs;
|
||||
let props: PropertyAliasValue|undefined;
|
||||
if (processOutputs && outputs != null && (props = outputs[eventName])) {
|
||||
const propsLength = props.length;
|
||||
if (propsLength) {
|
||||
const lCleanup = getCleanup(lView);
|
||||
for (let i = 0; i < propsLength; i += 3) {
|
||||
const index = props[i] as number;
|
||||
ngDevMode && assertDataInRange(lView, index);
|
||||
const minifiedName = props[i + 2];
|
||||
const directiveInstance = lView[index];
|
||||
const output = directiveInstance[minifiedName];
|
||||
|
||||
let props: PropertyAliasValue|undefined;
|
||||
if (outputs !== null && (props = outputs[eventName])) {
|
||||
const propsLength = props.length;
|
||||
if (propsLength) {
|
||||
const lCleanup = getCleanup(lView);
|
||||
for (let i = 0; i < propsLength; i += 3) {
|
||||
const index = props[i] as number;
|
||||
ngDevMode && assertDataInRange(lView, index);
|
||||
const minifiedName = props[i + 2];
|
||||
const directiveInstance = lView[index];
|
||||
const output = directiveInstance[minifiedName];
|
||||
|
||||
if (ngDevMode && !isObservable(output)) {
|
||||
throw new Error(
|
||||
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
||||
}
|
||||
|
||||
const subscription = output.subscribe(listenerFn);
|
||||
const idx = lCleanup.length;
|
||||
lCleanup.push(listenerFn, subscription);
|
||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||
if (ngDevMode && !isObservable(output)) {
|
||||
throw new Error(
|
||||
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
||||
}
|
||||
|
||||
const subscription = output.subscribe(listenerFn);
|
||||
const idx = lCleanup.length;
|
||||
lCleanup.push(listenerFn, subscription);
|
||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f
|
|||
import {throwMultipleComponentError} from '../errors';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
|
@ -49,11 +49,6 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNod
|
|||
*/
|
||||
const _CLEAN_PROMISE = (() => Promise.resolve(null))();
|
||||
|
||||
export const enum BindingDirection {
|
||||
Input,
|
||||
Output,
|
||||
}
|
||||
|
||||
/** Sets the host bindings for the current view. */
|
||||
export function setHostBindings(tView: TView, viewData: LView): void {
|
||||
const selectedIndex = getSelectedIndex();
|
||||
|
@ -803,41 +798,47 @@ export function createTNode(
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consolidates all inputs or outputs of all directives on this logical node.
|
||||
*
|
||||
* @param tNode
|
||||
* @param direction whether to consider inputs or outputs
|
||||
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
|
||||
*/
|
||||
export function generatePropertyAliases(
|
||||
tView: TView, tNode: TNode, direction: BindingDirection): PropertyAliases|null {
|
||||
let propStore: PropertyAliases|null = null;
|
||||
const start = tNode.directiveStart;
|
||||
const end = tNode.directiveEnd;
|
||||
function generatePropertyAliases(
|
||||
inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number,
|
||||
propStore: PropertyAliases | null): PropertyAliases|null {
|
||||
for (let publicName in inputAliasMap) {
|
||||
if (inputAliasMap.hasOwnProperty(publicName)) {
|
||||
propStore = propStore === null ? {} : propStore;
|
||||
const internalName = inputAliasMap[publicName];
|
||||
|
||||
if (end > start) {
|
||||
const isInput = direction === BindingDirection.Input;
|
||||
const defs = tView.data;
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||
const propertyAliasMap: {[publicName: string]: string} =
|
||||
isInput ? directiveDef.inputs : directiveDef.outputs;
|
||||
for (let publicName in propertyAliasMap) {
|
||||
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
||||
propStore = propStore || {};
|
||||
const internalName = propertyAliasMap[publicName];
|
||||
const hasProperty = propStore.hasOwnProperty(publicName);
|
||||
hasProperty ? propStore[publicName].push(i, publicName, internalName) :
|
||||
(propStore[publicName] = [i, publicName, internalName]);
|
||||
}
|
||||
if (propStore.hasOwnProperty(publicName)) {
|
||||
propStore[publicName].push(directiveDefIdx, publicName, internalName);
|
||||
} else {
|
||||
(propStore[publicName] = [directiveDefIdx, publicName, internalName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return propStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes data structures required to work with directive outputs and outputs.
|
||||
* Initialization is done for all directives matched on a given TNode.
|
||||
*/
|
||||
function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
|
||||
ngDevMode && assertFirstTemplatePass(tView);
|
||||
|
||||
const start = tNode.directiveStart;
|
||||
const end = tNode.directiveEnd;
|
||||
const defs = tView.data;
|
||||
|
||||
let inputsStore: PropertyAliases|null = null;
|
||||
let outputsStore: PropertyAliases|null = null;
|
||||
for (let i = start; i < end; i++) {
|
||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||
inputsStore = generatePropertyAliases(directiveDef.inputs, i, inputsStore);
|
||||
outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore);
|
||||
}
|
||||
|
||||
tNode.inputs = inputsStore;
|
||||
tNode.outputs = outputsStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping between attributes names that don't correspond to their element property names.
|
||||
*
|
||||
|
@ -863,13 +864,11 @@ export function elementPropertyInternal<T>(
|
|||
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
|
||||
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const element = getNativeByIndex(index, lView) as RElement | RComment;
|
||||
const tNode = getTNode(index, lView);
|
||||
let inputData: PropertyAliases|null|undefined;
|
||||
let inputData = tNode.inputs;
|
||||
let dataValue: PropertyAliasValue|undefined;
|
||||
if (!nativeOnly && (inputData = initializeTNodeInputs(tView, tNode)) &&
|
||||
(dataValue = inputData[propName])) {
|
||||
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
|
||||
setInputsForProperty(lView, dataValue, value);
|
||||
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
||||
if (ngDevMode) {
|
||||
|
@ -1055,6 +1054,8 @@ export function resolveDirectives(
|
|||
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
|
||||
initialPreOrderCheckHooksLength);
|
||||
}
|
||||
|
||||
initializeInputAndOutputAliases(tView, tNode);
|
||||
}
|
||||
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||
}
|
||||
|
@ -1773,16 +1774,6 @@ export function storePropertyBindingMetadata(
|
|||
|
||||
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
||||
|
||||
export function initializeTNodeInputs(tView: TView, tNode: TNode): PropertyAliases|null {
|
||||
// If tNode.inputs is undefined, a listener has created outputs, but inputs haven't
|
||||
// yet been checked.
|
||||
if (tNode.inputs === undefined) {
|
||||
// mark inputs as checked
|
||||
tNode.inputs = generatePropertyAliases(tView, tNode, BindingDirection.Input);
|
||||
}
|
||||
return tNode.inputs;
|
||||
}
|
||||
|
||||
export function getCleanup(view: LView): any[] {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
|
||||
|
|
|
@ -423,7 +423,7 @@
|
|||
"name": "initNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeTNodeInputs"
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
},
|
||||
{
|
||||
"name": "insertBloom"
|
||||
|
|
|
@ -936,7 +936,7 @@
|
|||
"name": "initNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeTNodeInputs"
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
},
|
||||
{
|
||||
"name": "injectElementRef"
|
||||
|
|
Loading…
Reference in New Issue