HarmonyOS 与 ArkTS 开发注意事项
本文持续记录一些使用 ArkTS 进行 HarmonyOS 开发时遇到的一些问题以及解决方案。以及一些可能称不上是问题,但是在我看来有必要记录一下的点。
本文的主要结构将分为 ArkTS 和 HarmonyOS 两部分。同时内容多与 iOS 开发以及 Swift 语言进行对比。
因为代码高亮限制,本文中涉及到 ArkTS 的代码,都将使用 TypeScript 的语法高亮设置
ArkTS
ArkTS 是 TS 的超集,同时也阉割了一些 TS 的用法。
本节所记录的内容是从一个 TS 零基础小白的视角出发所遇到的问题。所以某些内容未必属于 ArkTS 引入,也有可能是 TS 就存在。某些小节可能会指出该小节的内容是属于 ArkTS 独有,还是 TS 就有。但是大多数小节可能并不会做此区分。
async 与 await
ArkTS 的异步协程不像是 Swift 那样,需要在方法声明上显式增加 async
关键字。
只要方法返回 Promise
类型,那么在调用时候就可以通过添加 await
关键字,来获取 Promise
里所包含的数据。
比如下面的方法:
function getData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched!");
}, 1000);
});
}
function main() {
const result = await getData(); // 可以使用 await
console.log(result); // 输出: "Data fetched!"
}
这其实是 TS 的语法。当你在 TypeScript 中声明一个函数为 async
时,该函数会自动返回一个 Promise
对象,而不是直接返回结果。即使函数内部没有显式地返回 Promise
,async
函数会将其结果封装在一个 Promise
中。比如下面这样:
async function myAsyncFunction() {
return "Hello, Async!";
}
// 调用方式
myAsyncFunction().then(result => console.log(result)); // 输出: "Hello, Async!"
myAsyncFunction
因为被 async
修饰,所以自动返回了一个 Promise<string>
而不是单纯的 string
。
所以如果方法没有标记 async
,但是返回了 Promise
,其实和标记了但是省略返回类型是一样的,自然也可以在调时直接使用 await
同步获取内容了。
HarmonyOS
环境变量配置
这里说的环境变量是 “正式环境”、“预上线环境” 以及 “测试环境”。包含接口环境配置以及一些 debug 入口等等。
在 iOS 开发中我们有预编译宏,但是 HarmonyOS 这块儿没有这个。HarmonyOS 的方法是在各个子模块中建立 target 的 sourceRoots
字段,将不同环境的代码添加到不同的 target 中,然后 entry 再引用不同的 target,以此来达到不同环境加载不同代码的需求。
本节不详细展开具体的详细内容,详细内容见官方文档。
在做这一项配置的过程中,有一个概念很容易搞错,或者说官方文档的讲解流程和实际上配置的流程有一定的出入。(当然也有可能是我个人能力不足导致理解错误)。
我们来看下面几个 build-profile.json5
文件。首先是一个 feature module 的:
// feature/build-profile.json5
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default",
"source": {
"sourceRoots": ["./src/default"] // 配置target为default的差异化代码空间
}
},
{
"name": "custom",
"source": {
"sourceRoots": ["./src/custom"] // 配置target为custom的差异化代码空间
}
}
]
}
其包含两个 target,使用 sourceRoots
引用了不同的代码。
然后再看 entry 的:
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default",
},
]
}
entry 只包含一个 default
target。
最后看工程的:
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
},
{
"name": "custom",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default",
"custom",
]
},
]
},
{
"name": "feature",
"srcPath": "./feature",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
},
{
"name": "custom",
"applyToProducts": [
"custom"
]
},
]
},
]
}
在阅读官方文档后,我首先的想法就是上面这三个配置文件。定义 default
和 custom
两个 product,然后 entry
作为入口,添加到两个 product 中。feature/default
添加到 default
;feature/custom
添加到 custom
。这样 default
product 包含 entry
和 feature/default
,custom
product 包含 entry
和 feature/custom
。思路看着没问题对吧,然而实际运行起来会发现配置不生效:custom
product 使用的是 feature/default
。
问题在于思路不对,正确的思路应该是:将 entry
中定义的 target,下放给各个 module 使用。也就是说要先在 entry
中定义各个 target,然后再在各个 module 中使用 entry
定义的 target,自定向下使用。
比如有5个子 module,各自都有不同的 target 需求,分别是 1-5,那么 entry
中就应该定义这 1-5个 target,然后5个子 module 从中选取自己需要的那一个。
所以上述例子中的配置文件应该改成下面这样,先看 entry 的 build-profile.json5
:
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default",
},
{
"name": "custom", // 增加 custom target
},
]
}
然后修改工程级 build-profile.json5
:
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
},
{
"name": "custom",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default",
]
},
{
"name": "custom",
"applyToProducts": [
"custom",
]
},
]
},
{
"name": "feature",
"srcPath": "./feature",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
},
{
"name": "custom",
"applyToProducts": [
"custom"
]
},
]
},
]
}
这么修改后再运行,custom
product 就可以正确地读取到 feature/custom
了。
最后再来说一说官方中的例子。官方上的示例使用 entry
演示 sourceRoots
的用法,其实暗藏了 “entry
也需要定义对应的 target” 这一点,只不过没有明说,让读者忽略了这一关键内容。