官网信息链接
社区
所有版本及对应关系
申请调试证书,添加调试设备UDID
如何获取签名信息中的指纹信息
bm工具错误码
Entry模块的HAP和Feature模块的HAP在使用和功能上的区别是什么
- Entry类型的HAP:是应用的主模块,在module.json5配置文件中的type标签配置为“entry”类型。在同一个应用中,同一设备类型只支持一个Entry类型的HAP,通常用于实现应用的入口界面、入口图标、主特性功能等。
- Feature类型的HAP:是应用的动态特性模块,在module.json5配置文件中的type标签配置为“feature”类型。一个应用程序包可以包含一个或多个Feature类型的HAP,也可以不包含;Feature类型的HAP通常用于实现应用的特性功能,可以配置成按需下载安装,也可以配置成随Entry类型的HAP一起下载安装。
如何理解App、HAP、HAR的关系
- App是发布到应用市场的基本单元,不能直接在设备上安装和运行。
- HAP(Harmony Ability Package)是应用安装和运行的基本单元,包含代码、资源、第三方库及配置文件等,主要分为entry和feature两种类型。
- HAR(Harmony Archive)是静态共享包,包含代码、C++库、资源和配置文件。HAR支持多个模块或工程共享ArkUI组件和相关代码。
- HSP(Harmony Shared Package)是动态共享包,包含代码、C++库、资源和配置文件,用于实现代码和资源的共享。HSP跟随宿主应用的App包一起发布,与宿主应用同进程,具有相同的包名和生命周期。
应用程序包基础知识
团队内部测试
环境搭建
Windows
- 下载最新IDE
- hdc环境变量配置路径:
下载根目录\devecostudio-windows-5.0.11.100\DevEco Studio\sdk\default\openharmony\toolchains
MAC
配置环境变量
export PATH=\$PATH:/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains
export CLASSPATH HDC\_SERVER\_PORT=7035
launchctl setenv HDC\_SERVER\_PORT \$HDC\_SERVER\_PORT
export HDC\_SERVER\_PORT
\~
构建应用 >旧-编译构建
编译时可以选择debug或release
如何判断当前应用程序是Debug包还是Release包
API选择(Ctrl+Shift+ALt+S)
签名配置
可自动;也可以配置已申请的证书等
firgerprint
fingerprint对应应用包的指纹信息,取到的是应用签名证书(.cer文件)的SHA256(加密方式) hash值。官方回复
获取firgerprint的方式
- 通过鸿蒙API,日志打印;
- 通过
keytool -printcert -file <xxxxx.cer>获取对应所有者的证书指纹;
我这里显示多个证书,找到所有者是自己公司名称的证书,对应的指纹跟api获取一致。
打包相关
多包(HAP/HSP)引用相同的HAR时,会造成多包间代码和资源的重复拷贝,从而导致应用包膨大。HAR可以依赖其他HAR,但不支持循环依赖,也不支持依赖传递。
说明
循环依赖:例如有三个HSP,HSP-A、HSP-B和HSP-C,循环依赖指HSP-A依赖HSP-B,HSP-B依赖HSP-C,HSP-C又依赖HSP-A。
依赖传递:例如有三个HSP,HSP-A、HSP-B和HSP-C,依赖关系是HSP-A依赖HSP-B,HSP-B依赖HSP-C。不支持传递依赖指HSP-A可以使用HSP-B的方法和组件,但是HSP-A不能直接使用HSP-C的方法和组件。
编译har
打签名的har包
打har包时,会发现有时候打出的包,文件名不带-signed后缀。
此时,需要在hvigor-config.json5开启
"properties": {
// 构建签名HAR开关
"ohos.sign.har": true
}
编写插件 Hvigorfile.ts
实现打Har/Hap包,自动将包的文件名改成带版本号的命名
// 工程级hvigorfile.ts文件
import { hvigor, HvigorNode, HvigorPlugin } from '@ohos/hvigor';
import { appTasks, OhosHarContext, OhosPluginId, Target } from '@ohos/hvigor-ohos-plugin';
import * as fs from 'fs';
import * as path from 'path';
// 实现自定义插件
export function renameHarPlugin(): HvigorPlugin {
return {
pluginId: 'renameHarPlugin',
context() {
return {
data: 'renameHarPlugin'
};
},
async apply(currentNode: HvigorNode): Promise<void> {
hvigor.nodesEvaluated(async () => {
// 注册模块级任务
harTask(currentNode);
});
}
};
}
function harTask(currentNode: HvigorNode) {
// 等待全部节点加载完成之后获取子节点信息
currentNode.subNodes((node: HvigorNode) => {
// 获取har模块上下文信息
const harContext = node.getContext(OhosPluginId.OHOS_HAR_PLUGIN) as OhosHarContext;
const moduleName = harContext?.getModuleName();
console.log(`har模块 moduleName: ${moduleName}`);
harContext?.targets((target: Target) => {
const targetName = target.getTargetName();
const outputPath = target.getBuildTargetOutputPath();
console.log(`har模块 targetName:${targetName} outputPath: ${outputPath}`);
node.registerTask({
// 任务名称
name: `${targetName}@renameHar`,
// 任务执行逻辑主体函数
run() {
const version = harContext?.getVersion();
console.log(`har模块 version:${version}`);
const src = path.join(outputPath, `${moduleName}-signed.har`);
const dst = path.join(outputPath, `${moduleName}-signed-${version}.har`);
if (fs.existsSync(src)) {
fs.renameSync(src, dst);
console.log(`[har rename] ${src} → ${dst}`);
} else {
console.warn(`[har rename] missing: ${src}`);
}
},
// 配置前置任务依赖
// dependencies: [`${targetName}@PackageHar`],
dependencies: [`${targetName}@PackageSignHar`],
// 配置任务的后置任务依赖
postDependencies: ['assembleHar']
});
});
});
}
// 实现自定义插件
export function renameHapPlugin(): HvigorPlugin {
return {
pluginId: 'renameHapPlugin',
context() {
return {
data: 'renameHapPlugin'
};
},
async apply(currentNode: HvigorNode): Promise<void> {
hvigor.nodesEvaluated(async () => {
// 注册模块级任务
hapTask(currentNode);
});
}
};
}
function hapTask(currentNode: HvigorNode) {
// 等待全部节点加载完成之后获取子节点信息
currentNode.subNodes((node: HvigorNode) => {
// 获取hap模块上下文信息
const hapContext = node.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
const moduleName = hapContext?.getModuleName();
console.log(`hap模块 moduleName: ${moduleName}`);
hapContext?.targets((target: Target) => {
const targetName = target.getTargetName();
const outputPath = target.getBuildTargetOutputPath();
console.log(`hap模块 targetName:${targetName} outputPath: ${outputPath}`);
if(targetName == 'default'){
return
}
node.registerTask({
// 任务名称
name: `${moduleName}@renameHap`,
// 任务执行逻辑主体函数
run() {
const version = hapContext?.getVersion();
console.log(`hap模块 version:${version}`);
const src = path.join(outputPath, `${moduleName}-${targetName}-signed.hap`);
const dst = path.join(outputPath, `${moduleName}-${targetName}-signed-${version}.hap`);
if (fs.existsSync(src)) {
fs.renameSync(src, dst);
console.log(`[hap rename] ${src} → ${dst}`);
} else {
console.warn(`[hap rename] missing: ${src}`);
}
},
// 配置前置任务依赖
dependencies: [`${targetName}@SignHap`],
// 配置任务的后置任务依赖
postDependencies: ['assembleHap']
});
});
});
}
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [renameHarPlugin(), renameHapPlugin()] /* Custom plugin to extend the functionality of Hvigor. */
};
配置Product
Entry下的 build-profile.json5
{
...
"buildModeBinder": [
{
"buildModeName": "release",
"mappings": [
{
"buildOptionName": "release",
"targetName": "release"
}
]
},
{
"buildModeName": "debug",
"mappings": [
{
"buildOptionName": "debug",
"targetName": "default"
}
]
}
],
"targets": [
{
"name": "default"
},
{
"name": "release",
}
]
}
项目级的build-profile.json5
{
...
"products": [
{
"name": "default",
"signingConfig": "default",
// 标识编译应用/元服务所使用的SDK版本
"compileSdkVersion": "5.0.5(17)",
// 标识应用/元服务运行所需目标SDK版本
"targetSdkVersion": "5.0.5(17)",
// 兼容的最低SDK版本
"compatibleSdkVersion": "5.0.1(13)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
},
{
"name": "release",
"signingConfig": "default",
// 标识编译应用/元服务所使用的SDK版本
"compileSdkVersion": "5.0.5(17)",
// 标识应用/元服务运行所需目标SDK版本
"targetSdkVersion": "5.0.5(17)",
// 兼容的最低SDK版本
"compatibleSdkVersion": "5.0.1(13)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{
"name": "debug"
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "xxxEntry",
"srcPath": "./products/xxxEntry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
},
{
"name": "release",
"applyToProducts": [
"release"
]
}
]
}
]
}
多目标产物构建
类似于Android的渠道
配置多目标产物
HAR改HSP
hap hsp安装
遇到的问题: 当sdk从har改为集成态hsp后,无法直接安装hap。
需要分别安装hsp和hap。
集成态tgz因没有签名,无法直接安装的。
编译项目后,需要获取签名后的hsp,安装hsp后,才能安装hap。
hdc install xxx.hsp
hdc install xxx.hap
PS
日志
hdc file recv /data/log/hilog
基础语法
关键点记录
引用类型在内存中通过指针访问数据,修改引用会影响原始数据。 -- 一点与java一样
一些基本转换
string转number
- 使用
parseInt/parseFloat,会截断非数字部分
let str: string = "123";
let num: number = parseInt(str);
console.log(num); // 输出: 123
// 处理非数字字符(会截断非数字部分)
let str2: string = "456abc";
let num2: number = parseInt(str2);
console.log(num2); // 输出: 456
// 处理非数字字符(会截断非数字部分)
let str2: string = "456abc789";
let num2: number = parseInt(str2);
console.log(num2); // 输出: 456
// 指定进制(如16进制)
let hexStr: string = "1a";
let hexNum: number = parseInt(hexStr, 16);
console.log(hexNum); // 输出: 26
// 转float
parseFloat(...)
- 使用
Number
let validStr: string = "123";
let num4: number = Number(validStr);
console.log(num4); // 输出: 123
let invalidStr: string = "123abc";
let num5: number = Number(invalidStr);
console.log(num5); // 输出: NaN
- 使用
+运算符
let str6: string = "99";
let num6: number = +str6;
console.log(num6); // 输出: 99
let str7: string = "99.99";
let num7: number = +str7;
console.log(num7); // 输出: 99.99
number转16进制字符串
// 基本转换方法
let decimalNumber: number = 255;
// 转换为十六进制字符串(小写)
let hexString: string = decimalNumber.toString(16);
console.log(hexString); // 输出 "ff"
Uint8Array转字符串
let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
// 直接转换
let str = String.fromCharCode.apply(null, uint8Array);
// 或使用展开运算符
let str2 = String.fromCharCode(...uint8Array);
console.log(str); // 输出 "Hello"
Uint8Array转十六进制字符串
function uint8ToHex(uint8: Uint8Array): string {
return Array.from(uint8)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
}
let hex = uint8ToHex(new Uint8Array([255, 13, 171]));
console.log(hex); // 输出 "ff0dab"
字符串编码为UTF-8格式的Uint8Array
- 方式一:
let str: string = "Hello, HarmonyOS!";
let encoder = new util.TextEncoder();
let byteArray: Uint8Array = encoder.encode(str);
console.log(byteArray); // 输出UTF-8字节数组
- 方式一:
/**
* 手动将字符串编码为UTF-8格式的Uint8Array
* @param str 输入字符串
* @returns UTF-8字节数组
*/
public stringToUtf8Bytes(str: string): Uint8Array {
const bytes: number[] = [];
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
// 处理单字节(ASCII)
if (code < 0x80) {
bytes.push(code);
}
// 处理双字节(常见中文)
else if (code < 0x800) {
bytes.push(0xC0 | (code >> 6));
bytes.push(0x80 | (code & 0x3F));
}
// 处理三字节(部分生僻汉字)
else if (code < 0x10000) {
bytes.push(0xE0 | (code >> 12));
bytes.push(0x80 | ((code >> 6) & 0x3F));
bytes.push(0x80 | (code & 0x3F));
}
// 处理四字节(Emoji等)
else {
bytes.push(0xF0 | (code >> 18));
bytes.push(0x80 | ((code >> 12) & 0x3F));
bytes.push(0x80 | ((code >> 6) & 0x3F));
bytes.push(0x80 | (code & 0x3F));
}
}
return new Uint8Array(bytes);
}
ArrayBuffer与Uint8Array互转
/**
* arrayBuffer转Uint8Array
* @param buffer
* @returns
*/
public arrayBufferToUint8Array(buffer: ArrayBuffer): Uint8Array {
let temp = new Uint8Array(buffer);
return temp;
}
/**
* uint8Array转ArrayBuffer
* @param buffer
* @returns
*/
public uint8ArrayToArrayBuffer(array: Uint8Array): ArrayBuffer {
let temp = array.buffer as ArrayBuffer;
return temp;
}
固定数组长度
// 元组
[number, number]
部分操作数组的方法
// 定义示例数组
let numbers: number[] = [1, 2, 3, 4, 5];
let users: { id: number; name: string }[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
// 使用 find 查找元素
let target = numbers.find((num) => num > 3); // 返回 4
let user = users.find((u) => u.id === 2); // 返回 { id:2, name:"Bob" }
// 使用 map 转换数组
let doubled = numbers.map((num) => num * 2); // 返回 [2,4,6,8,10]
let names = users.map((u) => u.name); // 返回 ["Alice", "Bob"]
// 使用 filter 过滤数组
let evens = numbers.filter((num) => num % 2 === 0); // 返回 [2,4]
枚举相关
遍历
export enum Test {
Test_1 = 1,
Test_2 = 2,
}
// 输出value的数组
let valueArr = Object.values(Test)
.filter(value => typeof value === 'number') as Test[]
// 以枚举的key 作为dialog的显示文本
getEnumNames<T extends object>(e: T): TextCascadePickerRangeContent[] {
return Object.keys(e).filter(key => isNaN(Number(key))).map(name => ({
text: name
} as TextCascadePickerRangeContent));
}
...
this.testArr = this.getEnumNames(Test);
this.getUIContext().showTextPickerDialog({
range: this.testArr,
...
- 用string匹配枚举的key,得到对应的value
enum Direction {
Up = 1,
Down = 2,
Left = 3,
Right = 4
}
let text = "Up"
let param: Direction = Direction[text as keyof Direction]
console.log(param); // 输出 1
// 根据数值获取枚举名称的两种方式
const name1: string = Direction[1]; // "Up"
const name2: string = Direction[2]; // "Down"
// 使用示例
console.log(Direction[3]); // 输出 "Left"
console.log(Direction[Direction.Up]); // 输出 "Up"(先获取数值1,再反向映射)
仅枚举类型传参限制
在 ArkTS 中,enum(枚举)是一种特殊的值类型,用于定义一组具名常量。默认情况下,数字枚举的成员类型为 number,这意味着在函数参数声明为枚举类型时,调用方依旧可以传入任意数字而不仅限于枚举成员。要“严格”限制函数只接受枚举定义中出现的成员,而非任意数字,需要借助以下几种常见做法:
- 使用字符串枚举(String Enum) ,因字符串枚举只能接受定义中的常量而拒绝其他字符串。
- 将枚举成员组合成联合类型(Union Type) ,让函数参数类型变为各枚举成员字面量的联合,从而在编译时拒绝其他值。
- 结合类型别名(
type)与索引类型操作符(keyof typeof) ,在必要时进一步约束只允许通过枚举键名或值来调用。
2. 问题:数字枚举无法严格限制输入
如上所述,由于数字枚举成员的底层类型是 number,因此当我们定义:
enum Status {
Idle = 0,
Running = 1,
Paused = 2
}
function setStatus(s: Status) {
console.log("当前状态编号:", s);
}
// 这些调用都不会在编译时报错:
setStatus(Status.Idle); // 合法
setStatus(1); // 直接传数字 “1” 也被视作 `Status`,不报错
setStatus(42); // 任意数字同样不会提示错误
原因:ArkTS 中的“数字枚举”其实等价于一个
number类型(比如Idle是0,Running是1),因此编译阶段不会拒绝任何数字常量传入。([cnblogs.com]
2.1 为什么这在实际场景中不友好?
- 当函数只期望输入枚举声明过的几种状态或选项时,调用方如果传入一个未定义的数字(如
42),可能会导致程序逻辑错误。 - 不能在编译期保证传入的参数合法,增加了运行期检查的成本和风险。
3. 解决方案一:使用“字符串枚举”(String Enum)
最简洁的方式是 将枚举从“数字枚举”换成“字符串枚举” ,此时在函数参数声明为该枚举类型时,编译器会拒绝任何不在枚举成员列表中的字符串。
3.1 示例代码
/**
* 将数字枚举替换为字符串枚举
*/
enum ApiEnvironment {
Development = "development",
Staging = "staging",
Production = "production"
}
/**
* 函数只接受 ApiEnvironment 中定义的三个字符串,
* 其他字符串都会导致编译失败
*/
function configureEnv(env: ApiEnvironment) {
console.log(`当前环境:${env}`);
}
// 合法调用:
configureEnv(ApiEnvironment.Development);
configureEnv(ApiEnvironment.Production);
// 下面调用均在编译时报错,提示:
// “不能将类型 'string' 分配给类型 'ApiEnvironment'”
// configureEnv("testing");
// configureEnv("production"); // 即使字符串相似,也会报错
// configureEnv("Development"); // 大小写不匹配也报错
- 在上例中,
ApiEnvironment的底层类型是string,但它又仅限于三个字面量"development"、"staging"、"production"。 - 编译器在解析
configureEnv("testing")时,会发现"testing"既不是ApiEnvironment.Development,也不是其他两个合法成员,于是直接编译失败。 - 优点:简单直观,不需要额外类型别名;编译时即可保证调用方只能传预定义的字符串。
3.2 何时不适用?
- 如果项目中已经使用“数字枚举”较多、或需要与第三方库保持兼容,全面改为字符串枚举的成本较高。
- 如果枚举含义需要跟某些数字 ID 保持一致,则字符串枚举不可行。
4. 解决方案二:将枚举成员组合成“联合字面量类型”(Literal Union)
若一定要使用数字枚举,又想在函数参数处严格限制,仅允许枚举定义中的数字常量,可借助 联合类型(Union)将枚举成员列成字面量联合,再将函数参数类型标为该联合类型。
4.1 原理说明
假设我们有:
enum HttpCode {
OK = 200,
NotFound = 404,
Error = 500
}
数字枚举 HttpCode 本身等同于 number,但我们只想允许 200、404、500。可以先提取出枚举成员的字面量:
// 1. 先列出字面量联合
type HttpCodeValue = 200 | 404 | 500;
// 2. 函数只接受上述联合
function handleResponse(code: HttpCodeValue) {
console.log(`返回码:${code}`);
}
// 合法:
handleResponse(200);
handleResponse(404);
// 编译错误:
// “不能将类型 '500 | 200 | 404 | 400' 分配给类型 '200 | 404 | 500'”
// handleResponse(400);
// handleResponse(HttpCode.OK + 1);
这样
handleResponse参数类型就不再是宽泛的number,而是被限制成200 | 404 | 500,严格对应枚举成员。
4.2 结合“类型别名”与“索引类型运算符”自动提取(高级用法)
如果枚举成员较多,手动列出所有字面量会很麻烦。可以在 ArkTS 中(同 TS 的做法)结合如下写法自动提取:
enum HttpCode {
OK = 200,
NotFound = 404,
Error = 500
}
/**
* 1. keyof typeof HttpCode 会得到所有 “键名” 字面量: "OK" | "NotFound" | "Error"
* 2. typeof HttpCode[Key] 就是对应枚举成员的值,即 200 | 404 | 500
*/
type HttpCodeValue = typeof HttpCode[keyof typeof HttpCode];
/**
* 此时 HttpCodeValue 等价于:200 | 404 | 500
*/
function handleResponse(code: HttpCodeValue) {
console.log(`返回码:${code}`);
}
// 合法:
handleResponse(HttpCode.OK); // 200
handleResponse(HttpCode.NotFound); // 404
// 编译错误:
// “不能将类型 '400' 分配给类型 '200 | 404 | 500'”
// handleResponse(400);
-
typeof HttpCode得到枚举对象类型,包含键 (key) 与值 (value) 的映射,例如{ OK: 200; NotFound: 404; Error: 500; 200: "OK"; 404: "NotFound"; ... }。 -
keyof typeof HttpCode只取出“键名”,即"OK" | "NotFound" | "Error" | 200 | 404 | 500。为了只拿数字值,需要再进一步筛选:// 只保留枚举定义的“键名”对应的值 type KeyNames = keyof typeof HttpCode; // "OK" | "NotFound" | "Error" | 200 | 404 | 500 type ValueOfKeyNames = typeof HttpCode[KeyNames & string]; // 这里用 "& string"去除数值索引,最终得到:200 | 404 | 500 -
上述写法在 ArkTS 中与 TS 几乎相同,编译器会在类型检查阶段按照联合类型推断具体字面量范围。(docs.asprain.cn, docs.asprain.cn)
4.3 完整示例归纳
// 定义数字枚举
enum UserRole {
Guest = 0,
Member = 1,
Admin = 2,
SuperAdmin = 3
}
// 自动提取 “只含数值成员” 的字面量联合
type UserRoleValue = typeof UserRole[keyof typeof UserRole] & number;
// 因为 typeof UserRole[keyof typeof UserRole] 会包含 “键名索引” 的成员类型
// 所以需要 “& number” 限定只保留数值部分。
// 最终 UserRoleValue 等同于:0 | 1 | 2 | 3
// 函数仅接受 0|1|2|3,编译阶段拒绝其他数字
function assignRole(role: UserRoleValue) {
console.log("正在分配角色编号:", role);
}
// 合法:
// assignRole(UserRole.Admin); // 自动推断为 2
// assignRole(3); // 3 属于联合类型
// 编译错误:
// “不能将类型 '4' 分配给类型 '0 | 1 | 2 | 3'”
// assignRole(4);
// “不能将类型 'string' 分配给类型 '0 | 1 | 2 | 3'”
// assignRole("Admin");
注意:如果枚举很大,自动提取联合类型的写法虽然麻烦一点,但一旦写对,就能为函数“参数只允许枚举值”提供动态且可维护的解决方案,无需手动列出每个字面量。(docs.asprain.cn, docs.asprain.cn)
5. 解决方案三:在函数签名中直接使用“键名联合”或“值联合”
在一些极端场景下,也可以不借助 typeof ...[keyof typeof ...] 提取,而是直接写成“联合字面量”或“键名联合 + 索引”,如下:
5.1 “键名联合”+索引(仅允许通过枚举键名调用)
enum Status {
Ready = 0,
Waiting = 1,
Done = 2
}
/**
* 参数只能是 "Ready"|"Waiting"|"Done" 三个字符串之一,
* 然后再通过索引拿到对应的数字值。此时无法传入任意字符串或数字。
*/
function setStatusByName(name: keyof typeof Status) {
const code: Status = Status[name];
console.log(`已将状态设为 [${name} : ${code}]`);
}
// 合法:
setStatusByName("Ready");
setStatusByName("Done");
// 编译错误:
// “不能将类型 'string' 分配给类型 '"Ready" | "Waiting" | "Done"'”
// setStatusByName("Unknown");
// 由于传入必须是键名,无法直接传入数字
-
keyof typeof Status只会展开成"Ready" | "Waiting" | "Done" | 0 | 1 | 2。 -
如果你只想保留键名(字符串),则需要加上
& string:type StatusKeyName = keyof typeof Status & string; // "Ready" | "Waiting" | "Done"这样
setStatusByName只接受键名层面的字符串,进一步保证不能直接传数字。(docs.asprain.cn, docs.asprain.cn)
5.2 “值联合”直接写字面量(最简易,但不动态)
如果枚举成员较少,也可以直接在函数签名中写字面量联合:
enum Direction {
Up = 1,
Down = 2,
Left = 3,
Right = 4
}
/**
* 直接列出所有可能的字面量值进行联合
*/
function move(dir: 1 | 2 | 3 | 4) {
console.log("移动方向编号:", dir);
}
// 合法:
move(1);
// 编译错误:
// “不能将类型 '5' 分配给类型 '1 | 2 | 3 | 4'”
// move(5);
- 这种写法最为直白,但维护成本较高:每次枚举新增或删除,都得同步更新函数签名。(docs.asprain.cn, cnblogs.com)
如何查找一个元素是否存在于数组中
1. 直接使用 includes() 方法(适用于基本类型)
-
语法:
array.includes(targetElement) -
特点:直接返回布尔值,简单高效,但仅适用于基本类型(如
number,string,boolean) ,无法直接判断对象引用。 -
示例: let numbers: number[] = [1, 2, 3, 4, 5]; let hasThree = numbers.includes(3); // 返回 true let hasSix = numbers.includes(6); // 返回 false
2. 使用 indexOf() 方法(兼容性更广)
-
语法:
array.indexOf(targetElement) !== -1 -
特点:返回元素索引,若不存在则返回
-1。兼容性较好,但同样不适用于对象引用比较。 -
示例:
let fruits: string[] = ["apple", "banana", "orange"]; let hasBanana = fruits.indexOf("banana") !== -1; // 返回 true
3. 使用 some() 方法(灵活通用,支持对象和条件判断)
-
语法:
array.some((item) => { /* 条件 */ }) -
特点:可自定义判断条件,适用于所有类型(包括对象) ,是判断复杂元素的推荐方法。
-
示例:
// 基本类型 let numbers = [1, 2, 3]; let hasEven = numbers.some(num => num % 2 === 0); // 检查是否存在偶数 → true // 对象类型(需匹配属性) interface User { id: number; name: string; } let users: User[] = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]; let hasUserBob = users.some(user => user.name === "Bob"); // 返回 true
4. 使用 find() 方法(需判断返回值)
-
语法:
array.find((item) => { /* 条件 */ }) !== undefined -
特点:返回匹配的元素,若未找到则返回
undefined。适用于需要同时获取元素的场景。 -
示例:
let users: User[] = [ /* ... */ ]; let isBobExist = users.find(user => user.name === "Bob") !== undefined; // true
总结:如何选择?
| 方法 | 适用场景 | 对象支持 | 直接返回值 |
|---|---|---|---|
includes() | 基本类型的精确匹配 | ❌ | 布尔值 |
indexOf() | 基本类型且需兼容性 | ❌ | 索引值 |
some() | 对象或复杂条件判断(推荐) | ✔️ | 布尔值 |
find() | 需同时获取匹配元素 | ✔️ | 元素或undefined |
注意事项
- 对象比较:
includes()和indexOf()通过引用地址判断是否相等,若需检查对象属性是否匹配,必须用some()或find()。 - 性能:对于大型数组,
some()和find()在找到第一个匹配项后立即停止遍历,效率较高。
for循环中串行Promise函数
方案一:使用 async/await + for 循环(推荐)
通过 async 函数配合 await 关键字,实现直观的串行控制流:
// 示例方法A:模拟异步操作
function methodA(param: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`执行完成: ${param}`);
resolve();
}, 1000);
});
}
// 串行执行函数
async function runSerial() {
const items = [1, 2, 3, 4]; // 待处理数据
for (const item of items) {
// 等待当前Promise完成后再进入下一轮循环
await methodA(item);
}
console.log("所有任务完成");
}
// 执行
runSerial();
输出结果(每秒输出一行):
执行完成: 1
执行完成: 2
执行完成: 3
执行完成: 4
所有任务完成
方案二:使用 Array.reduce 链式调用
通过 reduce 构建 Promise 链,实现无 async/await 的串行控制:
function runSerialReduce() {
const items = [1, 2, 3, 4];
items.reduce((prevPromise, currentItem) => {
return prevPromise.then(() => {
return methodA(currentItem);
});
}, Promise.resolve()) // 初始Promise
.then(() => {
console.log("所有任务完成");
});
}
// 执行
runSerialReduce();
关键差异说明
| 特性 | async/await + for 循环 | reduce + Promise 链 |
|---|---|---|
| 可读性 | 高(符合同步代码习惯) | 中(需要理解 Promise 链式逻辑) |
| 错误处理 | 可直接用 try/catch 捕获 | 需在 .catch() 中处理 |
| 动态中断能力 | 支持(通过 break 或 return) | 不支持(需额外状态控制) |
| 适用场景 | 大多数顺序执行场景 | 函数式编程偏好场景 |
扩展:带结果收集的串行执行
若需要保留每个 Promise 的结果:
async function runSerialWithResults() {
const items = [1, 2, 3, 4];
const results: number[] = [];
for (const item of items) {
const result = await methodA(item); // 假设methodA返回实际结果
results.push(result);
}
console.log("收集结果:", results);
}