在 uni-app 开发鸿蒙应用时,Vue 组件在复杂 UI 场景下(如地图、扫码、自定义绘制)往往性能不足或功能受限。从 HBuilderX 4.62 版本开始,uni-app 提供了通过 UTS 插件注册鸿蒙原生组件的方案,允许在 Vue 页面中使用 <embed> 标签直接渲染 ArkTS 组件,并支持同层渲染——原生组件能与普通 Vue 组件混合布局,无需额外层级处理。
整体架构分为三层:Vue 页面通过 <embed> 传递属性和事件;UTS 插件负责桥接,导入 .ets 文件;ArkTS 层执行真正的原生渲染。这种设计让前端开发者只需关注 Vue 模板语法,无需手动编写通道通信代码,相比 Flutter 的原生集成方案更友好。
一、注册鸿蒙原生组件的核心 API
注册过程主要依赖 defineNativeEmbed 函数,其类型签名如下:
- export interface NativeEmbedEvent { detail: object; }
- export interface NativeEmbedBuilderOptions { width: number; height: number; on?: Map<string, (event?: NativeEmbedEvent) => void>; }
- export interface DefineNativeEmbedOptions<T extends NativeEmbedBuilderOptions = NativeEmbedBuilderOptions> { builder: (options: T) => void; }
- export declare function defineNativeEmbed<T extends NativeEmbedBuilderOptions = NativeEmbedBuilderOptions>(tag: string, options: DefineNativeEmbedOptions<T>): void;
复制代码
关键参数说明:
tag:组件标签名,必须小写,不支持驼峰命名(例如 'button' 而不是 'myButton'),否则可能无法正常渲染。
builder:构建函数,接收 NativeEmbedBuilderOptions 对象,包含 width、height 以及事件回调 Map(key 为事件名,小写)。
二、实战:注册一个鸿蒙原生按钮
在 UTS 插件的 app-harmony/ 目录下创建 button.ets 文件。首先定义一个标准的 ArkTS 组件 ButtonComponent,它接收 label 属性和 onButtonClick 回调:
- import { NativeEmbedBuilderOptions, defineNativeEmbed } from "@dcloudio/uni-app-runtime";
- interface ButtonBuilderOptions extends NativeEmbedBuilderOptions { label: string; }
- interface ButtonClickEventDetail { text: string; }
- @Component
- struct ButtonComponent {
- @Prop label: string;
- onButtonClick?: Function;
- build() {
- Button(this.label)
- .width('100%')
- .height('100%')
- .onClick(() => {
- if (this.onButtonClick) {
- const detail = { text: 'test' } as ButtonClickEventDetail;
- this.onButtonClick({ detail });
- }
- });
- }
- }
- @Builder
- function ButtonBuilder(options: ButtonBuilderOptions) {
- ButtonComponent({
- label: options.label,
- onButtonClick: options?.on?.get('buttonclick')
- })
- .width(options.width)
- .height(options.height);
- }
- defineNativeEmbed('button', {
- builder: ButtonBuilder
- });
复制代码
这段代码的关键逻辑:
- ButtonComponent 通过 @Prop 接收 label,并通过 onButtonClick 回调传递点击事件。
- @Builder 函数 ButtonBuilder 将 options 中的 width、height 传递给组件,并从事件 Map 中取出 'buttonclick' 事件的回调。
- defineNativeEmbed 将该组件注册为标签名 'button' 的原生组件。
三、在 UTS 插件中导入 .ets 文件
编辑或创建 UTS 插件根目录下的 app-harmony/index.uts 文件,只需要引入 .ets 文件即可触发注册:
注意:不需要导出 .ets 中的任何内容,因为 defineNativeEmbed 在执行时已经全局注册。.ets 文件必须放在 app-harmony/ 目录下,不能在 utssdk/ 或其他目录——utssdk/ 只存放纯 TypeScript 接口定义。
四、在 Vue 页面中使用原生组件
注册完成后,在 Vue 页面通过 <embed> 标签调用,并传入 options 属性和事件监听:
- <template>
- <embed class="native-button" tag="button" :options="options" @buttonclick="onClick"></embed>
- </template>
- <script>
- export default {
- data() { return { options: { label: 'hello' } }; },
- methods: {
- onClick(e) { console.log('onClick', e.detail.text); this.options = { label: 'world' }; }
- }
- };
- </script>
- <style scoped>
- .native-button { display: block; width: 200px; height: 50px; margin: 10px auto; }
- </style>
复制代码
更新 options 时需要注意:必须对整个 options 对象重新赋值(例如 this.options = { label: 'new value' }),而不是修改已有对象的属性(如 this.options.label = 'new value' 不会触发更新)。这是因为 embed 组件通过引用变化来判断是否需要重绘原生层。
五、事件转发机制
所有通过 @ 绑定在 <embed> 组件上的事件(如 @buttonclick)都会自动转发到原生层。在 ArkTS 代码中通过 options.on?.get('事件名') 接收。事件名必须完全一致,建议统一使用小写格式。例如 Vue 端使用 @buttonclick,原生端就用 'buttonclick'。
六、在 WebView 之上展示原生组件
如果页面包含 WebView 组件,需要让原生组件覆盖在 WebView 之上(例如悬浮按钮),可以在 defineNativeEmbed 中设置 nodeRenderType 参数:
- import { NodeRenderType } from '@kit.ArkUI';
- defineNativeEmbed('button', {
- builder: ButtonBuilder,
- nodeRenderType: NodeRenderType.RENDER_TYPE_DISPLAY
- });
复制代码
NodeRenderType.RENDER_TYPE_DISPLAY 使组件以 Display 模式渲染,可覆盖在 WebView 内容之上。注意:如果页面没有使用 WebView,不要设置此属性,否则可能影响普通页面中的组件表现。
七、踩坑记录总结
1. 标签名必须全小写,禁止驼峰。
2. options 更新必须重新赋值整个对象,不能直接修改属性。
3. 事件名大小写敏感,Vue 端和原生端必须完全一致(建议全小写)。
4. .ets 文件只能放在 app-harmony/ 目录下。
5. 导入 .ets 文件只需 import 副作用,无需 export。
6. nodeRenderType 仅在需要覆盖 WebView 时使用,普通页面无需设置。
社区已有基于该方案的 UTS 插件案例,例如“UTS 组件鸿蒙花瓣地图”和“鸿蒙粘贴控件”,可在插件市场获取完整示例项目(插件 ID:23082、24925),这些案例演示了原生地图组件和剪切板控件的完整集成流程。
通过这套方案,开发者可以在 uni-app 中高效复用鸿蒙原生能力,既享受跨端框架的便利,又能获得原生组件的性能与功能。 |