鸿蒙ACE-V1状态分析$$

$$ 的两种用途

$$语法:内置组件双向同步

developer.huawei.com/consumer/cn…

@Builder装饰器:自定义构建函数

developer.huawei.com/consumer/cn…

参数传递规则

自定义构建函数的参数传递有按值传递按引用传递两种,均需遵守以下规则:

  • 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
  • 在@Builder修饰的函数内部,不允许改变参数值。
  • @Builder内UI语法遵循UI语法规则
  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递

转换前代码



class Tmp {
  paramA1: string = ''
}


@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)
  }
}

转换后代码

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;
}

class Tmp {
    paramA1: string = '';
}

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; } }

image.png

传递

$$this.text 转换成了 { value: this.text, changeEvent: newValue => { this.text = newValue; } }

这样状态变量变化时就调用changeEvent,changeEvent内会改变状态变量,状态变量改变从而再驱动UI更新

底层实现

$$只支持特定组件的特定属性的双向绑定。我们就找一下TextInput看 它咋实现的

JSTextInput

所有的系统组件,在底层都对应一个C++的实现类。TextInput也不例外,它对应底层的JSTextInput

image.png

JSTextInput::Create

TextInput.create({ text: { value: this.text, changeEvent: newValue => { this.text = newValue; } }

经过转换之后,调用的是JSTextInput的create方法

image.png

最终调用到了JSTextFiled的CreateTextInput方法

image.png

JSTextField::CreateTextInput

和我们猜想的一样,显示的时候取得value属性

image.png

ParseTextFieldTextObject(info, changeEventVal);

最终将changeEvent设置到了onChangeEvent方法的参数里

image.png

TextFieldModel::GetInstance()->SetOnChangeEvent

TextFieldModel使用新的渲染管线的时候,实际的类是 TextFieldModelNG

最终是调用的eventHub的setOnChangeEvent

image.png

JSTextField::SetOnChange

我们再回头看下TextInput的onChange方法绑定到哪个方法

image.png

可以跟踪到,最终是调用了下面的方法

image.png

最终是调用的eventHub的setOnChange

image.png

TextFiledEventHub的setOnChangeEvent和SetOnChange

image.png

image.png

TextFiledEventHub的FireOnChange

这个是TextInput文案变化时的onChange回调。 可以看到两个回调函数都会被调用。

image.png

小节
  1. $$this.text 被转换后会变成一个对象,该对象有value和changeEvent两个属性
  2. 这个对象的value取的原始this.text的值,changeEvent是一个函数,用于TextInput的onChange回调
  3. TextInput的onChange回调的时候,该对象中的changeEvent和我们自己传入的onChange都会回调

@Builder装饰器:自定义构建函数 按引用传参

代码转换前后

如果Builder的参数不需要传递给自定义组件HelloComponent,那么使用 $$范式 或者 具名变量p都一样

如果需要传递给自定义组件HelloComponent,$$范式 和 具名变量p就不一样了。

$$ 范式传递的是$$.__paramA1,这个取得是状态变量

具名变量p传递的是p.paramA1,这个取得是状态变量的值

image.png

传递

  1. 原有的 paramA1 被转换成了一个 getter方法

    1. 如果paramA1原来是组件的属性,则getter返回状态变量的包装类或者paramA1原来的值
    2. 如果paramA1原来是字面量,则getter直接返回该字面量
  2. 之后调用makeBuilderParameterProxy对参数进行处理,处理的结果就是返回一个代理,代理对象的get和set

image.png

makeBuilderParameterProxy

我们在方法中也看到,这里虽然传入了@Builder的函数名,但其实只在set方法里抛出错误信息的时候用到了。

@Builder的引用传参,不能在Builder内部设置,只能使用get方法。这里可以看 参数传递规则 第二条

image.png

举例解释 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”]的值如下图所示

image.png

此时获取的是状态变量 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”]的值如下图所示

image.png

此时获取的是状态变量 private __label: ObservedPropertySimplePU;,同时由于prop1和prop不相等,最终将状态变量__label返回

小结

  1. $$作为@Builder的参数按引用传递时,它要求参数必须只有一个,且传递的时候必须是字面量
  2. 转换到底层,这个字面量对象会被 makeBuilderParameterProxy 创建一个Proxy进行代理,将原有参数修改为函数类型,这样每次访问的时候都是调用函数获取状态变量的值

总结

  1. $$有两种使用方式,内置组件的双向同步以及@Builder的按引用传递范式。编译器针对这两种方式的处理是不一样的
  2. $$给内置组件双向同步时,原有属性会被编译成一个有value和changeEvent的对象。通过value取值,通过changeEvent接收变化事件,然后修改状态变量完成UI更新
  3. $$给@Builder按引用传参时,原有属性被编译成一个getter,同时会生成一个proxy代理get方法,用于同时支持获取状态变量原有类还是状态变量的值

参考资料

  1. gitee.com/openharmony…
  2. 声明式范式的语法编译转换,语法验证 https://gitee.com/openharmony/developtools_ace_ets2bundle
阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20248,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?