前端知库

鸿蒙的 ArkTS/ArkUI 和 Web、React Native 有什么区别?Web 前端页面怎样适配鸿蒙?

ArkTS 在 TypeScript 基础上强制静态类型、禁止运行时改对象布局,不支持 any、JSX、解构声明等写法;ArkUI 用 struct 自定义组件、链式属性和装饰器状态变量声明界面。Web 页面在鸿蒙上由基于 Chromium 的 ArkWeb 组件显示,适配重点是按 UA 里的 OpenHarmony 识别设备、按系统版本对应的内核查兼容性、通过 javaScriptProxy 和 runJavaScript 与应用互相调用。

参考答案

简答

鸿蒙原生应用默认用 ArkTS 写。ArkTS 是在 TypeScript 基础上扩展出来的,风格和 TS 很像,但规则更严:必须用静态类型,不能用 any,运行时不能给对象加删属性,JSX、var、解构声明这些写法也不支持。界面用 ArkUI 的声明式写法,组件是一个带 @Component 的 struct,在 build() 里用 Column()、Text() 这类系统组件拼界面,样式用 .fontSize() 这样链式调用。

它和 React、Vue 的思路相同,都是“状态变了,界面自动跟着变”。区别在于状态要用 @State 这类装饰器标出来,没有装饰的变量改了界面也不刷新;界面最后画的是 ArkUI 自己的组件,不是 DOM。React Native 也不生成 DOM,它在运行时创建 Android、iOS 的原生视图,但写的仍是 JavaScript 和 JSX。

Web 前端更常碰到的是另一件事:H5 页面在鸿蒙应用里由 ArkWeb 的 Web 组件显示。ArkWeb 基于 Chromium,不同系统版本对应不同内核,比如 HarmonyOS 6.0 默认 M132,所以查兼容性要按内核版本查。页面识别鸿蒙设备,官方建议看 UA 里的 OpenHarmony 关键字。

页面和应用互相调用也有固定方式。应用用 javaScriptProxy() 或 registerJavaScriptProxy() 把对象注入页面,页面直接调用;应用调页面用 runJavaScript()。注册时要配合 deleteJavaScriptRegister 防止内存泄漏,调试要先在应用里调用 setWebDebuggingAccess() 打开开关。

ArkTS 比 TypeScript 多了哪些限制

ArkTS 是 HarmonyOS 应用的默认开发语言。官方的说法是,它在 TypeScript 生态的基础上做了扩展,保持 TS 的基本风格,从 API version 10 起进一步强化了开发期的静态检查。主要的差异有四条:强制使用静态类型、禁止在运行时改变对象布局、限制部分运算符的语义(例如一元 + 只能用于数字)、不支持结构化类型(structural typing)。

为什么要这样限制?因为类型和对象结构在运行前都确定了,编译器能提前验证代码,运行时就能少做类型检查,官方把这一点归为性能上的考虑。所以 ArkTS 不支持一切“运行时才知道长什么样”的写法。

“从TypeScript到ArkTS的适配规则”列了每条规则的名字,其中级别为“错误”的不遵守就编译失败。前端开发者最容易撞上的几条如下,每条后面是规则名:

  • 不能用 any 和 unknown(arkts-no-any-unknown)。
  • 不能用 var,改用 let(arkts-no-var)。
  • 不支持解构变量声明(arkts-no-destruct-decls)。
  • 不支持函数表达式,改用箭头函数(arkts-no-func-expressions)。
  • 不支持 in 运算符,因为对象布局在编译时已知(arkts-no-in)。
  • 不支持 JSX(arkts-no-jsx)。
  • 不支持结构化类型,形状相同的两个类型不能互相赋值(arkts-no-structural-typing)。

文档也说明,按这些约束改过的代码仍然是合法的 TS 代码,没有被约束的 TS 特性 ArkTS 完全支持。另外 ArkTS 兼容 TS/JS 生态,可以用 TS/JS 开发或复用已有代码,具体限制见官方的“兼容 TS/JS 的约束”。

ArkUI 的组件和状态怎么写

ArkUI 提供两种开发范式:基于 ArkTS 的声明式开发范式,和兼容 JS 的类 Web 开发范式(HML、CSS、JavaScript 三段式)。官方推荐新应用用声明式范式,理由之一是它不需要 JS 框架管理页面 DOM,渲染更新链路更短。

下面是官方“创建自定义组件”里的例子,点一下文字,界面从 Hello, World! 变成 Hello, ArkUI!:

arkts
@Component
struct HelloComponent {
  @State message: string = 'Hello, World!';

  build() {
    Row() {
      Text(this.message)
        .fontSize(20)
        .margin(10)
        .onClick(() => {
          this.message = 'Hello, ArkUI!';
        })
    }
  }
}

拿它和 React 对比,读者会注意到几处不同。组件不是函数也不是 class,而是 struct 加 @Component;页面入口组件还要再加 @Entry。界面写在 build() 里,创建系统组件不用 new,也不写标签,直接 Text(this.message)。样式和事件都用“.”链式调用,官方建议每个属性方法单独一行。

状态这一点最要注意。“状态管理概述”写明,自定义组件里的变量必须用装饰器装饰才能成为状态变量,状态变量改变才会刷新界面;不加装饰器,界面只在初始化时渲染一次。这和 React 每次渲染重新执行函数、Vue 用响应式代理自动追踪都不一样。状态管理目前分 V1、V2 两个版本,官方建议新应用直接用 V2;而且状态管理只能在 UI 主线程使用,不能在 Worker、TaskPool 里用。

和 Web、React Native 的差别放在一起看

三者都是“写组件,描述界面应该长什么样”,差别在于最后画出来的东西和用的语言。

Web 页面画的是 DOM,由浏览器排版渲染,语言是 JavaScript,样式是 CSS。React Native 用 JavaScript 和 React 写,官方文档说明它在运行时为组件创建对应的 Android、iOS 原生视图,详见《React Native 与浏览器里的 React 有哪些共同点和差异?》。ArkUI 声明式范式用 ArkTS 写,界面由 ArkUI 框架的组件和渲染引擎绘制,同样没有 DOM,也没有 CSS 文件,样式通过组件的属性方法设置。

所以 React 或 Vue 的代码不能直接搬到 ArkUI 里。组件的拆分方式和“状态驱动界面”的思路可以沿用,语法、样式和状态声明要按 ArkTS 重写。

H5 页面在鸿蒙应用里怎样运行

更常见的情况是,鸿蒙应用里嵌了 H5 页面。显示网页用的是 ArkWeb(方舟 Web)提供的 Web 组件,访问在线网页时应用要声明 ohos.permission.INTERNET 权限。

ArkWeb 基于 Chromium 内核开发,系统版本和内核版本的对应关系由官方表格给出,例如:HarmonyOS 4.0 及之前是 M99,4.1 到 5.1 是 M114,6.0 默认 M132(也可切换到 M114)。官方建议按内核版本到 MDN 等网站查 W3C 标准的支持情况。所以评估某个 CSS 或 JS 特性能不能用,先确认目标设备的系统版本,再看对应 Chromium 版本是否支持。

怎样识别鸿蒙设备

ArkWeb 的默认 UA 格式是:

text
Mozilla/5.0 ({DeviceType}; {OSName} {OSVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{ChromeCompatibleVersion}.0.0.0 Safari/537.36 ArkWeb/{ArkWeb VersionCode} {DeviceCompat} {扩展区}

DeviceType 取值是 Phone、Tablet、PC(2in1 设备),OSName 默认是 OpenHarmony,手机上 DeviceCompat 默认是 Mobile。官方推荐用 OpenHarmony 关键字识别鸿蒙设备,理由是 ArkWeb 表示用的 Web 内核,OpenHarmony 才表示操作系统。下面用官方文档里的示例 UA 演示:

JavaScript
function parseHarmonyUA(ua) {
  const match = /\((Phone|Tablet|PC); OpenHarmony ([\d.]+)\)/.exec(ua);
  if (!match) return null;
  return { deviceType: match[1], osVersion: match[2] };
}

const ua =
  'Mozilla/5.0 (Phone; OpenHarmony 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile';
const info = parseHarmonyUA(ua);

console.log(info.deviceType); // Phone
console.log(info.osVersion); // 5.0
console.log(String(parseHarmonyUA('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)'))); // null

为什么不只看有没有 Mobile?因为平板和 PC 上 DeviceCompat 默认为空,只看 Mobile 会把它们当成桌面浏览器。文档还提到一个相关的细节:ArkWeb 根据 UA 里有没有 Mobile 决定是否启用页面 meta 标签的 viewport 属性,没有 Mobile 时默认关闭,应用可以显式设置 metaViewport 为 true 打开。所以平板上布局不对时,要先确认 viewport 是否生效。

页面和应用怎样互相调用

这和《Hybrid 开发是什么?JSBridge 的 URL Scheme 拦截和注入对象有什么区别?》里的“注入对象”是同一种思路。应用侧有两种注册方式:Web 组件初始化时用 javaScriptProxy(),初始化完成后用 registerJavaScriptProxy(),后者在下次加载或重新加载后生效。两种方式都要配合 deleteJavaScriptRegister 使用,防止内存泄漏。

假设应用把一个对象以 testObjName 的名字注册,并在 methodList 里列出 test,页面就能像调用全局对象一样调用它,这是官方示例里页面侧的写法:

HTML
<script>
  function callArkTS() {
    let str = testObjName.test();
    document.getElementById('demo').innerHTML = str;
  }
</script>

反过来,应用调用页面里的函数用 runJavaScript() 或 runJavaScriptExt(),后者除了字符串还支持 ArrayBuffer 参数。页面只要把函数挂在全局,比如 function htmlTest() {},应用就能用 runJavaScript('htmlTest()') 执行。

注入时还可以传 permission 参数,官方示例用它按 scheme、host 等限定哪些网址可以调用哪些方法。

怎样调试

用电脑上的 DevTools 调试真机网页的通用做法见《移动端页面怎样在真机上调试?Safari Web Inspector、chrome://inspect 和 vConsole 各适合什么场景?》。区别是要先让应用调用 setWebDebuggingAccess() 打开 Web 调试开关,否则 DevTools 发现不了页面;设备需要 4.1.0 及以上版本,从 API version 20 开始还支持指定端口的无线调试。

常见追问

为什么 ArkTS 不支持 any 和运行时加属性?

官方给的理由是性能和正确性。类型和对象布局都在编译时确定,编译器就能提前检查错误,运行时也少做类型检查。代价是很多 JS 的动态写法要改成用类或接口声明类型,比如对象字面量要有明确的类型,判断成员是否存在不能用 in,要用 instanceof。

已有的 H5 业务要上鸿蒙,最省事的办法是什么?

用 ArkWeb 的 Web 组件直接加载已有页面,原生能力通过 javaScriptProxy 注入的对象提供。页面侧要做的是按 OpenHarmony 识别设备、按系统版本对应的 Chromium 内核检查兼容性,再确认平板和 PC 上 viewport 是否生效。需要原生体验的页面再用 ArkUI 重写。

ArkUI 的类 Web 开发范式是不是就能直接跑 Vue 或 React?

不是。类 Web 范式用的是 HML 标签、CSS 和 JavaScript 三段式写法,只是更贴近 Web 开发习惯,不是浏览器环境。而且官方推荐新应用用声明式范式;在 Stage 模型下,类 Web 范式只支持卡片,应用页面只支持声明式范式。