$$ 的两种用途
$$语法:内置组件双向同步
developer.huawei.com/consumer/cn…
@Builder装饰器:自定义构建函数
developer.huawei.com/consumer/cn…
参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:
- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
- 在@Builder修饰的函数内部,不允许改变参数值。
- @Builder内UI语法遵循UI语法规则。
- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递
转换前代码
js 代码解读复制代码/**
*
* TwoWayCmpt.ets
* Created by unravel on 2024/5/3
* @abstract
*/
class Tmp {
paramA1: string = ''
}
// $$ 的两种用法之二,Builder中 按引用传递参数
@Builder
function overBuilder($$: Tmp) {
Row() {
Column() {
Text(`overBuilder===${$$.paramA1}`)
HelloComponent({ message: $$.paramA1 })
}
}
}
@Builder
function overBuilder2(p: Tmp) {
Row() {
Column() {
Text(`overBuilder===${p.paramA1}`)
HelloComponent({ message: p.paramA1 })
}
}
}
@Component
struct HelloComponent {
@Link message: string;
build() {
Row() {
Text(`HelloComponent===${this.message}`)
}
}
}
@Component
export struct TwoWayCmpt {
@State text: string = ''
@State label: string = 'Hello';
controller: TextInputController = new TextInputController()
private aSimpleString: string = ''
// $$ 的两种用法之一,同步系统组件的属性
@Builder
DoubleDollarInput() {
TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
.placeholderColor(Color.Grey)
.placeholderFont({ size: 14, weight: 400 })
.caretColor(Color.Blue)
.width(300)
}
build() {
Column({ space: 20 }) {
Text(this.text)
this.DoubleDollarInput()
overBuilder({ paramA1: this.label })
overBuilder2({ paramA1: this.label })
overBuilder({ paramA1: 'aaaaa' })
overBuilder2({ paramA1: 'bbbbb' })
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
转换后代码
js 代码解读复制代码if (!("finalizeConstruction" in ViewPU.prototype)) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { });
}
interface TwoWayCmpt_Params {
text?: string;
label?: string;
controller?: TextInputController;
aSimpleString?: string;
}
interface HelloComponent_Params {
message?: string;
}
/**
*
* TwoWayCmpt.ets
* Created by unravel on 2024/5/3
* @abstract
*/
class Tmp {
paramA1: string = '';
}
// $$ 的两种用法之二,Builder中 按引用传递参数
function overBuilder($$: Tmp, parent = null) {
const __$$__ = $$;
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, $$ = __$$__) => {
Row.create();
}, Row);
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, $$ = __$$__) => {
Column.create();
}, Column);
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, $$ = __$$__) => {
Text.create(`overBuilder===${$$.paramA1}`);
}, Text);
Text.pop();
{
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, $$ = __$$__) => {
if (isInitialRender) {
let componentCall = new HelloComponent(parent ? parent : this, { message: $$.__paramA1 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/TwoWayCmpt.ets", line: 18 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
message: $$.paramA1
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
(parent ? parent : this).updateStateVarsOfChildByElmtId(elmtId, {});
}
}, { name: "HelloComponent" });
}
Column.pop();
Row.pop();
}
function overBuilder2(p: Tmp, parent = null) {
const __p__ = p;
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, p = __p__) => {
Row.create();
}, Row);
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, p = __p__) => {
Column.create();
}, Column);
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, p = __p__) => {
Text.create(`overBuilder===${p.paramA1}`);
}, Text);
Text.pop();
{
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender, p = __p__) => {
if (isInitialRender) {
let componentCall = new HelloComponent(parent ? parent : this, { message: p.paramA1 }, undefined, elmtId, () => { }, { page: "entry/src/main/ets/pages/TwoWayCmpt.ets", line: 28 });
ViewPU.create(componentCall);
let paramsLambda = () => {
return {
message: p.paramA1
};
};
componentCall.paramsGenerator_ = paramsLambda;
}
else {
(parent ? parent : this).updateStateVarsOfChildByElmtId(elmtId, {});
}
}, { name: "HelloComponent" });
}
Column.pop();
Row.pop();
}
class HelloComponent 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 = new SynchedPropertySimpleTwoWayPU(params.message, this, "message");
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: HelloComponent_Params) {
}
updateStateVars(params: HelloComponent_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__message.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__message.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __message: SynchedPropertySimpleTwoWayPU;
get message() {
return this.__message.get();
}
set message(newValue: string) {
this.__message.set(newValue);
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(`HelloComponent===${this.message}`);
}, Text);
Text.pop();
Row.pop();
}
rerender() {
this.updateDirtyElements();
}
}
export class TwoWayCmpt extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
super(parent, __localStorage, elmtId, extraInfo);
if (typeof paramsLambda === "function") {
this.paramsGenerator_ = paramsLambda;
}
this.__text = new ObservedPropertySimplePU('', this, "text");
this.__label = new ObservedPropertySimplePU('Hello', this, "label");
this.controller = new TextInputController();
this.aSimpleString = '';
this.setInitiallyProvidedValue(params);
this.finalizeConstruction();
}
setInitiallyProvidedValue(params: TwoWayCmpt_Params) {
if (params.text !== undefined) {
this.text = params.text;
}
if (params.label !== undefined) {
this.label = params.label;
}
if (params.controller !== undefined) {
this.controller = params.controller;
}
if (params.aSimpleString !== undefined) {
this.aSimpleString = params.aSimpleString;
}
}
updateStateVars(params: TwoWayCmpt_Params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
this.__text.purgeDependencyOnElmtId(rmElmtId);
this.__label.purgeDependencyOnElmtId(rmElmtId);
}
aboutToBeDeleted() {
this.__text.aboutToBeDeleted();
this.__label.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id__());
this.aboutToBeDeletedInternal();
}
private __text: ObservedPropertySimplePU;
get text() {
return this.__text.get();
}
set text(newValue: string) {
this.__text.set(newValue);
}
private __label: ObservedPropertySimplePU;
get label() {
return this.__label.get();
}
set label(newValue: string) {
this.__label.set(newValue);
}
private controller: TextInputController;
private aSimpleString: string;
// $$ 的两种用法之一,同步系统组件的属性
DoubleDollarInput(parent = null) {
this.observeComponentCreation2((elmtId, isInitialRender) => {
TextInput.create({ text: { value: this.text, changeEvent: newValue => { this.text = newValue; } }, placeholder: 'input your word...', controller: this.controller });
TextInput.placeholderColor(Color.Grey);
TextInput.placeholderFont({ size: 14, weight: 400 });
TextInput.caretColor(Color.Blue);
TextInput.width(300);
}, TextInput);
}
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Column.create({ space: 20 });
Column.width('100%');
Column.height('100%');
Column.justifyContent(FlexAlign.Center);
}, Column);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create(this.text);
}, Text);
Text.pop();
this.DoubleDollarInput.bind(this)();
overBuilder.bind(this)(makeBuilderParameterProxy("overBuilder", { paramA1: () => (this["__label"] ? this["__label"] : this["label"]) }));
overBuilder2.bind(this)(makeBuilderParameterProxy("overBuilder2", { paramA1: () => (this["__label"] ? this["__label"] : this["label"]) }));
overBuilder.bind(this)(makeBuilderParameterProxy("overBuilder", { paramA1: () => 'aaaaa' }));
overBuilder2.bind(this)(makeBuilderParameterProxy("overBuilder2", { paramA1: () => 'bbbbb' }));
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
$$语法:内置组件双向同步
代码转换前后
$$this.text 转换成了 { value: this.text, changeEvent: newValue => { this.text = newValue; } }
传递
$$this.text 转换成了 { value: this.text, changeEvent: newValue => { this.text = newValue; } }
这样状态变量变化时就调用changeEvent,changeEvent内会改变状态变量,状态变量改变从而再驱动UI更新
底层实现
$$只支持特定组件的特定属性的双向绑定。我们就找一下TextInput看 它咋实现的
JSTextInput
所有的系统组件,在底层都对应一个C++的实现类。TextInput也不例外,它对应底层的JSTextInput
JSTextInput::Create
TextInput.create({ text: { value: this.text, changeEvent: newValue => { this.text = newValue; } }
经过转换之后,调用的是JSTextInput的create方法
最终调用到了JSTextFiled的CreateTextInput方法
JSTextField::CreateTextInput
和我们猜想的一样,显示的时候取得value属性
ParseTextFieldTextObject(info, changeEventVal);
最终将changeEvent设置到了onChangeEvent方法的参数里
TextFieldModel::GetInstance()->SetOnChangeEvent
TextFieldModel使用新的渲染管线的时候,实际的类是 TextFieldModelNG
最终是调用的eventHub的setOnChangeEvent
JSTextField::SetOnChange
我们再回头看下TextInput的onChange方法绑定到哪个方法
可以跟踪到,最终是调用了下面的方法
最终是调用的eventHub的setOnChange
TextFiledEventHub的setOnChangeEvent和SetOnChange
TextFiledEventHub的FireOnChange
这个是TextInput文案变化时的onChange回调。 可以看到两个回调函数都会被调用。
小节
- $$this.text 被转换后会变成一个对象,该对象有value和changeEvent两个属性
- 这个对象的value取的原始this.text的值,changeEvent是一个函数,用于TextInput的onChange回调
- TextInput的onChange回调的时候,该对象中的changeEvent和我们自己传入的onChange都会回调
@Builder装饰器:自定义构建函数 按引用传参
代码转换前后
如果Builder的参数不需要传递给自定义组件HelloComponent,那么使用 $$范式 或者 具名变量p都一样
如果需要传递给自定义组件HelloComponent,$$范式 和 具名变量p就不一样了。
$$ 范式传递的是$$.__paramA1,这个取得是状态变量
具名变量p传递的是p.paramA1,这个取得是状态变量的值
传递
-
原有的 paramA1 被转换成了一个 getter方法
- 如果paramA1原来是组件的属性,则getter返回状态变量的包装类或者paramA1原来的值
- 如果paramA1原来是字面量,则getter直接返回该字面量
-
之后调用makeBuilderParameterProxy对参数进行处理,处理的结果就是返回一个代理,代理对象的get和set
makeBuilderParameterProxy
我们在方法中也看到,这里虽然传入了@Builder的函数名,但其实只在set方法里抛出错误信息的时候用到了。
@Builder的引用传参,不能在Builder内部设置,只能使用get方法。这里可以看 参数传递规则 第二条
举例解释 makeBuilderParameterProxy
拿我们上面包装的paramA1举例,paramA1的原始值是this.label,是一个状态变量。经过转换之后变成了 { paramA1: () => (this["__label"] ? this["__label"] : this["label"]) }
此时方法的source是{ paramA1: () => (this["__label"] ? this["__label"] : this["label"]) },使用source创建了一个Proxy,对source对象进行代理set和get方法的访问
访问 paramA1(Builder中的$$.paramA1)
我们访问paramA1的时候,prop的值就是paramA1,不是以__开头,处理后的prop1也是paramA1并且和prop相等
target[prop1]即source[paramA1] 此时获取到的是() => (this["__label"] ? this["__label"] : this["label"]),是一个函数
调用value()得到的是(this["__label"] ? this["__label"] : this["label"]),此时this["__label"]和this["label"]的值如下图所示
此时获取的是状态变量 private __label: ObservedPropertySimplePU;,同时由于prop1和prop相等,最终调用funcRec.get()获取了状态变量的值
访问 __paramA1(Builder中传递给HelloComponent的参数$$.__paramA1)
我们访问__paramA1的时候,prop1时paramA1,prop是__paramA1,它俩不相等
target[prop1]即source[paramA1] 此时获取到的是() => (this["__label"] ? this["__label"] : this["label"]),是一个函数
调用value()得到的是(this["__label"] ? this["__label"] : this["label"]),此时this["__label"]和this["label"]的值如下图所示
此时获取的是状态变量 private __label: ObservedPropertySimplePU;,同时由于prop1和prop不相等,最终将状态变量__label返回
小结
- $$作为@Builder的参数按引用传递时,它要求参数必须只有一个,且传递的时候必须是字面量
- 转换到底层,这个字面量对象会被 makeBuilderParameterProxy 创建一个Proxy进行代理,将原有参数修改为函数类型,这样每次访问的时候都是调用函数获取状态变量的值
总结
- $$有两种使用方式,内置组件的双向同步以及@Builder的按引用传递范式。编译器针对这两种方式的处理是不一样的
- $$给内置组件双向同步时,原有属性会被编译成一个有value和changeEvent的对象。通过value取值,通过changeEvent接收变化事件,然后修改状态变量完成UI更新
- $$给@Builder按引用传参时,原有属性被编译成一个getter,同时会生成一个proxy代理get方法,用于同时支持获取状态变量原有类还是状态变量的值
评论记录:
回复评论: