ArkUI实现
编译期
- 通过声明式语法使用系统和自定义组件描述UI树
- 将装饰器修饰的 UI 组件树,转换成 ViewPU 树
运行时
- 监听并响应系统硬件的VSync信号
- 在测量和布局阶段,通过上面的ViewPU树进一步构建 FrameNode 树 和 RSNode 树
- 在布局和绘制阶段,通过 RSNode 录制绘制命令,然后使用IPC 发送命令到 RenderService 进程中,由RenderService完成 UI 绘制
转换前的代码
js 代码解读复制代码import { BasicDataSource } from './BasicDataSource';
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
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 Index {
@State message1: string = 'hello'
message2: string = 'Hi'
@State showMode: number = 0
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
Column() {
Row() {
Column() {
Text('the first text')
.fontSize(50)
.fontWeight(FontWeight.Bold)
Text('the second text')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
}
Row() {
Column() {
Text('the third text')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
}
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item)
}.cachedCount(5)
ForEach([1, 2, 3], (item: number, index: number) => {
Text(`item: ${item} index: ${index}`)
.fontSize(50)
.fontWeight(FontWeight.Bold
)
})
if (this.showMode === 0) {
Child({
message1: this.message1,
message2: this.message2
})
} else if (this.showMode === 1) {
Child2()
} else {
Text("this is if else else branch")
}
}
}
}
@Component
struct Child {
@Link message1: string;
@Prop message2: string;
build() {
Column() {
Text(this.message1)
}
}
}
@Component
struct Child2 {
private message: string = 'Hello';
build() {
Column() {
Text(this.message)
}
}
}
转换成TS后的代码
js 代码解读复制代码if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface Child2_Params {
message?: string;
}
interface Child_Params {
message1?: string;
message2?: string;
}
interface Index_Params {
message1?: string;
message2?: string;
showMode?: number;
data?: MyDataSource;
}
import { BasicDataSource } from "@bundle:com.unravel.myapplication/entry/ets/pages/BasicDataSource";
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
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);
}
}
class Index extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__message1 = new ObservedPropertySimplePU('hello', this, "message1");
this.message2 = 'Hi';
this.__showMode = new ObservedPropertySimplePU(0, this, "showMode");
this.data = new MyDataSource();
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: Index_Params) {
if (params.message1 !== undefined) {
this.message1 = params.message1;
}
if (params.message2 !== undefined) {
this.message2 = params.message2;
}
if (params.showMode !== undefined) {
this.showMode = params.showMode;
}
if (params.data !== undefined) {
this.data = params.data;
}
}
updateStateVars(params: Index_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__message1.purgeDependencyOnElmtId(rmElmtId);
this.__showMode.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__message1.aboutToBeDeleted();
this.__showMode.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __message1: ObservedPropertySimplePU;
get message1() {
return this.__message1.get();
}
set message1(newValue: string) {
this.__message1.set(newValue);
}
private message2: string;
private __showMode: ObservedPropertySimplePU;
get showMode() {
return this.__showMode.get();
}
set showMode(newValue: number) {
this.__showMode.set(newValue);
}
private data: MyDataSource;
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`);
}
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('the first text');
Text.fontSize(50);
Text.fontWeight(FontWeight.Bold);
}, Text);
Text.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('the second text');
Text.fontSize(50);
Text.fontWeight(FontWeight.Bold);
}, Text);
Text.pop();
Column.pop();
Row.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create('the third text');
Text.fontSize(50);
Text.fontWeight(FontWeight.Bold);
}, Text);
Text.pop();
Column.pop();
Row.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
List.create({ space: 3 });
List.cachedCount(5);
}, List);
{
const __lazyForEachItemGenFunction = _item => {
const item = _item;
{
const itemCreation2 = (elmtId, isInitialRender) => {
ListItem.create(() => { }, false);
};
const observedDeepRender = () => {
this.observeComponentCreation2(itemCreation2, ListItem);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
Row.margin({ left: 10, right: 10 });
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(item);
Text.fontSize(50);
Text.onAppear(() => {
console.info("appear:" + item);
});
}, Text);
Text.pop();
Row.pop();
ListItem.pop();
};
observedDeepRender();
}
};
const __lazyForEachItemIdFunc = (item: string) => item;
LazyForEach.create("1", this, this.data, __lazyForEachItemGenFunction, __lazyForEachItemIdFunc);
LazyForEach.pop();
}
List.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
ForEach.create();
const forEachItemGenFunction = (_item, index: number) => {
const item = _item;
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`item: ${item} index: ${index}`);
Text.fontSize(50);
Text.fontWeight(FontWeight.Bold);
}, Text);
Text.pop();
};
this.forEachUpdateFunction(elmtId, [1, 2, 3], forEachItemGenFunction, undefined, true, false);
}, ForEach);
ForEach.pop();
this.observeComponentCreation2((elmtId, isInitialRender) => {
If.create();
if (this.showMode === 0) {
this.ifElseBranchUpdateFunction(0, () => {
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new Child(this, {
message1: this.__message1,
message2: this.message2
}, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 81 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
message1: this.message1,
message2: this.message2
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {
message2: this.message2
});
}
}, { name: "Child" });
}
});
}
else if (this.showMode === 1) {
this.ifElseBranchUpdateFunction(1, () => {
{
this.observeComponentCreation2((elmtId, isInitialRender) => {
if (isInitialRender) {
let componentCall = new Child2(this, {}, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/Index.ets", line: 86 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {});
}
}, { name: "Child2" });
}
});
}
else {
this.ifElseBranchUpdateFunction(2, () => {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create("this is if else else branch");
}, Text);
Text.pop();
});
}
}, If);
If.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
static getEntryName(): string {
return "Index";
}
}
class Child extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__message1 = new SynchedPropertySimpleTwoWayPU(params.message1, this, "message1");
this.__message2 = new SynchedPropertySimpleOneWayPU(params.message2, this, "message2");
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: Child_Params) {
}
updateStateVars(params: Child_Params) {
this.__message2.reset(params.message2);
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__message1.purgeDependencyOnElmtId(rmElmtId);
this.__message2.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__message1.aboutToBeDeleted();
this.__message2.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __message1: SynchedPropertySimpleTwoWayPU;
get message1() {
return this.__message1.get();
}
set message1(newValue: string) {
this.__message1.set(newValue);
}
private __message2: SynchedPropertySimpleOneWayPU;
get message2() {
return this.__message2.get();
}
set message2(newValue: string) {
this.__message2.set(newValue);
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(this.message1);
}, Text);
Text.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
class Child2 extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.message = 'Hello';
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: Child2_Params) {
if (params.message !== undefined) {
this.message = params.message;
}
}
updateStateVars(params: Child2_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private message: string;
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create();
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(this.message);
}, Text);
Text.pop();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
registerNamedRoute(() => new Index(undefined, {}), "", { bundleName: "com.unravel.myapplication", moduleName: "entry", pagePath: "pages/Index" });
build 转换前后
- 转换前后的代码如图,我们看到最终是在 initialRender 中完成具体的组件的搭建
- 创建每个系统组件和自定义组件的时候都是通过 observeComponentCreation2 来完成的
- 整个过程以栈的形式运作,每个组件create入栈,pop出栈,最终组成组件树
一帧的核心绘制流程
- Component树经过测算后转换成FrameNode树,再经过布局、绘制操作转换成RSNode
- RSNode 录制指令,交给RSCanvasNode转换成渲染指令,然后通过IPC发送给RenderService再经由GPU、CPU绘制出来
if/else 转换前后
一、If 是一个单独的组件,也可以通过Create、Pop创建和销毁
二、有多少个分支,就会创建多少个 ifElseBranchUpdateFunction,它的第一个参数是唯一的一个数字,从0递增,第二个参数是一个箭头函数,用于创建组件
ForEach转换前后
可以对比ForEach 的键值生成规则、组件创建规则 理解代码
developer.huawei.com/consumer/cn…
一、ForEach 是一个单独的组件,也可以通过Create、Pop创建和销毁
二、ForEach的更新是通过 forEachUpdateFunction 来完成的,包括id生成,diff更新等
js 代码解读复制代码/**
Partial updates for ForEach.
* @param elmtId ID of element.
* @param itemArray Array of items for use of itemGenFunc.
* @param itemGenFunc Item generation function to generate new elements. If index parameter is
* given set itemGenFuncUsesIndex to true.
* @param idGenFunc ID generation function to generate unique ID for each element. If index parameter is
* given set idGenFuncUsesIndex to true.
* @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
* @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
*/
public forEachUpdateFunction(
elmtId: number,
itemArray: Array,
itemGenFunc: (item: any, index?: number) => void,
idGenFunc?: (item: any, index?: number) => string,
itemGenFuncUsesIndex: boolean = false,
idGenFuncUsesIndex: boolean = false
): void {
if (itemArray === null || itemArray === undefined) {
return;
}
if (itemGenFunc === null || itemGenFunc === undefined) {
return;
}
// 没有传入生成id的函数,默认使用带index的生成函数
if (idGenFunc === undefined) {
idGenFuncUsesIndex = true;
// catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message
idGenFunc = (item: any, index: number): string => {
try {
// 默认id函数:通过index拼接item的json串
return `${index}__${JSON.stringify(item)}`;
} catch (e) {
throw new Error(
` ForEach id ${elmtId}: use of default id generator function not possible on provided data structure. Need to specify id generator function (ForEach 3rd parameter). Application Error!`
);
}
};
}
let diffIndexArray = []; // New indexes compared to old one.
let newIdArray = [];
let idDuplicates = [];
const arr = itemArray; // just to trigger a 'get' onto the array
// ID gen is with index.
if (idGenFuncUsesIndex) {
// Create array of new ids.
arr.forEach((item, indx) => {
newIdArray.push(idGenFunc(item, indx));
});
} else {
// 如果传入的生成id的函数不带index,但是生成item的函数带index.也会拼接一个index
// Create array of new ids.
arr.forEach((item, index) => {
newIdArray.push(
`${itemGenFuncUsesIndex ? index + "_" : ""}` + idGenFunc(item)
);
});
}
// Set new array on C++ side.
// C++ returns array of indexes of newly added array items.
// these are indexes in new child list.
ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates);
// Item gen is with index.
diffIndexArray.forEach((indx) => {
ForEach.createNewChildStart(newIdArray[indx], this);
if (itemGenFuncUsesIndex) {
itemGenFunc(arr[indx], indx);
} else {
itemGenFunc(arr[indx]);
}
ForEach.createNewChildFinish(newIdArray[indx], this);
});
}
LazyForEach 转换前后
一、LazyForEach 是一个单独的组件,也可以通过Create、Pop创建和销毁
create参数中的builder和idfunc都是我们生成的函数
import 转换前后
- 无论是本地路径引入还是通过har包引入,import转换后都转换成了完全形式的引入
- 一些引入但是没有用到的变量,编译后会被自动tree shaking
自定义组件 转化前后
- 自定义组件最终都会转换为 ViewPU的子类
- 自定义组件的参数 会被集合到一个interface定义中,命名参数传递也是基于此实现
- 普通变量不做改变,状态变量会被重写为getter和setter 同时生成双下划线开头的状态类
在组件构建的时候就会通过传入的参数初始化对应的私有状态变量
系统组件
void RegisterAllModule(BindingTarget globalObj, void* nativeEngine)
系统组件和自定义组件不同,每一个系统组件都绑定了一个对应的C++类
所有的组件都通过 RegisterAllModule 进行了注册。
static const std::unordered_map> bindFuncs =
系统组件和TS类的映射关系如下
以Text为例
首先在Bind方法内声明了类名为Text,然后将一些属性绑定到了C++底层的方法
最后继承并绑定globalObj的属性,也就是所有组件的公共属性
JSClass::Declare
C++底层是通过模板类来创建的组件类。生成的类有静态方法、对象方法、getter/setter以及类方法等
void JsiClass::StaticMethod
JSClass::InheritAndBind(globalObj)
initialRender:构建UI树
observeComponentCreation2
每个组件的创建都是通过 observeComponentCreation2 完成的。
原有的闭包被封装进updateFunc,另外还使用了两个Map存储组件id到更新函数以及组件id到组件实例的映射。这两个Map用于状态变化时找到组件并更新组件
create
TextModel::Create
看一下Text的create方法,最终实现调用了 TextModel::GetInstance()->Create(data);
TextModelNG::Create
TextModel的实际实现根据条件可能是TextModelNG或TextModel,我们进入TextModelNG,里面逻辑会根据节点id判断是否已经有对应组件,没有才进行创建。之后将这个节点进行了入栈
很简单,就是一个入栈操作
pop
JSContainerBase Pop方法
Pop方法位于JSText的父类 JSContainerBase 中
ViewStackModel::PopContainer
Pop方法是对 ViewStackModel::GetInstance()->PopContainer(); 的包装
在节点是AtomicNode的时候一直出栈,直到栈中只保留一个节点
isAtomicNode也有解释,是指button、image、自定义组件等等,只有Create函数没有Pop函数
ViewStackProcessor::Pop
ViewStackProcessor::Finish
小结
- 任何组件的创建函数 都被包裹在 observeComponentCreation2 函数中,这个函数内部会将组件创建函数包装成一个updateFunc函数并存储起来,方便状态变量变化时取到组件实例,进而更新UI
- create 方法会将 UI 节点入栈;
- pop 方法会将 UI 节点出栈,且将该节点挂载到栈顶节点下;
- 通过 create 和 pop 方法,以及编译期间的层级关系,即可构造出一颗 UI 树
ViewPU简析
js 代码解读复制代码 /*
* Copyright (c) 2022-2024 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.
*
* * ViewPU - View for Partial Update
*
* all definitions in this file are framework internal
*/
type DFXCommand = { what: string, viewId: number, isRecursive: boolean };
type RecycleUpdateFunc = (elmtId: number, isFirstRender: boolean, recycleNode: ViewPU) => void;
abstract class ViewPU extends PUV2ViewBase
implements IViewPropertiesChangeSubscriber, IView {
// flag for initial rendering or re-render on-going.
// 正在渲染的标记位
private isRenderInProgress: boolean = false;
// flag for initial rendering being done
// 首次渲染完成的标记位
private isInitialRenderDone: boolean = false;
private runReuse_: boolean = false;
private paramsGenerator_: () => Object;
/**
* 按照Map保存Watch的回调函数,key是回调函数名,value是回调函数
* 比如
* @State @Watch('onShowModeChange') showMode: number = 0
* onShowModeChange() {
* console.info("showMode changed")
* }
* 则watchedProps = {'onShowModeChange' : onShowModeChange}
**/
private watchedProps: Map(propName: string) => void>
= new Map(propName: string) => void>();
private recycleManager_: RecycleManager = undefined;
// @Provide'd variables by this class and its ancestors
/**
* 本类以及祖先提供的Provide变量,和watchedProps类似使用Map存储
* @Provide('hhhh') a: boolean = false
* providedVars_ = {'hhh': ObservedPropertyAbstractPU}
**/
protected providedVars_: MapObservedPropertyAbstractPU > = new MapObservedPropertyAbstractPU>();
// Set of dependent elmtIds that need partial update
// during next re-render
// 在下次重新渲染时需要部分更新的依赖elmtid集合
protected dirtDescendantElementIds_: Set = new Set();
// my LocalStorage instance, shared with ancestor Views.
// create a default instance on demand if none is initialized
// LocalStorage实例,与祖先视图共享。如果没有初始化,则根据需要创建一个默认实例
// localStorage_ 的 getter中使用
protected localStoragebackStore_: LocalStorage = undefined;
// 存储可观测的状态变量,ownObservedPropertiesStore_的getter中使用
private ownObservedPropertiesStore__?: Set<ObservedPropertyAbstractPU>;
private get ownObservedPropertiesStore_() {
if (!this.ownObservedPropertiesStore__) {
// lazy init
this.ownObservedPropertiesStore__ = new Set<ObservedPropertyAbstractPU>();
this.obtainOwnObservedProperties();
}
return this.ownObservedPropertiesStore__;
}
// 获取本组件拥有的可观测属性
protected obtainOwnObservedProperties(): void {
let usesStateMgmtVersion = 0;
Object.getOwnPropertyNames(this)
.filter((propName) => {
// 以__开头,同时不以__ob_、___comp_、___watch_开头的属性
// do not include backing store, and ObserveV2/MonitorV2/ComputedV2 meta data objects
return (propName.startsWith('__') &&
!propName.startsWith(ObserveV2.OB_PREFIX) &&
!propName.startsWith(MonitorV2.WATCH_PREFIX) &&
!propName.startsWith(ComputedV2.COMPUTED_PREFIX));
})
.forEach((propName) => {
const stateVar = Reflect.get(this, propName) as Object;
// 存储 类型是object并且有notifyPropertyHasChangedPU属性的状态变量
if (stateVar && typeof stateVar === 'object' && 'notifyPropertyHasChangedPU' in stateVar) {
stateMgmtConsole.debug(`... add state variable ${propName} to ${stateVar}`);
this.ownObservedPropertiesStore_.add(stateVar as unknown as ObservedPropertyAbstractPU);
usesStateMgmtVersion = 2;
} else {
stateMgmtConsole.debug(`${this.debugInfo__()} ${propName} application may use an unregular naming style, or stateVar may be Non-Object.`);
}
});
if (this.isViewV3 == true) {
if (usesStateMgmtVersion == 2) {
// 不能在V3版本的View中使用V2版本的状态管理
const error = `${this.debugInfo__()}: mixed use of stateMgmt V2 and V3 variable decorators. Application error!`;
stateMgmtConsole.applicationError(error);
throw new Error(error);
}
}
stateMgmtConsole.debug(`${this.debugInfo__()}: uses stateMgmt version ${this.isViewV3 == true ? 3 : 2}`);
}
public get localStorage_(): LocalStorage {
// 如果父类有localStorage_就是用父类的
if (!this.localStoragebackStore_ && this.getParent()) {
stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: get localStorage_ : Using LocalStorage instance of the parent View.`);
this.localStoragebackStore_ = this.getParent().localStorage_;
}
// 如果父类没有localStorage_,创建一个空的Storage
if (!this.localStoragebackStore_) {
stateMgmtConsole.info(`${this.debugInfo__()}: constructor: is accessing LocalStorage without being provided an instance. Creating a default instance.`);
this.localStoragebackStore_ = new LocalStorage({ /* empty */ });
}
return this.localStoragebackStore_;
}
// localStorage_ 的 setter方法,做一些重复赋值判断逻辑
public set localStorage_(instance: LocalStorage) {
if (!instance) {
// setting to undefined not allowed
return;
}
if (this.localStoragebackStore_) {
stateMgmtConsole.applicationError(`${this.debugInfo__()}: constructor: is setting LocalStorage instance twice. Application error.`);
}
this.localStoragebackStore_ = instance;
}
// FIXME
// 指示这是 V2 还是 V3 组件
// indicate if this is V2 or a V3 component
// 默认为 V2,在使用 instanceOf 拆分 ViewPU 和 ViewV3 时,遇到第一个 V3 装饰器变量时变为 V3
// V2 by default, changed to V3 by the first V3 decorated variable
// when splitting ViewPU and ViewV3
// 在此之前,这是一个解决方案
// use instanceOf. Until then, this is a workaround.
// @state, @track 等 V3 装饰器函数会修改 isViewV3 为 true (装饰器可以修改原型中的函数)
// @state, @track, etc V3 decorator functions modify isViewV3 to return true
// (decorator can modify functions in prototype)
// FIXME
private get isViewV3(): boolean {
return false;
}
/**
* Create a View
*
* 1. option: top level View, specify
* - compilerAssignedUniqueChildId must specify
* - parent=undefined
* - localStorage must provide if @LocalSTorageLink/Prop variables are used
* in this View or descendant Views.
*
* 2. option: not a top level View
* - compilerAssignedUniqueChildId must specify
* - parent must specify
* - localStorage do not specify, will inherit from parent View.
*
*/
/**
创建一个 View
选项:顶级 View,需要指定
必须指定 compilerAssignedUniqueChildId
parent=undefined
如果在此 View 或子 View 中使用了 @LocalSTorageLink/Prop 变量,则必须提供 localStorage
选项:非顶级 View
必须指定 compilerAssignedUniqueChildId
必须指定 parent
不需要指定 localStorage,将从父 View 继承。
*/
constructor(parent: IView, localStorage: LocalStorage, elmtId: number = UINodeRegisterProxy.notRecordingDependencies, extraInfo: ExtraInfo = undefined) {
super(parent, elmtId, extraInfo);
// if set use the elmtId also as the ViewPU object's subscribable id.
// these matching is requirement for updateChildViewById(elmtId) being able to
// find the child ViewPU object by given elmtId
// 如果设置了将 elmtId 用作 ViewPU 对象的可订阅 id。 updateChildViewById(elmtId) 通过给定的 elmtId 找到子 ViewPU 对象需满足下面的匹配关系
//this.id_ = elmtId == UINodeRegisterProxy.notRecordingDependencies ? SubscriberManager.MakeId() : elmtId;
this.localStoragebackStore_ = undefined;
stateMgmtConsole.debug(`ViewPU constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}'`);
// 传递了localStorage则使用传递的localStorage
if (localStorage) {
this.localStorage_ = localStorage;
stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: Using LocalStorage instance provided via @Entry or view instance creation.`);
}
// 将此组件添加到 SubscriberManager
SubscriberManager.Add(this);
stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: done`);
}
// inform the subscribed property
// that the View and thereby all properties
// are about to be deleted
// 通知已订阅的属性, 即将删除View 以及所有相关的属性
abstract aboutToBeDeleted(): void;
aboutToReuse(params: Object): void { }
aboutToRecycle(): void { }
// super class will call this function from
// its aboutToBeDeleted implementation
// 父类的aboutToBeDeleted 实现中会调用此函数
protected aboutToBeDeletedInternal(): void {
stateMgmtConsole.debug(`${this.debugInfo__()}: aboutToBeDeletedInternal`);
// if this isDeleting_ is true already, it may be set delete status recursively by its parent, so it is not necessary
// to set and recursively set its children any more
// 如果this isDeleting_已经为真,它可以被父节点递归地设置为删除状态,所以没有必要再递归地设置它的子节点
if (!this.isDeleting_) {
this.isDeleting_ = true;
this.setDeleteStatusRecursively();
}
// tell UINodeRegisterProxy that all elmtIds under
// this ViewPU should be treated as already unregistered
// 告诉UINodeRegisterProxy, 这个ViewPU下的所有elmtid应该被视为已经未注册
stateMgmtConsole.debug(`${this.constructor.name}: aboutToBeDeletedInternal `);
// purge the elmtIds owned by this viewPU from the updateFuncByElmtId and also the state variable dependent elmtIds
// 从updateFuncByElmtId和依赖于状态变量的elmtid中清除这个viewPU拥有的elmtid。即我们之前提到的两个Map
Array.from(this.updateFuncByElmtId.keys()).forEach((elmtId: number) => {
this.purgeDeleteElmtId(elmtId);
})
if (this.hasRecycleManager()) {
this.getRecycleManager().purgeAllCachedRecycleNode();
}
// un-registration of ElementIDs
stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID`);
// it will unregister removed elmtIds from all ViewPu, equals purgeDeletedElmtIdsRecursively
// 它将注销从所有ViewPu中删除的elmtIds,等于purgedeletedelmtids递归
this.purgeDeletedElmtIds();
// 一旦它的子元素被取消注册 就 取消注册它自己的id
// un-registers its own id once its children are unregistered above
//FIXME: Uncomment once photos app avoids rerendering of removed elementIds
//UINodeRegisterProxy unregisterRemovedElmtsFromViewPUs([this id__()]);
stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID - DONE`);
// in case this ViewPU is currently frozen
// 如果这个ViewPU当前处于冻结状态。就从inactiveComponents_移除
PUV2ViewBase.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
// FIXME needed ?
MonitorV2.clearWatchesFromTarget(this);
// 清空 更新函数map,watched变量,provided的状态变量,本组件使用的状态变量
this.updateFuncByElmtId.clear();
this.watchedProps.clear();
this.providedVars_.clear();
if (this.ownObservedPropertiesStore__) {
this.ownObservedPropertiesStore__.clear();
}
// 从父组件中移除自身
if (this.getParent()) {
this.getParent().removeChild(this);
}
// localStorage置空
this.localStoragebackStore_ = undefined;
}
protected debugInfoStateVars(): string {
let result: string = `|--${this.constructor.name}[${this.id__()}]`;
Object.getOwnPropertyNames(this)
.filter((varName: string) => varName.startsWith('__') && !varName.startsWith(ObserveV2.OB_PREFIX))
.forEach((varName) => {
const prop: any = Reflect.get(this, varName);
if ('debugInfoDecorator' in prop) {
const observedProp = prop as ObservedPropertyAbstractPU;
result += `\n ${observedProp.debugInfoDecorator()} '${observedProp.info()}'[${observedProp.id__()}]`;
result += `\n ${observedProp.debugInfoSubscribers()}`;
result += `\n ${observedProp.debugInfoSyncPeers()}`;
result += `\n ${observedProp.debugInfoDependentElmtIds()}`;
result += `\n ${observedProp.debugInfoDependentComponents()}`;
}
});
return result;
}
/**
* 当相应的CustomNode的活动状态发生变化时,ArkUI引擎将调用该函数
* ArkUI engine will call this function when the corresponding CustomNode's active status change.
* @param active为active,为false为inactive
* @param active true for active, false for inactive
*/
public setActiveInternal(active: boolean): void {
stateMgmtProfiler.begin('ViewPU.setActive');
// 如果没有指定 freezeWhenInactive 不做处理
if (!this.isCompFreezeAllowed()) {
stateMgmtConsole.debug(`${this.debugInfo__()}: ViewPU.setActive. Component freeze state is ${this.isCompFreezeAllowed()} - ignoring`);
stateMgmtProfiler.end();
return;
}
stateMgmtConsole.debug(`${this.debugInfo__()}: ViewPU.setActive ${active ? ' inActive -> active' : 'active -> inActive'}`);
this.isActive_ = active;
if (this.isActive_) {
this.onActiveInternal();
} else {
this.onInactiveInternal();
}
stateMgmtProfiler.end();
}
// 变为活跃
private onActiveInternal(): void {
if (!this.isActive_) {
return;
}
stateMgmtConsole.debug(`${this.debugInfo__()}: onActiveInternal`);
// 执行延迟的初始化
this.performDelayedUpdate();
// 把不活跃的组件移除
// Remove the active component from the Map for Dfx
ViewPU.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
// 遍历子组件,将子组件置为active
for (const child of this.childrenWeakrefMap_.values()) {
const childViewPU: IView | undefined = child.deref();
if (childViewPU) {
childViewPU.setActiveInternal(this.isActive_);
}
}
}
// 变为不活跃
private onInactiveInternal(): void {
if (this.isActive_) {
return;
}
stateMgmtConsole.debug(`${this.debugInfo__()}: onInactiveInternal`);
// 设置本组件依赖的状态变量 开启延迟通知
for (const stateLinkProp of this.ownObservedPropertiesStore_) {
stateLinkProp.enableDelayedNotification();
}
// 将本组件不活跃标志添加进去
// Add the inactive Components to Map for Dfx listing
ViewPU.inactiveComponents_.add(`${this.constructor.name}[${this.id__()}]`);
// 将子组件设置为不活跃
for (const child of this.childrenWeakrefMap_.values()) {
const childViewPU: IView | undefined = child.deref();
if (childViewPU) {
childViewPU.setActiveInternal(this.isActive_);
}
}
}
// abstract functions to be implemented by application defined class / transpiled code
// 清空某个组件依赖的状态变量
protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number);
// 初次渲染
protected abstract initialRender(): void;
// 重新渲染
protected abstract rerender(): void;
// 更新回收利用
public abstract updateRecycleElmtId(oldElmtId: number, newElmtId: number): void;
// 更新状态变量
public abstract updateStateVars(params: Object);
// 初始化渲染View,里面初始化一些变量和标志位
public initialRenderView(): void {
stateMgmtProfiler.begin('ViewPU.initialRenderView');
this.obtainOwnObservedProperties();
this.isRenderInProgress = true;
this.initialRender();
this.isRenderInProgress = false;
this.isInitialRenderDone = true;
stateMgmtProfiler.end();
}
// 更新某个组件
public UpdateElement(elmtId: number): void {
stateMgmtProfiler.begin('ViewPU.UpdateElement');
if (elmtId == this.id__()) {
// do not attempt to update itself.
// a @Prop can add a dependency of the ViewPU onto itself. Ignore it.
stateMgmtProfiler.end();
return;
}
// do not process an Element that has been marked to be deleted
const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
const updateFunc = entry ? entry.getUpdateFunc() : undefined;
if (typeof updateFunc !== 'function') {
stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: update function of elmtId ${elmtId} not found, internal error!`);
} else {
stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${entry.getComponentName()} elmtId ${elmtId} start ...`);
this.isRenderInProgress = true;
stateMgmtProfiler.begin('ViewPU.updateFunc');
// 执行更新函数
updateFunc(elmtId, /* isFirstRender */ false);
stateMgmtProfiler.end();
stateMgmtProfiler.begin('ViewPU.finishUpdateFunc (native)');
// 执行完成更新函数
this.finishUpdateFunc(elmtId);
stateMgmtProfiler.end();
this.isRenderInProgress = false;
stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${entry.getComponentName()} elmtId ${elmtId} - DONE`);
}
stateMgmtProfiler.end();
}
/**
通过执行所有更新函数来强制完成渲染/更新
* force a complete rerender / update by executing all update functions
首先执行常规渲染
* exec a regular rerender first
*
* @param deep recurse all children as well
*
* framework internal functions, apps must not call
*/
public forceCompleteRerender(deep: boolean = false): void {
stateMgmtProfiler.begin('ViewPU.forceCompleteRerender');
stateMgmtConsole.warn(`${this.debugInfo__()}: forceCompleteRerender - start.`);
// see which elmtIds are managed by this View
// and clean up all book keeping for them
// 查看哪些elmtid由这个视图管理,并清除它们的所有记录
this.purgeDeletedElmtIds();
Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId));
if (deep) {
this.childrenWeakrefMap_.forEach((weakRefChild: WeakRef ) => {
const child = weakRefChild.deref();
if (child) {
if (child instanceof ViewPU) {
child.forceCompleteRerender(true);
} else {
throw new Error('forceCompleteRerender not implemented for ViewV2, yet');
}
}
});
}
stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`);
stateMgmtProfiler.end();
}
/**
通过执行update函数在特定节点上强制完成渲染/更新
* force a complete rerender / update on specific node by executing update function.
*
* @param elmtId which node needs to update.
*
* framework internal functions, apps must not call
*/
public forceRerenderNode(elmtId: number): void {
stateMgmtProfiler.begin('ViewPU.forceRerenderNode');
// see which elmtIds are managed by this View
// and clean up all book keeping for them
// 查看哪些elmtid由这个视图管理,并清除它们的所有记录
this.purgeDeletedElmtIds();
this.UpdateElement(elmtId);
// remove elemtId from dirtDescendantElementIds.
this.dirtDescendantElementIds_.delete(elmtId);
stateMgmtProfiler.end();
}
// implements IMultiPropertiesChangeSubscriber
// View属性变化时的回调
viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set): void {
stateMgmtProfiler.begin('ViewPU.viewPropertyHasChanged');
aceTrace.begin('ViewPU.viewPropertyHasChanged', this.constructor.name, varName, dependentElmtIds.size);
if (this.isRenderInProgress) {
stateMgmtConsole.applicationError(`${this.debugInfo__()}: State variable '${varName}' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!`);
}
this.syncInstanceId();
// 如果有依赖的视图id,并且非首次渲染。
if (dependentElmtIds.size && !this.isFirstRender()) {
if (!this.dirtDescendantElementIds_.size && !this.runReuse_) {
// 在添加第一个elmtid时将ComposedElement标记为dirty 不需要每次都这样做
// mark ComposedElement dirty when first elmtIds are added
// do not need to do this every time
this.markNeedUpdate();
}
stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged property: elmtIds that need re-render due to state variable change: ${this.debugInfoElmtIds(Array.from(dependentElmtIds))} .`);
// 如果有recycleManager,将id代理后添加到dirtDescendantElementIds_中,否则直接添加到dirtDescendantElementIds_中
for (const elmtId of dependentElmtIds) {
if (this.hasRecycleManager()) {
this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId));
} else {
this.dirtDescendantElementIds_.add(elmtId);
}
}
stateMgmtConsole.debug(` ... updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
} else {
stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged: state variable change adds no elmtIds for re-render`);
stateMgmtConsole.debug(` ... unchanged full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
}
// 获取这个状态变量对应的Watch函数,执行
let cb = this.watchedProps.get(varName)
if (cb && typeof cb === 'function') {
stateMgmtConsole.debug(` ... calling @Watch function`);
cb.call(this, varName);
}
this.restoreInstanceId();
aceTrace.end();
stateMgmtProfiler.end();
}
/**
通知特定elmtId的组件重新渲染,但是不执行watch函数
* inform that UINode with given elmtId needs rerender
* does NOT exec @Watch function.
* only used on V3 code path from ObserveV2.fireChange.
*
* FIXME will still use in the future?
*/
public uiNodeNeedUpdateV3(elmtId: number): void {
if (this.isFirstRender()) {
return;
}
stateMgmtProfiler.begin(`ViewPU.uiNodeNeedUpdate ${this.debugInfoElmtId(elmtId)}`);
if (!this.dirtDescendantElementIds_.size) { // && !this runReuse_) {
// mark ComposedElement dirty when first elmtIds are added
// do not need to do this every time
this.syncInstanceId();
this.markNeedUpdate();
this.restoreInstanceId();
}
if (this.hasRecycleManager()) {
this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId));
} else {
this.dirtDescendantElementIds_.add(elmtId);
}
stateMgmtConsole.debug(`${this.debugInfo__()}: uiNodeNeedUpdate: updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
stateMgmtProfiler.end();
}
// 执行延迟更新
private performDelayedUpdate(): void {
if (!this.ownObservedPropertiesStore_.size) {
return;
}
stateMgmtProfiler.begin('ViewPU.performDelayedUpdate');
aceTrace.begin('ViewPU.performDelayedUpdate', this.constructor.name);
stateMgmtConsole.debug(`${this.debugInfo__()}: performDelayedUpdate start ...`);
this.syncInstanceId();
// 遍历本组件拥有的状态变量,拿到依赖这个状态变量的自足缉拿,进行刷新
for (const stateLinkPropVar of this.ownObservedPropertiesStore_) {
const changedElmtIds = stateLinkPropVar.moveElmtIdsForDelayedUpdate();
if (changedElmtIds) {
const varName = stateLinkPropVar.info();
if (changedElmtIds.size && !this.isFirstRender()) {
for (const elmtId of changedElmtIds) {
this.dirtDescendantElementIds_.add(elmtId);
}
}
stateMgmtConsole.debug(`${this.debugInfo__()}: performDelayedUpdate: all elmtIds that need re-render [${Array.from(this.dirtDescendantElementIds_).toString()}].`);
// 获取这个状态变量的watch,调用
const cb = this.watchedProps.get(varName);
if (cb) {
stateMgmtConsole.debug(` ... calling @Watch function`);
cb.call(this, varName);
}
}
} // for all ownStateLinkProps_
this.restoreInstanceId();
if (this.dirtDescendantElementIds_.size) {
this.markNeedUpdate();
}
aceTrace.end()
stateMgmtProfiler.end();
}
/**
从子组件的构造函数*调用的函数,以注册一个@Watch变量
* Function to be called from the constructor of the sub component
* to register a @Watch variable
变量名,而不是别名
* @param propStr name of the variable. Note from @Provide and @Consume this is
* the variable name and not the alias!
* @param callback application defined member function of sub-class
*/
protected declareWatch(propStr: string, callback: (propName: string) => void): void {
// 存起来
this.watchedProps.set(propStr, callback);
}
/**
添加一个Provide状态变量
* This View @Provide's a variable under given name
* Call this function from the constructor of the sub class
* @param providedPropName either the variable name or the alias defined as
* decorator param
* @param store the backing store object for this variable (not the get/set variable!)
*/
protected addProvidedVar(providedPropName: string, store: ObservedPropertyAbstractPU, allowOverride: boolean = false) {
if (!allowOverride && this.findProvidePU(providedPropName)) {
throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}. Property with this name is provided by one of the ancestor Views already. @Provide override not allowed.`);
}
// 设置装饰器
store.setDecoratorInfo('@Provide');
// 存起来
this.providedVars_.set(providedPropName, store);
}
/*
从本组件开始递归查找Provided属性,没有就继续从父组件查找
findProvidePU finds @Provided property recursively by traversing ViewPU's towards that of the UI tree root @Component:
if 'this' ViewPU has a @Provide('providedPropName') return it, otherwise ask from its parent ViewPU.
*/
public findProvidePU(providedPropName: string): ObservedPropertyAbstractPU | undefined {
return this.providedVars_.get(providedPropName) || (this.parent_ && this.parent_.findProvidePU(providedPropName));
}
/**
* Method for the sub-class to call from its constructor for resolving
* a @Consume variable and initializing its backing store
* with the SyncedPropertyTwoWay object created from the
* @Provide variable's backing store.
* @param providedPropName the name of the @Provide'd variable.
* This is either the @Consume decorator parameter, or variable name.
* @param consumeVarName the @Consume variable name (not the
* @Consume decorator parameter)
* @returns initializing value of the @Consume backing store
*/
protected initializeConsume(providedPropName: string,
consumeVarName: string): ObservedPropertyAbstractPU {
let providedVarStore: ObservedPropertyAbstractPU = this.findProvidePU(providedPropName);
if (providedVarStore === undefined) {
throw new ReferenceError(`${this.debugInfo__()} missing @Provide property with name ${providedPropName}.
Fail to resolve @Consume(${providedPropName}).`);
}
const factory = (source: ObservedPropertyAbstract ) => {
const result: ObservedPropertyAbstractPU = new SynchedPropertyTwoWayPU(source, this, consumeVarName);
result.setDecoratorInfo('@Consume');
stateMgmtConsole.debug(`The @Consume is instance of ${result.constructor.name}`);
return result;
};
return providedVarStore.createSync(factory) as ObservedPropertyAbstractPU;
}
/**
* given the elmtId of a child or child of child within this custom component
* remember this component needs a partial update
* @param elmtId
*/
public markElemenDirtyById(elmtId: number): void {
// TODO ace-ets2bundle, framework, compiled apps need to update together
// this function will be removed after a short transition period
stateMgmtConsole.applicationError(`${this.debugInfo__()}: markElemenDirtyById no longer supported.
Please update your ace-ets2bundle and recompile your application. Application error!`);
}
/**
* For each recorded dirty Element in this custom component
* run its update function
*
*/
public updateDirtyElements(): void {
stateMgmtProfiler.begin('ViewPU.updateDirtyElements');
do {
stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render): sorted dirty elmtIds: ${Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber)}, starting ....`);
// see which elmtIds are managed by this View
// and clean up all book keeping for them
this.purgeDeletedElmtIds();
// process all elmtIds marked as needing update in ascending order.
// ascending order ensures parent nodes will be updated before their children
// prior cleanup ensure no already deleted Elements have their update func executed
const dirtElmtIdsFromRootNode = Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber);
// if state changed during exec update lambda inside UpdateElement, then the dirty elmtIds will be added
// to newly created this.dirtDescendantElementIds_ Set
dirtElmtIdsFromRootNode.forEach(elmtId => {
if (this.hasRecycleManager()) {
this.UpdateElement(this.recycleManager_.proxyNodeId(elmtId));
} else {
this.UpdateElement(elmtId);
}
this.dirtDescendantElementIds_.delete(elmtId);
});
if (this.dirtDescendantElementIds_.size) {
stateMgmtConsole.applicationError(`${this.debugInfo__()}: New UINode objects added to update queue while re-render! - Likely caused by @Component state change during build phase, not allowed. Application error!`);
}
} while (this.dirtDescendantElementIds_.size);
stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render) - DONE, dump of ViewPU in next lines`);
stateMgmtProfiler.end();
}
// executed on first render only
// kept for backward compatibility with old ace-ets2bundle
public observeComponentCreation(compilerAssignedUpdateFunc: UpdateFunc): void {
if (this.isDeleting_) {
stateMgmtConsole.error(`View ${this.constructor.name} elmtId ${this.id__()} is already in process of destruction, will not execute observeComponentCreation `);
return;
}
const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} start ....`);
this.currentlyRenderedElmtIdStack_.push(elmtId);
compilerAssignedUpdateFunc(elmtId, isFirstRender);
this.currentlyRenderedElmtIdStack_.pop();
stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} - DONE ....`);
}
const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
// in observeComponentCreation function we do not get info about the component name, in
// observeComponentCreation2 we do.
this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc });
// add element id -> owning ViewPU
UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
try {
updateFunc(elmtId, /* is first render */ true);
} catch (error) {
// avoid the incompatible change that move set function before updateFunc.
this.updateFuncByElmtId.delete(elmtId);
UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in update func: ${(error as Error).message}`);
throw error;
}
}
public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: UIClassObject): void {
if (this.isDeleting_) {
stateMgmtConsole.error(`View ${this.constructor.name} elmtId ${this.id__()} is already in process of destruction, will not execute observeComponentCreation2 `);
return;
}
// 组件名称
const _componentName: string = (classObject && ('name' in classObject)) ? Reflect.get(classObject, 'name') as string : 'unspecified UINode';
// pop函数
const _popFunc: () => void = (classObject && 'pop' in classObject) ? classObject.pop! : (): void => { };
// 更新函数
const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
this.syncInstanceId();
stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} ${_componentName}[${elmtId}] ${!this.isViewV3 ? '(enable PU state observe) ' : ''} ${ConfigureStateMgmt.instance.needsV2Observe() ? '(enabled V2 state observe) ' : ''} - start ....`);
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
if (!this.isViewV3) {
// Enable PU state tracking only in PU @Components
this.currentlyRenderedElmtIdStack_.push(elmtId);
}
// if V2 @Observed/@Track used anywhere in the app (there is no more fine grained criteria),
// enable V2 object deep observation
// FIXME: A @Component should only use PU or V2 state, but ReactNative dynamic viewer uses both.
if (this.isViewV3 || ConfigureStateMgmt.instance.needsV2Observe()) {
// FIXME: like in V2 setting bindId_ in ObserveV2 does not work with 'stacked'
// update + initial render calls, like in if and ForEach case, convert to stack as well
ObserveV2.getObserve().startRecordDependencies(this, elmtId);
}
compilerAssignedUpdateFunc(elmtId, isFirstRender);
if (!isFirstRender) {
_popFunc();
}
let node = this.getNodeById(elmtId);
if (node !== undefined) {
(node as ArkComponent).cleanStageValue();
}
if (this.isViewV3 || ConfigureStateMgmt.instance.needsV2Observe()) {
ObserveV2.getObserve().stopRecordDependencies();
}
if (!this.isViewV3) {
this.currentlyRenderedElmtIdStack_.pop();
}
ViewStackProcessor.StopGetAccessRecording();
stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} ${_componentName}[${elmtId}] - DONE ....`);
this.restoreInstanceId();
};
// 生成一个唯一的elmtId
const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
// needs to move set before updateFunc.
// make sure the key and object value exist since it will add node in attributeModifier during updateFunc.
// 存储组件id到组件的更新函数
this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc, classObject: classObject });
// add element id -> owning ViewPU
// 存储组件id到组件自身
UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
try {
updateFunc(elmtId, /* is first render */ true);
} catch (error) {
// avoid the incompatible change that move set function before updateFunc.
this.updateFuncByElmtId.delete(elmtId);
UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in update func: ${(error as Error).message}`);
throw error;
}
stateMgmtConsole.debug(`${this.debugInfo__()} is initial rendering elmtId ${elmtId}, tag: ${_componentName}, and updateFuncByElmtId size :${this.updateFuncByElmtId.size}`);
}
getOrCreateRecycleManager(): RecycleManager {
if (!this.recycleManager_) {
this.recycleManager_ = new RecycleManager;
}
return this.recycleManager_;
}
getRecycleManager(): RecycleManager {
return this.recycleManager_;
}
hasRecycleManager(): boolean {
return !(this.recycleManager_ === undefined);
}
initRecycleManager(): void {
if (this.recycleManager_) {
stateMgmtConsole.error(`${this.debugInfo__()}: init recycleManager multiple times. Internal error.`);
return;
}
this.recycleManager_ = new RecycleManager;
}
rebuildUpdateFunc(elmtId, compilerAssignedUpdateFunc): void {
const updateFunc = (elmtId, isFirstRender): void => {
this.currentlyRenderedElmtIdStack_.push(elmtId);
compilerAssignedUpdateFunc(elmtId, isFirstRender);
this.currentlyRenderedElmtIdStack_.pop();
};
if (this.updateFuncByElmtId.has(elmtId)) {
this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc });
}
}
/**
* @function observeRecycleComponentCreation
* @description custom node recycle creation
* @param name custom node name
* @param recycleUpdateFunc custom node recycle update which can be converted to a normal update function
* @return void
*/
public observeRecycleComponentCreation(name: string, recycleUpdateFunc: RecycleUpdateFunc): void {
// convert recycle update func to update func
const compilerAssignedUpdateFunc: UpdateFunc = (element, isFirstRender) => {
recycleUpdateFunc(element, isFirstRender, undefined);
};
let node: ViewPU;
// if there is no suitable recycle node, run a normal creation function.
if (!this.hasRecycleManager() || !(node = this.getRecycleManager().popRecycleNode(name))) {
stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: cannot init node by recycle, crate new node`);
this.observeComponentCreation(compilerAssignedUpdateFunc);
return;
}
// if there is a suitable recycle node, run a recycle update function.
const newElmtId: number = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
const oldElmtId: number = node.id__();
this.recycleManager_.updateNodeId(oldElmtId, newElmtId);
this.addChild(node);
this.rebuildUpdateFunc(oldElmtId, compilerAssignedUpdateFunc);
recycleUpdateFunc(oldElmtId, /* is first render */ true, node);
}
// 组件重用 是指用状态变量更新同一个组件
aboutToReuseInternal() {
this.runReuse_ = true;
stateMgmtTrace.scopedTrace(() => {
if (this.paramsGenerator_ && typeof this.paramsGenerator_ === 'function') {
const params = this.paramsGenerator_();
this.updateStateVars(params);
this.aboutToReuse(params);
}
}, "aboutToReuse", this.constructor.name);
// 组件重用时,遍历拥有的状态变量,更新子组件
for (const stateLinkPropVar of this.ownObservedPropertiesStore_) {
const changedElmtIds = stateLinkPropVar.moveElmtIdsForDelayedUpdate(true);
if (changedElmtIds) {
if (changedElmtIds.size && !this.isFirstRender()) {
for (const elmtId of changedElmtIds) {
this.dirtDescendantElementIds_.add(elmtId);
}
}
}
}
this.updateDirtyElements();
this.childrenWeakrefMap_.forEach((weakRefChild) => {
const child = weakRefChild.deref();
if (child) {
if (child instanceof ViewPU) {
child.aboutToReuseInternal();
} else {
// FIXME fix for mixed V2 - V3 Hierarchies
throw new Error('aboutToReuseInternal: Recycle not implemented for ViewV2, yet');
}
} // if child
});
this.runReuse_ = false;
}
// 复用
aboutToRecycleInternal() {
this.runReuse_ = true;
stateMgmtTrace.scopedTrace(() => {
this.aboutToRecycle();
}, 'aboutToRecycle', this.constructor.name);
this.childrenWeakrefMap_.forEach((weakRefChild) => {
const child = weakRefChild.deref();
if (child) {
if (child instanceof ViewPU) {
child.aboutToRecycleInternal();
} else {
// FIXME fix for mixed V2 - V3 Hierarchies
throw new Error('aboutToRecycleInternal: Recycle not yet implemented for ViewV2');
}
} // if child
});
this.runReuse_ = false;
}
// add current JS object to it's parent recycle manager
public recycleSelf(name: string): void {
if (this.getParent() && this.getParent() instanceof ViewPU && !(this.getParent() as ViewPU).isDeleting_) {
const parentPU : ViewPU = this.getParent() as ViewPU;
parentPU.getOrCreateRecycleManager().pushRecycleNode(name, this);
this.parent_.removeChild(this);
this.setActiveInternal(false);
} else {
this.resetRecycleCustomNode();
stateMgmtConsole.error(`${this.constructor.name}[${this.id__()}]: recycleNode must have a parent`);
}
}
public UpdateLazyForEachElements(elmtIds: Array): void {
if (!Array.isArray(elmtIds)) {
return;
}
Array.from(elmtIds).sort(ViewPU.compareNumber).forEach((elmtId: number) => {
const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
const updateFunc: UpdateFunc = entry ? entry.getUpdateFunc() : undefined;
if (typeof updateFunc !== 'function') {
stateMgmtConsole.debug(`${this.debugInfo__()}: update function of elmtId ${elmtId} not found, internal error!`);
} else {
this.isRenderInProgress = true;
updateFunc(elmtId, false);
this.finishUpdateFunc(elmtId);
this.isRenderInProgress = false;
}
})
}
/**
创建@StorageLink和@LocalStorageLink的状态变量
* CreateStorageLink and CreateStorageLinkPU are used by the implementation of @StorageLink and
* @LocalStotrageLink in full update and partial update solution respectively.
* These are not part of the public AppStorage API , apps should not use.
在LocalStorage中的键
* @param storagePropName - key in LocalStorage LocalStorage中的key
在LocalStorage中创建新属性时使用的值
* @param defaultValue - value to use when creating a new prop in the LocalStotage
拥有@StorageLink/@LocalStorageLink变量的视图/视图PU
* @param owningView - the View/ViewPU owning the @StorageLink/@LocalStorageLink variable
StorageLink/@LocalStorageLink变量名
* @param viewVariableName - @StorageLink/@LocalStorageLink variable name
* @returns SynchedPropertySimple/ObjectTwoWay/PU
*/
public createStorageLink(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU {
const appStorageLink = AppStorage.__createSync(storagePropName, defaultValue,
(source: ObservedPropertyAbstract ) => (source === undefined)
? undefined
: new SynchedPropertyTwoWayPU(source, this, viewVariableName)
) as ObservedPropertyAbstractPU;
appStorageLink.setDecoratorInfo('@StorageLink');
return appStorageLink;
}
public createStorageProp(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU {
const appStorageProp = AppStorage.__createSync(storagePropName, defaultValue,
(source: ObservedPropertyAbstract ) => (source === undefined)
? undefined
: new SynchedPropertyOneWayPU(source, this, viewVariableName)
) as ObservedPropertyAbstractPU;
appStorageProp.setDecoratorInfo('@StorageProp');
return appStorageProp;
}
public createLocalStorageLink(storagePropName: string, defaultValue: T,
viewVariableName: string): ObservedPropertyAbstractPU {
const localStorageLink = this.localStorage_.__createSync(storagePropName, defaultValue,
(source: ObservedPropertyAbstract ) => (source === undefined)
? undefined
: new SynchedPropertyTwoWayPU(source, this, viewVariableName)
) as ObservedPropertyAbstractPU;
localStorageLink.setDecoratorInfo('@LocalStorageLink');
return localStorageLink;
}
public createLocalStorageProp(storagePropName: string, defaultValue: T,
viewVariableName: string): ObservedPropertyAbstractPU {
const localStorageProp = this.localStorage_.__createSync(storagePropName, defaultValue,
(source: ObservedPropertyAbstract ) => (source === undefined)
? undefined
: new SynchedPropertyObjectOneWayPU(source, this, viewVariableName)
) as ObservedPropertyAbstractPU;
localStorageProp.setDecoratorInfo('@LocalStorageProp');
return localStorageProp;
}
/**
* onDumpInfo is used to process commands delivered by the hidumper process
* @param commands - list of commands provided in the shell
* @returns void
*/
protected onDumpInfo(commands: string[]): void {
let dfxCommands: DFXCommand[] = this.processOnDumpCommands(commands);
dfxCommands.forEach((command) => {
let view: ViewPU = undefined;
if (command.viewId) {
view = this.findViewPUInHierarchy(command.viewId);
if (!view) {
DumpLog.print(0, `\nTarget view: ${command.viewId} not found for command: ${command.what}\n`);
return;
}
} else {
view = this;
command.viewId = view.id__();
}
switch (command.what) {
case '-dumpAll':
view.printDFXHeader('ViewPU Info', command);
DumpLog.print(0, view.debugInfoView(command.isRecursive));
break;
case '-viewHierarchy':
view.printDFXHeader('ViewPU Hierarchy', command);
DumpLog.print(0, view.debugInfoViewHierarchy(command.isRecursive));
break;
case '-stateVariables':
view.printDFXHeader('ViewPU State Variables', command);
DumpLog.print(0, view.debugInfoStateVars());
break;
case '-registeredElementIds':
view.printDFXHeader('ViewPU Registered Element IDs', command);
DumpLog.print(0, view.debugInfoUpdateFuncByElmtId(command.isRecursive));
break;
case '-dirtyElementIds':
view.printDFXHeader('ViewPU Dirty Registered Element IDs', command);
DumpLog.print(0, view.debugInfoDirtDescendantElementIds(command.isRecursive));
break;
case '-inactiveComponents':
view.printDFXHeader('List of Inactive Components', command);
DumpLog.print(0, view.debugInfoInactiveComponents());
break;
case '-profiler':
view.printDFXHeader('Profiler Info', command);
view.dumpReport();
this.sendStateInfo('{}');
break;
default:
DumpLog.print(0, `\nUnsupported JS DFX dump command: [${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`);
}
})
}
private printDFXHeader(header: string, command: DFXCommand): void {
let length: number = 50;
let remainder: number = length - header.length < 0 ? 0 : length - header.length;
DumpLog.print(0, `\n${'-'.repeat(remainder / 2)}${header}${'-'.repeat(remainder / 2)}`);
DumpLog.print(0, `[${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`);
}
private processOnDumpCommands(commands: string[]): DFXCommand[] {
let isFlag: Function = (param: string): boolean => {
return '-r'.match(param) != null || param.startsWith('-viewId=');
}
let dfxCommands: DFXCommand[] = [];
for (var i: number = 0; i < commands.length; i++) {
let command = commands[i];
if (isFlag(command)) {
if (command.startsWith('-viewId=')) {
let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1];
if (dfxCommand) {
let input: string[] = command.split('=');
if (input[1]) {
let viewId: number = Number.parseInt(input[1]);
dfxCommand.viewId = Number.isNaN(viewId) ? UINodeRegisterProxy.notRecordingDependencies : viewId;
}
}
} else if (command.match('-r')) {
let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1];
if (dfxCommand) {
dfxCommand.isRecursive = true;
}
}
} else {
dfxCommands.push({
what: command,
viewId: undefined,
isRecursive: false,
});
}
}
return dfxCommands;
}
public findViewPUInHierarchy(id: number): ViewPU {
let weakChild = this.childrenWeakrefMap_.get(id);
if (weakChild) {
const child = weakChild.deref();
// found child with id, is it a ViewPU?
return (child instanceof ViewPU) ? child : undefined;
}
// did not find, continue searching
let retVal: ViewPU = undefined;
for (const [key, value] of this.childrenWeakrefMap_.entries()) {
retVal = value.deref().findViewPUInHierarchy(id);
if (retVal) {
break;
}
}
return retVal;
}
private debugInfoView(recursive: boolean = false): string {
return this.debugInfoViewInternal(recursive);
}
private debugInfoViewInternal(recursive: boolean = false): string {
let retVal: string = `@Component\n${this.constructor.name}[${this.id__()}]`;
retVal += `\n\nView Hierarchy:\n${this.debugInfoViewHierarchy(recursive)}`;
retVal += `\n\nState variables:\n${this.debugInfoStateVars()}`;
retVal += `\n\nRegistered Element IDs:\n${this.debugInfoUpdateFuncByElmtId(recursive)}`;
retVal += `\n\nDirty Registered Element IDs:\n${this.debugInfoDirtDescendantElementIds(recursive)}`;
return retVal;
}
private debugInfoDirtDescendantElementIds(recursive: boolean = false): string {
return this.debugInfoDirtDescendantElementIdsInternal(0, recursive, { total: 0 });
}
public debugInfoDirtDescendantElementIdsInternal(depth: number = 0, recursive: boolean = false, counter: ProfileRecursionCounter): string {
let retVaL: string = `\n${' '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`;
this.dirtDescendantElementIds_.forEach((value) => {
retVaL += `${value}, `;
});
counter.total += this.dirtDescendantElementIds_.size;
retVaL += `\n${' '.repeat(depth + 1)}}[${this.dirtDescendantElementIds_.size}]`;
if (recursive) {
this.childrenWeakrefMap_.forEach((value, key, map) => {
retVaL += value.deref()?.debugInfoDirtDescendantElementIdsInternal(depth + 1, recursive, counter);
})
}
if (recursive && depth == 0) {
retVaL += `\nTotal: ${counter.total}`;
}
return retVaL;
}
/**
* on first render create a new Instance of Repeat
* on re-render connect to existing instance
* @param arr
* @returns
*/
public __mkRepeatAPI: (arr: Array) => RepeatAPI = (arr: Array): RepeatAPI => {
// factory is for future extensions, currently always return the same
const elmtId = this.getCurrentlyRenderedElmtId();
let repeat = this.elmtId2Repeat_.get(elmtId) as __RepeatPU
if (!repeat) {
repeat = new __RepeatPU(this, arr);
this.elmtId2Repeat_.set(elmtId, repeat);
} else {
repeat.updateArr(arr)
}
return repeat;
}
}
状态变量相关
watchedProps
private watchedProps: Map
以Map形式存储@Watch声明的函数,key是传入Watch的字符串函数名,value是对应的函数地址
providedVars_
protected providedVars_: Map
以Map形式存储本组件以及祖先组件中声明的@Provided状态变量,key是传入@Provided的别名(没有别名时使用变量名),value是状态变量
localStoragebackStore_
protected localStoragebackStore_: LocalStorage = undefined;
存储组件的LocalStorage,如果传递了storage就使用传递的storage,否则,先查看父类有没有storage,有就取父类的storage,否则创建一个默认的storage。
也就是说组件一定有一个storage,只是我们不一定能访问
ownObservedPropertiesStore__
private ownObservedPropertiesStore__?: Set
本组件内声明的状态变量
createStorageLink与createLocalStorageLink
AppStorage是LocalStorage的一个子类,并且是一个单例
createStorageProp与createLocalStorageProp
更新相关
updateFuncByElmtId
它是一个类UpdateFuncsByElmtId,其实这个类内部也是使用的Map存储,key是组件id,value是包装后的更新函数
childrenWeakrefMap_
以Map形式存储本组件的子组件,key是组件id,value是组件实例
总结
- ArkTS中所有的自定义组件转换成TS后都是一个继承自ViewPU的子类
- 所有的组件的创建都是通过observeComponentCreation2方法完成
- 组件的创建是一个出入栈的过程,Create方法入栈,Pop方法出栈,栈为空时将该节点挂载到父节点上
鸿蒙中也存在三棵树,可以看官网的一张图 developer.huawei.com/consumer/cn…
参考资料
- 声明式范式的语法编译转换,语法验证等 https://gitee.com/openharmony/developtools_ace_ets2bundle
- ArkUI引擎 https://gitee.com/openharmony/arkui_ace_engine
- ArkUIX gitee.com/arkui-x
- openharmony文档 https://gitee.com/openharmony/docs/tree/master/zh-cn/readme
- flutter android 鸿蒙 linux apple图形渲染比较 https://zhuanlan.zhihu.com/p/622375832
- 渲染控制概述 developer.huawei.com/consumer/cn…
评论记录:
回复评论: