diff --git a/compiler/src/pre_define.ts b/compiler/src/pre_define.ts index 0cbbb9f..ced40f0 100644 --- a/compiler/src/pre_define.ts +++ b/compiler/src/pre_define.ts @@ -303,3 +303,9 @@ export const ITEMCREATION: string = 'itemCreation'; export const OBSERVEDSHALLOWRENDER: string = 'observedShallowRender'; export const OBSERVEDDEEPRENDER:string = 'observedDeepRender'; export const ItemComponents: string[] = ['ListItem', 'GridItem']; +export const FOREACHITEMGENFUNCTION: string = 'forEachItemGenFunction'; +export const __LAZYFOREACHITEMGENFUNCTION: string = '__lazyForEachItemGenFunction'; +export const _ITEM: string = '_item'; +export const FOREACHITEMIDFUNC: string = 'forEachItemIdFunc'; +export const __LAZYFOREACHITEMIDFUNC: string = '__lazyForEachItemIdFunc'; +export const FOREACHUPDATEFUNCTION: string = 'forEachUpdateFunction'; diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index 8c16d38..26fb1af 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -73,7 +73,13 @@ import { ITEMCREATION, OBSERVEDSHALLOWRENDER, OBSERVEDDEEPRENDER, - ItemComponents + ItemComponents, + FOREACHITEMGENFUNCTION, + __LAZYFOREACHITEMGENFUNCTION, + _ITEM, + FOREACHITEMIDFUNC, + __LAZYFOREACHITEMIDFUNC, + FOREACHUPDATEFUNCTION } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -131,7 +137,7 @@ export function processComponentBlock(node: ts.Block, isLazy: boolean, log: LogI const newStatements: ts.Statement[] = []; processComponentChild(node, newStatements, log, {isAcceleratePreview: false, line: 0, column: 0, fileName: ''}, isInnerBuilder, parent); - if (isLazy) { + if (isLazy && compatibleSdkVersion === '8') { newStatements.unshift(createRenderingInProgress(true)); } if (isTransition) { @@ -142,7 +148,7 @@ export function processComponentBlock(node: ts.Block, isLazy: boolean, log: LogI createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); } - if (isLazy) { + if (isLazy && compatibleSdkVersion === '8') { newStatements.push(createRenderingInProgress(false)); } return ts.factory.updateBlock(node, newStatements); @@ -266,7 +272,13 @@ export function processComponentChild(node: ts.Block | ts.SourceFile, newStateme break; case ComponentType.forEachComponent: parent = undefined; - processForEachComponent(item, newStatements, log, isInnerBuilder); + if (compatibleSdkVersion === '8') { + processForEachComponent(item, newStatements, log, isInnerBuilder); + } else { + processForEachComponentNew(item, newStatements, log); + } + lastName = name; + lastExpression = item; break; case ComponentType.customBuilderMethod: parent = undefined; @@ -807,7 +819,8 @@ function processForEachComponent(node: ts.ExpressionStatement, newStatements: ts ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [argumentsArray[0]]); } argumentsArray.splice(0, 1, arrayObserveredObject); - const newArrowNode: ts.ArrowFunction = processForEachBlock(node.expression, log, isInnerBuilder); + const newArrowNode: ts.ArrowFunction = + processForEachBlock(node.expression, log, isInnerBuilder) as ts.ArrowFunction; if (newArrowNode) { argumentsArray.splice(1, 1, newArrowNode); } @@ -817,6 +830,160 @@ function processForEachComponent(node: ts.ExpressionStatement, newStatements: ts newStatements.push(node, popNode); } +function processForEachComponentNew(node: ts.ExpressionStatement, newStatements: ts.Statement[], + log: LogInfo[]): void { + const newForEachStatements: ts.Statement[] = []; + const popNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(createFunction( + (node.expression as ts.CallExpression ).expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); + if (ts.isCallExpression(node.expression)) { + const argumentsArray: ts.Expression[] = Array.from(node.expression.arguments); + const propertyNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + node.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION)), undefined, [])); + const newArrowNode: ts.NodeArray = + processForEachBlock(node.expression, log) as ts.NodeArray; + const itemGenFunctionStatement: ts.VariableStatement = createItemGenFunctionStatement(node.expression, + argumentsArray, newArrowNode); + const itemIdFuncStatement: ts.VariableStatement = createItemIdFuncStatement(node.expression, argumentsArray); + const updateFunctionStatement: ts.ExpressionStatement = createUpdateFunctionStatement(argumentsArray); + const lazyForEachStatement: ts.ExpressionStatement = createLazyForEachStatement(argumentsArray); + if (node.expression.expression.getText() === COMPONENT_FOREACH) { + if (argumentsArray[2]) { + newForEachStatements.push(propertyNode, itemGenFunctionStatement, itemIdFuncStatement, updateFunctionStatement); + } else { + newForEachStatements.push(propertyNode, itemGenFunctionStatement, updateFunctionStatement); + } + newStatements.push(createComponentCreationStatement(node, newForEachStatements), popNode); + } else { + if (argumentsArray[2]) { + newStatements.push(itemGenFunctionStatement, itemIdFuncStatement, lazyForEachStatement, popNode); + } else { + newStatements.push(itemGenFunctionStatement, lazyForEachStatement, popNode); + } + } + } +} + +function createItemGenFunctionStatement( + node: ts.CallExpression, + argumentsArray: ts.Expression[], + newArrowNode: ts.NodeArray +): ts.VariableStatement { + if (argumentsArray[1] && ts.isArrowFunction(argumentsArray[1])) { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(node.expression.getText() === COMPONENT_FOREACH ? + FOREACHITEMGENFUNCTION : __LAZYFOREACHITEMGENFUNCTION), + undefined, undefined, + ts.factory.createArrowFunction( + undefined, undefined, + [ts.factory.createParameterDeclaration( + undefined, undefined, undefined, ts.factory.createIdentifier(_ITEM))], + undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier( + argumentsArray[1].parameters[0] && argumentsArray[1].parameters[0].name.getText()), + undefined, + undefined, + ts.factory.createIdentifier(_ITEM) + )], + ts.NodeFlags.Const + ) + ), + ...newArrowNode + ], + true + ) + ) + ) + ], + ts.NodeFlags.Const + ) + ); + } +} + +function createItemIdFuncStatement( + node: ts.CallExpression, + argumentsArray: ts.Expression[] +): ts.VariableStatement { + if (argumentsArray[2] && ts.isArrowFunction(argumentsArray[2])) { + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier(node.expression.getText() === COMPONENT_FOREACH ? + FOREACHITEMIDFUNC : __LAZYFOREACHITEMIDFUNC), undefined, undefined, + ts.factory.createArrowFunction( + undefined, undefined, + [ts.factory.createParameterDeclaration(undefined, undefined, undefined, + ts.factory.createIdentifier( + argumentsArray[2].parameters[0] ? argumentsArray[2].parameters[0].name.escapedText : '' + ) + )], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createIdentifier(argumentsArray[2].body ? argumentsArray[2].body.getText() : '') + ) + )], + ts.NodeFlags.Const + ) + ); + } + +} + +function createUpdateFunctionStatement(argumentsArray: ts.Expression[]): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(FOREACHUPDATEFUNCTION) + ), + undefined, + addForEachIdFuncParameter(argumentsArray) + ) + ); +} + +function addForEachIdFuncParameter(argumentsArray: ts.Expression[]): ts.Identifier[] { + const addForEachIdFuncParameterArr: ts.Identifier[] = []; + addForEachIdFuncParameterArr.push( + ts.factory.createIdentifier(ELMTID), + ts.factory.createIdentifier(argumentsArray[0] && argumentsArray[0].getText()) + ); + if (argumentsArray[2]) { + addForEachIdFuncParameterArr.push(ts.factory.createIdentifier(FOREACHITEMIDFUNC)); + } + addForEachIdFuncParameterArr.push(ts.factory.createIdentifier(FOREACHITEMGENFUNCTION)); + return addForEachIdFuncParameterArr; +} + +function createLazyForEachStatement(argumentsArray: ts.Expression[]): ts.ExpressionStatement { + return ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(COMPONENT_LAZYFOREACH), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) + ), + undefined, + [ts.factory.createStringLiteral(componentInfo.id.toString()), + ts.factory.createThis(), + argumentsArray[0], + ts.factory.createIdentifier(__LAZYFOREACHITEMGENFUNCTION), + ts.factory.createIdentifier(__LAZYFOREACHITEMIDFUNC) + ] + ) + ); +} + function addForEachId(node: ts.ExpressionStatement): ts.ExpressionStatement { const forEachComponent: ts.CallExpression = node.expression as ts.CallExpression; return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( @@ -826,7 +993,7 @@ function addForEachId(node: ts.ExpressionStatement): ts.ExpressionStatement { } function processForEachBlock(node: ts.CallExpression, log: LogInfo[], - isInnerBuilder: boolean = false): ts.ArrowFunction { + isInnerBuilder: boolean = false): ts.NodeArray | ts.ArrowFunction { if (node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { const isLazy: boolean = node.expression.getText() === COMPONENT_LAZYFOREACH; const arrowNode: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; @@ -842,14 +1009,22 @@ function processForEachBlock(node: ts.CallExpression, log: LogInfo[], const blockNode: ts.Block = ts.factory.createBlock([statement], true); // @ts-ignore statement.parent = blockNode; - return ts.factory.updateArrowFunction( - arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, - arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(blockNode, isLazy, log)); + if (compatibleSdkVersion === '8') { + return ts.factory.updateArrowFunction( + arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(blockNode, isLazy, log)); + } else { + return processComponentBlock(blockNode, isLazy, log).statements; + } } else { - return ts.factory.updateArrowFunction( - arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, - arrowNode.type, arrowNode.equalsGreaterThanToken, - processComponentBlock(body, isLazy, log, false, isInnerBuilder)); + if (compatibleSdkVersion === '8') { + return ts.factory.updateArrowFunction( + arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, + processComponentBlock(body, isLazy, log, false, isInnerBuilder)); + } else { + return processComponentBlock(body, isLazy, log).statements; + } } } return null; diff --git a/compiler/test/utForPartialUpdate/inner_component_transform/render_component/foreach/foreach.ts b/compiler/test/utForPartialUpdate/inner_component_transform/render_component/foreach/foreach.ts new file mode 100644 index 0000000..0f4f85b --- /dev/null +++ b/compiler/test/utForPartialUpdate/inner_component_transform/render_component/foreach/foreach.ts @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +exports.source = ` +@Entry +@Component +struct ParentView { + @State arr: string[] = ['1', '2', '3']; + build() { + List() { + ForEach(this.arr, + item => { + Text(item) + }, + item => item.toString() + ) + } + } +} +` +exports.expectResult = +`class ParentView extends View { + constructor(parent, params) { + super(parent); + this.__arr = new ObservedPropertyObject(['1', '2', '3'], this, "arr"); + this.setInitiallyProvidedValue(params); + } + setInitiallyProvidedValue(params) { + if (params.arr !== undefined) { + this.arr = params.arr; + } + } + setStateSourcePropertiesUnchanged() { + this.__arr.SetPropertyUnchanged(); + } + setOneWaySyncPropertiesUnchanged() { + } + setTwoWaySyncPropertiesUnchanged() { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + this.__arr.purgeDependencyOnElmtId(rmElmtId); + } + aboutToBeDeleted() { + this.__arr.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + get arr() { + return this.__arr.get(); + } + set arr(newValue) { + this.__arr.set(newValue); + } + render() { + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + List.create(); + if (!isInitialRender) { + List.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + ForEach.create(); + const forEachItemGenFunction = _item => { + const item = _item; + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + Text.create(item); + if (!isInitialRender) { + Text.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + Text.pop(); + }; + const forEachItemIdFunc = item => item.toString(); + this.forEachUpdateFunction(elmtId, this.arr, forEachItemIdFunc, forEachItemGenFunction); + if (!isInitialRender) { + ForEach.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + ForEach.pop(); + List.pop(); + } + rerender() { + this.__arr.markDependentElementsDirty(this); + this.updateDirtyElements(); + } +} +loadDocument(new ParentView("1", undefined, {})); +` \ No newline at end of file diff --git a/compiler/test/utForPartialUpdate/inner_component_transform/render_component/lazyforeach/lazyforeach.ts b/compiler/test/utForPartialUpdate/inner_component_transform/render_component/lazyforeach/lazyforeach.ts new file mode 100644 index 0000000..1ff6422 --- /dev/null +++ b/compiler/test/utForPartialUpdate/inner_component_transform/render_component/lazyforeach/lazyforeach.ts @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +exports.source = ` +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = [] + + public totalCount(): number { + return 0 + } + public getData(index: number): any { + return undefined + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener') + this.listeners.push(listener) + } + } + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener') + this.listeners.splice(pos, 1) + } + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded() + }) + } + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index) + }) + } + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index) + }) + } + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index) + }) + } + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to) + }) + } +} + +class MyDataSource extends BasicDataSource { + private dataArray: string[] = ['/path/image0', '/path/image1', '/path/image2', '/path/image3'] + + public totalCount(): number { + return this.dataArray.length + } + public getData(index: number): any { + return this.dataArray[index] + } + + public addData(index: number, data: string): void { + this.dataArray.splice(index, 0, data) + this.notifyDataAdd(index) + } + public pushData(data: string): void { + this.dataArray.push(data) + this.notifyDataAdd(this.dataArray.length - 1) + } +} + +@Entry +@Component +struct Test { +private data: MyDataSource = new MyDataSource() +build() { + Grid() { + LazyForEach (this.data, + (row) => { + GridItem() { + Text(row) + } + }, + row => row) + } +} +} +` +exports.expectResult = +`class BasicDataSource { + constructor() { + this.listeners = []; + } + totalCount() { + return 0; + } + getData(index) { + return undefined; + } + registerDataChangeListener(listener) { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener'); + this.listeners.push(listener); + } + } + unregisterDataChangeListener(listener) { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener'); + this.listeners.splice(pos, 1); + } + } + notifyDataReload() { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }); + } + notifyDataAdd(index) { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }); + } + notifyDataChange(index) { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }); + } + notifyDataDelete(index) { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }); + } + notifyDataMove(from, to) { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }); + } +} +class MyDataSource extends BasicDataSource { + constructor() { + super(...arguments); + this.dataArray = ['/path/image0', '/path/image1', '/path/image2', '/path/image3']; + } + totalCount() { + return this.dataArray.length; + } + getData(index) { + return this.dataArray[index]; + } + addData(index, data) { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + pushData(data) { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } +} +class Test extends View { + constructor(parent, params) { + super(parent); + this.data = new MyDataSource(); + this.setInitiallyProvidedValue(params); + } + setInitiallyProvidedValue(params) { + if (params.data !== undefined) { + this.data = params.data; + } + } + setStateSourcePropertiesUnchanged() { + } + setOneWaySyncPropertiesUnchanged() { + } + setTwoWaySyncPropertiesUnchanged() { + } + purgeVariableDependenciesOnElmtId(rmElmtId) { + } + aboutToBeDeleted() { + this.data = undefined; + SubscriberManager.Get().delete(this.id__()); + this.aboutToBeDeletedInternal(); + } + render() { + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + Grid.create(); + if (!isInitialRender) { + Grid.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + const __lazyForEachItemGenFunction = _item => { + const row = _item; + { + const isLazyCreate = true; + const itemCreation = (elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + GridItem.create(deepRenderFunction, isLazyCreate); + if (!isInitialRender) { + GridItem.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }; + const observedShallowRender = () => { + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + GridItem.create(deepRenderFunction, isLazyCreate); + if (!isInitialRender) { + GridItem.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + GridItem.pop(); + }; + const observedDeepRender = () => { + this.observeComponentCreation(itemCreation); + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + Text.create(row); + if (!isInitialRender) { + Text.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + Text.pop(); + GridItem.pop(); + }; + const deepRenderFunction = (elmtId, isInitialRender) => { + itemCreation(elmtId, isInitialRender); + this.updateFuncByElmtId.set(elmtId, itemCreation); + this.observeComponentCreation((elmtId, isInitialRender) => { + ViewStackProcessor.StartGetAccessRecordingFor(elmtId); + Text.create(row); + if (!isInitialRender) { + Text.pop(); + } + ViewStackProcessor.StopGetAccessRecording(); + }); + Text.pop(); + GridItem.pop(); + }; + if (isLazyCreate) { + observedShallowRender(); + } + else { + observedDeepRender(); + } + } + }; + const __lazyForEachItemIdFunc = row => row; + LazyForEach.create("1", this, this.data, __lazyForEachItemGenFunction, __lazyForEachItemIdFunc); + LazyForEach.pop(); + Grid.pop(); + } + rerender() { + this.updateDirtyElements(); + } +} +loadDocument(new Test("1", undefined, {})); +` \ No newline at end of file