一、前言
通过本文你将全面清晰的洞悉动态化跨端的实现原理,感受黑悟空(数据)一路打怪升级(在不同语言环境中流转改造),逆天改命(操控原生视图绘制),终成齐天大圣(完成视图渲染呈现)的艰辛历程。
二、原理介绍
1、动态化跨端原理介绍
动态化- 罗码(Roma,后文统称动态化)是一个完全自主研发的一站式跨平台解决方案,一份代码,可以在 Android、iOS、Harmony 及 Web 上运行。动态化的跨端原理与 React Native 、Weex 一致,在吸取业界各跨端框架优势的基础上,加上自主创新,打造的一个完全自主可控的综合跨端解决方案。其跨端的理论基础在于各端都具备统一解析和执行 JavaScript 的能力(JS 虚拟机)。业务代码被打包成 js 文件,在各端被 JS 虚拟机加载,被解析成多条不同功能的指令,调用原生宿主能力,完成数据的传递和视图的绘制。示意图如下:
-
鸿蒙系统的方舟虚拟机能直接加载js文件吗?
-
JS 虚拟机能高效的执行动态化相关指令吗?
为了让不同业务场景下的动态化产物高效地被 JS 虚拟机解析执行,需要扩充 JS 虚拟机的能力,提供适合动态化跨端场景下的功能,包括实例的创建和管理,视图的增、删、改、查以及便捷的跨语言通讯等功能。当然,为了使各平台更好的接入和使用动态化能力,动态化提供了一套统一接口,各平台依靠自身原生能力实现即可。
-
鸿蒙端的 App 如何接入动态化?
为了让App更好的接入动态化,我们提供了鸿蒙端的静态库文件 libRomaSdk.so (后文简称 SDK,由 NDK 通过 CMake 和 Ninja 将 C/C++ 代码编译而得来,这个过程可参考官网,这里不展开介绍),只要在工程中配置相关依赖即可使用动态化能力。
2、鸿蒙接入动态化原理介绍
App启动时第一时间就会加载动态化 SDK,包括 JS Engine(动态化自主研发的为各平台的 JS 虚拟机扩展的功能) 和 Jue Instance (鸿蒙端对 JS Engine Interface 的实现)两部分,完成动态化运行环境的初始化。具体包括业务代码实例管理、任务管理、虚拟Dom树管理、虚拟Dom树 Differ、业务页面渲染管理、生命周期管理、事件分发、业务逻辑处理等一系列功能。
三、业务示例
1.业务代码展示
<template style="border-width: 2px;"><div class="normalClass" style="margin-top: 100px;"><image class="normalClass" :src="imageUrl" style="height: 200;"></image><div class="normalClass" :style="{'background-color':bgColor}" style="height:60px;"><textstyle="border-width: 2px;width:260px;height:40px;align-self: center;text-align: center;background-color: white;"@click="change()">更新节点数据</text></div></div></template><script>export default {data() {return {bgColor: "white",imageUrl: "https://static.foodtalks.cn/company/images/434/121logo.png"}},mounted() {},methods: {change() {this.bgColor = "red";this.imageUrl = "https://www.szniego.com/uploads/image/20210202/1612232326.png";this.updateInstance();}}}</script><style scoped>.normalClass {margin: 10px;justify-content: center;align-items: center;align-self: stretch;border-width: 2px;}</style>
2.资源加载流程
/******/ (function(modules) { })/************************************************************************//******/ ({/***/ "./src/jueDemoList/ADemo/ADemo.jue":/*!*******************************************!*\!*** ./src/jueDemoList/ADemo/ADemo.jue ***!\*******************************************//*! exports provided: default *//***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_require__.r(__webpack_exports__);var template = JRTemplateManager._jr_create_jue_template('ADemo.jue', {"id": "ADemo","version": "11","dependencies": {"JSEngine": "0.9.4"}});JRTemplateManager._jr_create_t_node('ADemo.jue', '0', '40e129af-5e6a-70b8-a757-28a22785dc2f', 'document', {"style": "border-width: 2px;"});JRTemplateManager._jr_create_t_node('ADemo.jue', '40e129af-5e6a-70b8-a757-28a22785dc2f', '918d8bb8-9362-bcd6-00ee-35b85c435072', 'div', {"class": "normalClass","style": "margin-top: 100px;"});JRTemplateManager._jr_create_t_node('ADemo.jue', '918d8bb8-9362-bcd6-00ee-35b85c435072', '9e848985-59ac-bd8b-e85a-617a6e9a08dd', 'image', {"class": "normalClass",":src": "imageUrl","style": "height: 200;"});JRTemplateManager._jr_create_t_node('ADemo.jue', '918d8bb8-9362-bcd6-00ee-35b85c435072', 'b56d24a9-c08e-6b32-9558-162f2aece68d', 'div', {"class": "normalClass",":style": "{'background-color':bgColor}","style": "height:60px;"});JRTemplateManager._jr_create_t_node('ADemo.jue', 'b56d24a9-c08e-6b32-9558-162f2aece68d', '440bad33-f8bb-6baf-fe4f-b9bf768d4cc1', 'text', {"style": "border-width: 2px;width:260px;height:40px;align-self: center;text-align: center;background-color: white;","@click": "change()"});JRTemplateManager._jr_add_t_node_value('ADemo.jue', '440bad33-f8bb-6baf-fe4f-b9bf768d4cc1', 'value', '更新节点数据');var __default__ = {data: function data() {return {bgColor: "white",imageUrl: "https://static.foodtalks.cn/company/images/434/121logo.png"};},mounted: function mounted() {},methods: {change: function change() {this.bgColor = "red";this.imageUrl = "https://www.szniego.com/uploads/image/20210202/1612232326.png";this.updateInstance();}}};template.script = __default__;__default__.filename = 'ADemo.jue';__default__.__template__ = function () {return template;};/* harmony default export */ __webpack_exports__["default"] = (__default__);template.globalStyle = {};template.style = {".normalClass": {"margin": "10px","justify-content": "center","align-items": "center","align-self": "stretch","border-width": "2px"}};/***/ })/******/ });//# sourceMappingURL=ADemo.js.map
四、视图绘制流程
1.绘制原理
上图展示了动态化在鸿蒙端绘制页面的过程,总结一下就是三种语言环境,三个 Instance,三个 Thread 。三个 Instance 独立且一一对应。ArkTs 中的 Instance 在 UI 线程中用来完成页面的绘制,数据的绑定和事件的触发等。C++ 中的 Instance 作为数据的中转存储和事件转发,在 bg Thread 处理复杂耗时的逻辑,避免阻塞 UI Thread 和 js Thread。JS 中的 Instance 负责搜集产物信息,构建 V-Dom Tree,传递数据和事件等都在 js Thread 中处理。C++ 和 ArkTs 中根据 V-Dom Tree 创建 Component Tree 和 Render Tree。
2.代码分析
public aboutToAppear() {// 确保获取资源js文件并加载到内存中romaAssetsManager.ensureAsset(this.jueName!, progress).then((version) => {// 完成不同语言环境下Instance实例的创建// 1 创建 arkTS 和 cpp 实例this.romaInstance = this.createInstance(this.rootContent, this.pageId);// 2 创建 JS 实例this.romaInstance!.startInstance(this.initialProps).then((result) => {});}).catch((error: Error) => {})}
1.创建 ArkTS 环境中的 Instance 实例
private createInstance(root: NodeContent, pageId: string | null): RomaInstance {this.shouldDestroyRomaInstance = truereturn RomaEnv.createAndRegisterRomaInstance(this.jueName!, {root: root,uiContext: this.getUIContext(),abilityContext: getContext(this) as common.UIAbilityContext,pageId: pageId,errorListener: this.errorListener ? (error) => {this.errorListener?.(error);console.warn("CreateInstanceView", error.message);this.pageStage = PageStage.ERROR} : undefined}, this.stateListener);}// 类型public stateListener?: RomaStateListener;export type RomaStateListener = (instanceId: string, state: "createFinish" | "updateFinish" | "createInstance",romaInstance: RomaInstance) => void;
public createInstance(jueName:string, param: RomaInstanceParam, stateListener?:RomaStateListener): RomaInstance {const id = ++this.nextInstanceId;// 创建 arkTS 侧的实例const instance = new RomaInstance(jueName,id.toString(),"",this.napiBridge,param.uiContext,param.abilityContext)// 给实例绑定状态变化的监听if (stateListener) {instance.addStateListeners(stateListener);}instance.setPageId(param.pageId);instance.initialize(param.root);this.instanceMap.set(id.toString(), instance);return instance;}
2.创建 C++ 环境中的 Instance 实例
public initialize(root: NodeContent | null = null) {// 注册实例变化监听器this.napiBridge.createInstance( this.jueName, this.getId(), root,(mutations: Mutation[], isFromCore: boolean) => {this.descriptorManager.applyMutations(mutations, isFromCore)},(tag, commandName, args) => {// 省略无关代码},(instanceId: string, state: string) => {// 省略无关代码})}
static napi_value createInstance(napi_env env, napi_callback_info info) {ArkTS arkJs(env);auto args = arkJs.getCallbackArgs(info, 6);auto jueName = arkJs.getString(args[0]);InstanceId instanceId = arkJs.getString(args[1]);ArkUI_NodeContentHandle nodeContentHandle_;if(!arkJs.isUndefined(args[2])){OH_ArkUI_GetNodeContentFromNapiValue(env, args[2], &nodeContentHandle_);}auto listener_ref = arkJs.createReference(args[3]);auto componentMethod_ref = arkJs.createReference(args[4]);auto stateListenerMethod_ref = arkJs.createReference(args[5]);auto &engine = RomaEnv::getInstance();engine.createInstance(jueName, instanceId, nodeContentHandle_,[env, listener_ref](MutationsToNapiConverter mutationsToNapiConverter, auto const &mutations) {// C++环境中 Instance 状态变化时触发if (mutations.size() <= 0) {return;}ArkTS arkJs(env);auto napiMutations = mutationsToNapiConverter.convert(env, mutations);std::array<napi_value, 1> args = {napiMutations};// 获取从 ArkTS 环境中传递过来的监听对象并触发auto listener = arkJs.getReferenceValue(listener_ref);arkJs.call<1>(listener, args);},return arkJs.getUndefined();}
void RomaEnv::createInstance(std::string jueName, InstanceId instanceId, ArkUI_NodeContentHandle nodeContentHandle_, MutationsListener mutationsListener,ComponentMethodListener componentMethodListener, StateListener stateListener) {RomaEnv::getInstance().getBackgroundExecutor()([=]() {auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId);// 创建实例,页面 | 模板RomaInstance::Shared instance = std::make_shared<RomaInstance>(jueName, instanceId);instance->rootInstance_ = instance;instance->attachNativeXComponent(nodeContentHandle_);// 注册监听器instance->registerInstanceChangeListener(mutationsListener);……});}
3.创建 JS 环境中的 Instance 实例
public async startInstance(initialProps: TObject): Promise<void> {return this.napiBridge.startInstance(this.getId(),initialProps);}
static napi_value startInstance(napi_env env, napi_callback_info info) {ArkTS arkJs(env);arkJs.methodName = "startInstance";auto args = arkJs.getCallbackArgs(info, 3);InstanceId instanceId = arkJs.getString(args[0]);auto onFinishRef = arkJs.createReference(args[2]);auto &engine = RomaEnv::getInstance();engine.startInstance(instanceId, arkJs.getDynamic(args[1]), [env, onFinishRef]() {ArkTS arkJs(env);auto listener = arkJs.getReferenceValue(onFinishRef);arkJs.call<0>(listener, {});arkJs.deleteReference(onFinishRef);});return arkJs.getUndefined();}// startInstance 的具体实现void RomaEnv::startInstance(InstanceId instanceId, folly::dynamic &&initialProps,std::function<void()> &&onFinish) {try {RomaEnv::getInstance().getBackgroundExecutor()([=]() {auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId);if (nonConstRef) {// 准备进入 js 环境创建 InstancenonConstRef->start(instanceId, initialProps);this->taskExecutor_->runTask(TaskThread::MAIN, [onFinish]() {onFinish();});}});} catch (const std::exception &e) {throw e.what();};}
void RomaInstance::start(SurfaceId surfaceId, folly::dynamic const &initialProps){// 进入js线程执行实例创建RomaEnv::getInstance().getRuntimeExecutor()([jueName_ = jueName_, initialProps, surfaceId](jsi::Runtime &runtime) {// 判断 JS 的全局变量中是否有 JRPageManagerif (runtime.global().hasProperty(runtime, "JRPageManager")) {jsi::Object JRPageManager = runtime.global().getPropertyAsObject(runtime, "JRPageManager");if (JRPageManager.hasProperty(runtime, "createInstance")) {jsi::Function method = JRPageManager.getPropertyAsFunction(runtime, "createInstance");method.callWithThis(runtime, JRPageManager,{jsi::valueFromDynamic(runtime, jueName_+".jue"),jsi::valueFromDynamic(runtime, surfaceId),jsi::valueFromDynamic(runtime, initialProps),});}}});}
export function createInstance(bundleName,instanceID,options){// 判断是否传入实例bundleName以及对应bundle是否已经加载if (!bundleName || !has_load_bundle(bundleName)) {callLoadBundleJsFileFail(instanceID)return;}JRTransUICore._jr_ydby_new_template_instance(bundleName,instanceID,options);}
function _jr_ydby_new_template_instance(template_id, ctx_id, template_data, is_batchCreate) {// 1.根据模板创建JUE实例var ctx = new JueInstance(ctx_id, template, template_data);ctx.initRootCtxAndStaticCss();……// 创建v-domvar v_dom = new _jr_ydby_v_dom(template_id);ctx.v_dom = v_dom;// 2.构建v-dom 对应 root-nodevar root_node = _jr_ydby_new_node_instance(ctx.c_id, v_dom, template.root_node, null, {}, is_batchCreate);// 构建结束_jr_ydby_create_finshed(ctx.c_id);return ctx.c_id;}
4.在 JS 环境中构建 V-Dom Tree
export function _jr_ydby_new_node_instance(ctx_id, v_dom, current_t_node, parent_node, v_f_ctx, is_batchCreate, itemIndex) {var cur_env = _jr_ydby_node_parse_jscontext(ctx_id, v_f_ctx);let ctx = __jr_template__ctx[ctx_id];var current_node = null;if (current_t_node.type === 'document') {// 创建根节点current_node = new JUE_NODE(v_f_ctx, ctx_id, "", current_t_node);current_node.setAttr(cur_env, ctx)_jr_ydby_create_body(current_node);}else{// 创建其他子节点current_node = new JUE_NODE(v_f_ctx, ctx_id, parent_node.id, current_t_node);current_node.setAttr(cur_env, ctx);current_node.setStyle(cur_env, parent_node.style);current_node.setValue(cur_env, v_f_ctx);current_node.setEvent(cur_env);// 组装数据,生成 _jr_ydby_v_node对象parent_node.appendChild(current_node, v_dom);// 添加节点_jr_ydby_add_element(current_node, current_node.node_index);}// 循环创建当前节点下的子节点if (current_t_node.sub_nodes) {let v_if_else_parse_result = [];for (let i = 0; i < current_t_node.sub_nodes.length; i++) {v_f_ctx = Object.assign({}, v_f_ctx);let sub_t_node = current_t_node.sub_nodes[i];let att = sub_t_node.attr;let v_for_value = att['v-for'];……// 递归创建子节点_jr_ydby_new_node_instance(ctx_id, v_dom, sub_t_node, current_node, v_f_ctx, is_batchCreate);}}return current_node;}
5.在 C++ 环境中构建 Component Tree
current_node.node_index);搜集节点信息var nodePorperty = {template_id: node.template_id,ctx_d: node.ctx_id,tag: node.tag,id: node.id,is_root: node.is_root,type: node.type,parent_node: node.parent_node,style: node.style,cache: node.cache,attr: _jr_ydby_tools_deep_copy(node.attr),value: node.value,event: node.event,index: node.index,//仅cell-slot节点使用isComponentNode: node.isComponentNode,componentInstanceId: node.componentInstanceId,componentBundleName: node.componentBundleName};调用添加方法node.parent_node, nodePorperty, index);
runtime_->global().setProperty( *runtime_, "callAddElement",Function::createFromHostFunction( *runtime_,PropNameID::forAscii(*runtime_, "callAddElement"), 4,[](jsi::Runtime &runtime,jsi::Value const & /*thisValue*/,jsi::Value const *arguments,size_t /*count*/) noexcept -> jsi::Value {UIManager::callAddElement(surfaceIdFromValue(runtime, arguments[0]),stringFromValue(runtime, arguments[1]),commandArgsFromValue(runtime, arguments[2]),arguments[3].getNumber());return jsi::Value::undefined();}));
void UIManager::callAddElement(SurfaceId surfaceId, std::string const &parent_id, folly::dynamic props, size_t index) {ComponentName name = props["type"].asString();RomaEnv::getInstance().getBackgroundExecutor()([=]() {RomaNode::Shared node = nullptr;auto nonConstRef= RomaInstanceManager::getInstance().get(surfaceId);auto parent = nonConstRef->getNode(parent_id);if(parent == nullptr){return;}// 强引用保存节点到父节点的children_数组中folly::dynamic style = props["style"];node = RomaNodeFactory::createSharedNode(surfaceId, tag, name, parent_id, index, isComponentNode,componentInstanceId, props["attr"], style, props["event"],props["value"]);size_t childIndex = index;parent->appendChild(node, childIndex);auto shadowView = std::make_shared<ShadowView>(*node);// 组件节点,只插入,不创建,等组件实例创建时,再创建if (!isComponentNode) {// 增加普通节点的创建指令nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::CreateMutation(shadowView->getSharedShadowView()));}if ((parent->getComponentName() == "document") && nonConstRef->isComponent) {// 如果是子组件中的节点,并且是document的直接子节点auto parentNonConstRef = RomaInstanceManager::getInstance().get(nonConstRef->parentInstanceId_);if (parentNonConstRef) {auto componentNode = parentNonConstRef->getNode(nonConstRef->componentNodeId_);if (componentNode) {if (RomaEnv::getInstance().isUseYoga) {// 父子组件衔接yoga树componentNode->appendChild(node, index);}// 增加组件节点的插入指令auto parentShadowView = componentNode->getShadowView();auto documentShadowView = parent->getShadowView();……for (auto const &pair : documentShadowView->style_.items()) {parentShadowView->style_[pair.first] = pair.second;}nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::InsertMutation(parentShadowView, shadowView->getSharedShadowView(), index));}}} else {// 增加普通节点的插入指令auto parentShadowView = parent->getShadowView();nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::InsertMutation(parentShadowView, shadowView->getSharedShadowView(), childIndex));}// 保存到节点node->setShadowView(shadowView->getSharedShadowView());// 弱引用保存节点到实例的map中nonConstRef->addNode(tag, node);});
6. 在 JS 环境中发送节点创建完成的消息
export function _jr_ydby_create_finshed(ctx_id) {let instance = getInstanceById(ctx_id);callCreateFinish(ctx_id,{template_id:instance.template_id,version:instance.currentVersion});}
runtime_->global().setProperty(*runtime_, "callCreateFinish",Function::createFromHostFunction(*runtime_,PropNameID::forAscii(*runtime_, "callCreateFinish"), 1,[](jsi::Runtime &runtime, jsi::Value const & ,jsi::Value const *arguments, size_t /*count*/) noexcept -> jsi::Value {auto surfaceId = surfaceIdFromValue(runtime, arguments[0]);UIManager::callCreateFinish(surfaceId);return jsi::Value::undefined();}));
void UIManager::callCreateFinish(SurfaceId surfaceId) {RomaEnv::getInstance().getBackgroundExecutor()([=]() {auto nonConstRef = RomaInstanceManager::getInstance().get(surfaceId);if (nonConstRef && !nonConstRef->isComponent) {// 执行yoga布局// Layout nodes.std::vector<YogaLayoutableShadowNode const *> affectedLayoutableNodes{};// affectedLayoutableNodes.reserve(1024);LayoutContext layoutContext = LayoutContext();……if(starts_with(nonConstRef->rootInstance_.lock()->jueName_, "template")){layoutContext.layoutType = TEMPLATE;}if (nonConstRef->rootInstance_.lock()) {nonConstRef->rootInstance_.lock()->rootNode_->layoutIfNeeded(layoutContext);}if (nonConstRef->rootInstance_.lock()) {ShadowViewMutationList mutableList = nonConstRef->rootInstance_.lock()->lastMutations_;nonConstRef->rootInstance_.lock()->lastMutations_.clear();RomaEnv::getInstance().taskExecutor_->runTask(TaskThread::MAIN, [nonConstRef, mutableList, surfaceId] {// 使用 ArkUI 渲染 ,并触发 mutationsListener 监听if (nonConstRef->rootInstance_.lock()) {auto a = nonConstRef->rootInstance_.lock()->m_mutationsToNapiConverter;nonConstRef->rootInstance_.lock()->mutationsListener(a, mutableList,true);}nonConstRef->createFinish();});}} else if (nonConstRef) {RomaEnv::getInstance().taskExecutor_->runTask(TaskThread::MAIN, [nonConstRef, surfaceId] { nonConstRef->createFinish(); });}});}
(mutations: Mutation[], isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations, isFromCore) }
7.在 ArkTS 环境中构建 Render Tree
public applyMutations(mutations: Mutation[], isFromCore: boolean) {// 去重const tags = mutations.flatMap(mutation => this.applyMutation(mutation, isFromCore));const tags = new Set(tags);// 遍历各节点tags.forEach(tag => {// 取实例id,和tagconst strArr: string[] = tag.split("##");const instanceId = strArr[0];//实例idconst nodeId = strArr[1];//节点idif(instanceId === this.romaInstance.getId()){// 更新节点let updatedDescriptor = this.getDescriptor(nodeId);if(!updatedDescriptor) return;// 在当前实例中更新tag组件的UI描述信息this.descriptorListenersSetByTag.get(nodeId)?.forEach(cb => {onDescriptorChange(cb, updatedDescriptor);});}else {// 创建节点const instance: RomaInstance = RomaEnv.getRomaInstanceManager()?.getInstance(instanceId) as RomaInstance;let updatedDescriptor = instance?.getDescriptor(nodeId);if(!updatedDescriptor) return;instance.refreshComponentUI(nodeId, updatedDescriptor);}});}
public refreshComponentUI(tag: Tag, d: Descriptor) {this.getDescriptorListenersSet(tag)?.forEach(cb => {onDescriptorChange(cb,d);});}
aboutToAppear() {if (!this.componentCtx) {return;}this.componentCtx?.aboutToAppear((newDescriptor) => {this.descriptor = newDescriptor;// 链接自定义alt图方法this.customAltImage = RomaConfig.instance().getImageAltMethod();// 触发更新this.onLoadStart();this.updateImageSource();// 处理图片 object-position 模式相关逻辑this.initObjectPositionHandle()// 解析占位图this.altSource = this.getImageAlt();this.hasPlaceHoldImage = this.altSource ? true : false;// 解析背景色let bgColorStr = RomaStyleParser.getStyleToString('background-color',this.descriptor);……// 设置tint-colorthis.getTintColor();// 是否开启抗锯齿this.interpolation = this.colorFilter ? ImageInterpolation.High : ImageInterpolation.Low;}, (methodName, args:TAny[]) => {// 注册标签方法if (methodName === 'loadRef') {this.loadRef(args[0]);}});}
8.根据 Render Tree 绘制视图
public build() {// 根据表述信息,构建RomaComponentFactory.builder(new RomaComponentParam(this.romaInstance, this.descriptor.tag));}
export const RomaComponentFactory: WrappedBuilder<[RomaComponentParam]> = wrapBuilder(RomaComponentFactoryBuilder);在调用RomaComponentFactory.builder时,触发 RomaComponentFactoryBuilder 方法,如下只列出示例中用到的标签的实现,其他的省略了。
@Builderfunction RomaComponentFactoryBuilder(param: RomaComponentParam) {if (param.type == "document") {RomaDocument({componentCtx: param.componentCtx})} else if (param.type == "div") {RomaDiv({componentCtx: param.componentCtx})} else if (param.type === "text") {RomaText({componentCtx: param.componentCtx})} else if (param.type === "image" || param.type === "img") {RomaImageView({componentCtx: param.componentCtx})} else {RomaCustomComponentFactory.customComponentBuilder.builder(param.componentCtx);}}
build() {if(this.componentCtx && this.descriptor) {Image(this.imgSource).attributeModifier(this.componentCtx?.build(this.descriptor)).gestureModifier(this.componentCtx?.build(this.descriptor)).alt(this.getImageAlt()).objectFit(this.getResizeMode(RomaStyleParser.getStyleToString('object-fit', this.descriptor))).renderMode(this.getRenderMode()).colorFilter(this.colorFilter).interpolation(this.interpolation).backgroundColor(this.showColor).blur(this.getBlurNumber()).onComplete(event => this.onLoad(event as ImageOnCompleteEvent)).onError(event => this.dispatchOnError(event as ImageOnErrorEvent)).position(this.imgPosition).clipShape(this.imgClipShape)}}
五、视图更新流程
1.Differ 原理介绍
六、规划总结

扫一扫,加入技术交流群

