fix: move skill directories to repo root and update AGENTS.md structure diagram
This commit is contained in:
503
arkts-development/references/api-reference.md
Normal file
503
arkts-development/references/api-reference.md
Normal file
@@ -0,0 +1,503 @@
|
||||
# HarmonyOS API Reference
|
||||
|
||||
Common HarmonyOS APIs for ArkTS development.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Router Navigation](#router-navigation)
|
||||
2. [HTTP Networking](#http-networking)
|
||||
3. [Preferences Storage](#preferences-storage)
|
||||
4. [File Operations](#file-operations)
|
||||
5. [Device Info](#device-info)
|
||||
6. [Prompt & Dialog](#prompt--dialog)
|
||||
7. [Media](#media)
|
||||
|
||||
---
|
||||
|
||||
## Router Navigation
|
||||
|
||||
```typescript
|
||||
import { router } from '@kit.ArkUI';
|
||||
```
|
||||
|
||||
### Push Page
|
||||
|
||||
```typescript
|
||||
router.pushUrl({
|
||||
url: 'pages/DetailPage',
|
||||
params: {
|
||||
id: 123,
|
||||
title: 'Detail'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Push with Mode
|
||||
|
||||
```typescript
|
||||
// Standard mode (default) - adds to stack
|
||||
router.pushUrl({
|
||||
url: 'pages/Page',
|
||||
}, router.RouterMode.Standard);
|
||||
|
||||
// Single mode - reuses if exists
|
||||
router.pushUrl({
|
||||
url: 'pages/Page',
|
||||
}, router.RouterMode.Single);
|
||||
```
|
||||
|
||||
### Replace Page
|
||||
|
||||
```typescript
|
||||
router.replaceUrl({
|
||||
url: 'pages/NewPage'
|
||||
});
|
||||
```
|
||||
|
||||
### Back Navigation
|
||||
|
||||
```typescript
|
||||
// Back to previous
|
||||
router.back();
|
||||
|
||||
// Back to specific page
|
||||
router.back({
|
||||
url: 'pages/HomePage'
|
||||
});
|
||||
|
||||
// Back with result
|
||||
router.back({
|
||||
url: 'pages/HomePage',
|
||||
params: { result: 'success' }
|
||||
});
|
||||
```
|
||||
|
||||
### Get Parameters
|
||||
|
||||
```typescript
|
||||
// In target page
|
||||
aboutToAppear(): void {
|
||||
const params = router.getParams() as Record<string, Object>;
|
||||
if (params) {
|
||||
const id = params['id'] as number;
|
||||
const title = params['title'] as string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Router State
|
||||
|
||||
```typescript
|
||||
const state = router.getState();
|
||||
console.log('Current page:', state.name);
|
||||
console.log('Page path:', state.path);
|
||||
console.log('Stack index:', state.index);
|
||||
```
|
||||
|
||||
### Clear Router Stack
|
||||
|
||||
```typescript
|
||||
router.clear();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Networking
|
||||
|
||||
```typescript
|
||||
import { http } from '@kit.NetworkKit';
|
||||
```
|
||||
|
||||
### GET Request
|
||||
|
||||
```typescript
|
||||
async function getData(): Promise<void> {
|
||||
const httpRequest = http.createHttp();
|
||||
|
||||
try {
|
||||
const response = await httpRequest.request(
|
||||
'https://api.example.com/data',
|
||||
{
|
||||
method: http.RequestMethod.GET,
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
connectTimeout: 60000,
|
||||
readTimeout: 60000
|
||||
}
|
||||
);
|
||||
|
||||
if (response.responseCode === 200) {
|
||||
const data = JSON.parse(response.result as string);
|
||||
console.log('Data:', JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Request failed:', error);
|
||||
} finally {
|
||||
httpRequest.destroy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST Request
|
||||
|
||||
```typescript
|
||||
async function postData(body: object): Promise<void> {
|
||||
const httpRequest = http.createHttp();
|
||||
|
||||
try {
|
||||
const response = await httpRequest.request(
|
||||
'https://api.example.com/submit',
|
||||
{
|
||||
method: http.RequestMethod.POST,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer token'
|
||||
},
|
||||
extraData: JSON.stringify(body)
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Response code:', response.responseCode);
|
||||
} finally {
|
||||
httpRequest.destroy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Request Options
|
||||
|
||||
```typescript
|
||||
interface HttpRequestOptions {
|
||||
method: http.RequestMethod; // GET, POST, PUT, DELETE, etc.
|
||||
header?: Object; // Request headers
|
||||
extraData?: string | Object; // Request body
|
||||
connectTimeout?: number; // Connection timeout (ms)
|
||||
readTimeout?: number; // Read timeout (ms)
|
||||
expectDataType?: http.HttpDataType;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preferences Storage
|
||||
|
||||
```typescript
|
||||
import { preferences } from '@kit.ArkData';
|
||||
```
|
||||
|
||||
### Get Preferences Instance
|
||||
|
||||
```typescript
|
||||
// In component with context
|
||||
const dataPreferences = await preferences.getPreferences(
|
||||
this.context,
|
||||
'myPreferencesStore'
|
||||
);
|
||||
```
|
||||
|
||||
### Write Data
|
||||
|
||||
```typescript
|
||||
await dataPreferences.put('username', 'John');
|
||||
await dataPreferences.put('age', 25);
|
||||
await dataPreferences.put('isVip', true);
|
||||
await dataPreferences.put('scores', [90, 85, 92]);
|
||||
await dataPreferences.flush(); // Persist to disk
|
||||
```
|
||||
|
||||
### Read Data
|
||||
|
||||
```typescript
|
||||
// With default values
|
||||
const username = await dataPreferences.get('username', '') as string;
|
||||
const age = await dataPreferences.get('age', 0) as number;
|
||||
const isVip = await dataPreferences.get('isVip', false) as boolean;
|
||||
```
|
||||
|
||||
### Check Key Exists
|
||||
|
||||
```typescript
|
||||
const hasKey = await dataPreferences.has('username');
|
||||
```
|
||||
|
||||
### Delete Data
|
||||
|
||||
```typescript
|
||||
await dataPreferences.delete('username');
|
||||
await dataPreferences.flush();
|
||||
```
|
||||
|
||||
### Clear All
|
||||
|
||||
```typescript
|
||||
await dataPreferences.clear();
|
||||
await dataPreferences.flush();
|
||||
```
|
||||
|
||||
### Delete Preferences File
|
||||
|
||||
```typescript
|
||||
await preferences.deletePreferences(this.context, 'myPreferencesStore');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Operations
|
||||
|
||||
```typescript
|
||||
import { fileIo as fs } from '@kit.CoreFileKit';
|
||||
```
|
||||
|
||||
### Get Application Paths
|
||||
|
||||
```typescript
|
||||
// In AbilityStage or UIAbility
|
||||
const filesDir = this.context.filesDir; // /data/app/.../files
|
||||
const cacheDir = this.context.cacheDir; // /data/app/.../cache
|
||||
const tempDir = this.context.tempDir; // /data/app/.../temp
|
||||
```
|
||||
|
||||
### Write File
|
||||
|
||||
```typescript
|
||||
const filePath = `${this.context.filesDir}/data.txt`;
|
||||
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
|
||||
fs.writeSync(file.fd, 'Hello, HarmonyOS!');
|
||||
fs.closeSync(file);
|
||||
```
|
||||
|
||||
### Read File
|
||||
|
||||
```typescript
|
||||
const filePath = `${this.context.filesDir}/data.txt`;
|
||||
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
|
||||
const buffer = new ArrayBuffer(4096);
|
||||
const readLen = fs.readSync(file.fd, buffer);
|
||||
const content = String.fromCharCode(...new Uint8Array(buffer.slice(0, readLen)));
|
||||
fs.closeSync(file);
|
||||
```
|
||||
|
||||
### Check File Exists
|
||||
|
||||
```typescript
|
||||
const exists = fs.accessSync(filePath);
|
||||
```
|
||||
|
||||
### Delete File
|
||||
|
||||
```typescript
|
||||
fs.unlinkSync(filePath);
|
||||
```
|
||||
|
||||
### List Directory
|
||||
|
||||
```typescript
|
||||
const files = fs.listFileSync(this.context.filesDir);
|
||||
files.forEach((file: string) => {
|
||||
console.log('File:', file);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device Info
|
||||
|
||||
```typescript
|
||||
import { deviceInfo } from '@kit.BasicServicesKit';
|
||||
```
|
||||
|
||||
### Get Device Information
|
||||
|
||||
```typescript
|
||||
const brand = deviceInfo.brand; // e.g., "HUAWEI"
|
||||
const model = deviceInfo.productModel; // e.g., "Mate 60"
|
||||
const osVersion = deviceInfo.osFullName; // e.g., "HarmonyOS 5.0"
|
||||
const sdkVersion = deviceInfo.sdkApiVersion; // e.g., 12
|
||||
const deviceType = deviceInfo.deviceType; // e.g., "phone", "tablet"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt & Dialog
|
||||
|
||||
```typescript
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
```
|
||||
|
||||
### Toast
|
||||
|
||||
```typescript
|
||||
promptAction.showToast({
|
||||
message: 'Operation successful',
|
||||
duration: 2000,
|
||||
bottom: 80
|
||||
});
|
||||
```
|
||||
|
||||
### Alert Dialog
|
||||
|
||||
```typescript
|
||||
promptAction.showDialog({
|
||||
title: 'Confirm',
|
||||
message: 'Are you sure you want to delete?',
|
||||
buttons: [
|
||||
{ text: 'Cancel', color: '#999999' },
|
||||
{ text: 'Delete', color: '#FF0000' }
|
||||
]
|
||||
}).then((result) => {
|
||||
if (result.index === 1) {
|
||||
// Delete confirmed
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Action Sheet
|
||||
|
||||
```typescript
|
||||
promptAction.showActionMenu({
|
||||
title: 'Select Option',
|
||||
buttons: [
|
||||
{ text: 'Camera', color: '#000000' },
|
||||
{ text: 'Gallery', color: '#000000' },
|
||||
{ text: 'Cancel', color: '#999999' }
|
||||
]
|
||||
}).then((result) => {
|
||||
switch (result.index) {
|
||||
case 0: // Camera
|
||||
break;
|
||||
case 1: // Gallery
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Dialog
|
||||
|
||||
```typescript
|
||||
@CustomDialog
|
||||
struct ConfirmDialog {
|
||||
controller: CustomDialogController;
|
||||
title: string = '';
|
||||
onConfirm: () => void = () => {};
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title).fontSize(20).margin({ bottom: 20 })
|
||||
Row({ space: 20 }) {
|
||||
Button('Cancel')
|
||||
.onClick(() => { this.controller.close(); })
|
||||
Button('Confirm')
|
||||
.onClick(() => {
|
||||
this.onConfirm();
|
||||
this.controller.close();
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in component
|
||||
@Entry
|
||||
@Component
|
||||
struct DialogExample {
|
||||
dialogController: CustomDialogController = new CustomDialogController({
|
||||
builder: ConfirmDialog({
|
||||
title: 'Delete Item?',
|
||||
onConfirm: () => { this.handleDelete(); }
|
||||
}),
|
||||
autoCancel: true
|
||||
});
|
||||
|
||||
handleDelete(): void {
|
||||
// Delete logic
|
||||
}
|
||||
|
||||
build() {
|
||||
Button('Show Dialog')
|
||||
.onClick(() => { this.dialogController.open(); })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Media
|
||||
|
||||
### Image Picker
|
||||
|
||||
```typescript
|
||||
import { photoAccessHelper } from '@kit.MediaLibraryKit';
|
||||
import { picker } from '@kit.CoreFileKit';
|
||||
|
||||
async function pickImage(): Promise<string | null> {
|
||||
const photoPicker = new picker.PhotoViewPicker();
|
||||
|
||||
try {
|
||||
const result = await photoPicker.select({
|
||||
MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
|
||||
maxSelectNumber: 1
|
||||
});
|
||||
|
||||
if (result.photoUris.length > 0) {
|
||||
return result.photoUris[0];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Pick image failed:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### Camera Capture
|
||||
|
||||
```typescript
|
||||
import { camera } from '@kit.CameraKit';
|
||||
|
||||
// Request camera permission first
|
||||
// Then use camera APIs for capture
|
||||
```
|
||||
|
||||
### Audio Playback
|
||||
|
||||
```typescript
|
||||
import { media } from '@kit.MediaKit';
|
||||
|
||||
async function playAudio(uri: string): Promise<void> {
|
||||
const player = await media.createAVPlayer();
|
||||
|
||||
player.on('stateChange', (state: string) => {
|
||||
if (state === 'prepared') {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
|
||||
player.url = uri;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Import Patterns
|
||||
|
||||
```typescript
|
||||
// UI Kit
|
||||
import { router, promptAction } from '@kit.ArkUI';
|
||||
|
||||
// Network Kit
|
||||
import { http } from '@kit.NetworkKit';
|
||||
|
||||
// Data Kit
|
||||
import { preferences } from '@kit.ArkData';
|
||||
|
||||
// File Kit
|
||||
import { fileIo as fs, picker } from '@kit.CoreFileKit';
|
||||
|
||||
// Basic Services
|
||||
import { deviceInfo } from '@kit.BasicServicesKit';
|
||||
|
||||
// Media Kit
|
||||
import { media } from '@kit.MediaKit';
|
||||
```
|
||||
126
arkts-development/references/arkguard-obfuscation.md
Normal file
126
arkts-development/references/arkguard-obfuscation.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# ArkGuard 代码混淆指南
|
||||
|
||||
ArkGuard 是 HarmonyOS 官方推荐的代码混淆工具,用于提升应用安全性,防止逆向分析。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- **DevEco Studio**: 5.0.3.600 及以上版本
|
||||
- **项目模型**: 仅支持 Stage 模型
|
||||
- **生效模式**: 仅在 Release 模式下生效
|
||||
|
||||
## 开启混淆
|
||||
|
||||
在模块的 `build-profile.json5` 中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"arkOptions": {
|
||||
"obfuscation": {
|
||||
"ruleOptions": {
|
||||
"enable": true,
|
||||
"files": ["./obfuscation-rules.txt"]
|
||||
},
|
||||
"consumerFiles": ["./consumer-rules.txt"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 混淆规则配置
|
||||
|
||||
在项目根目录创建 `obfuscation-rules.txt`:
|
||||
|
||||
```text
|
||||
# 开启属性混淆
|
||||
-enable-property-obfuscation
|
||||
|
||||
# 开启顶层作用域名称混淆
|
||||
-enable-toplevel-obfuscation
|
||||
|
||||
# 开启文件名混淆
|
||||
-enable-filename-obfuscation
|
||||
|
||||
# 开启导入导出名称混淆
|
||||
-enable-export-obfuscation
|
||||
```
|
||||
|
||||
## 白名单配置
|
||||
|
||||
某些名称不能混淆(如动态属性名、API 字段、数据库字段等):
|
||||
|
||||
```text
|
||||
# 保留属性名
|
||||
-keep-property-name apiKey
|
||||
-keep-property-name userId
|
||||
-keep-property-name responseData
|
||||
|
||||
# 保留全局名称
|
||||
-keep-global-name AppConfig
|
||||
|
||||
# 保留文件名
|
||||
-keep-file-name MainPage
|
||||
-keep-file-name LoginPage
|
||||
```
|
||||
|
||||
## 配置文件说明
|
||||
|
||||
| 配置文件 | 作用 | 可修改 | 影响范围 |
|
||||
|---------|------|:------:|---------|
|
||||
| `obfuscation-rules.txt` | 本模块编译时的混淆规则 | ✓ | 本模块 |
|
||||
| `consumer-rules.txt` | 本模块被依赖时的混淆规则(建议仅配置保留项) | ✓ | 依赖此模块的模块 |
|
||||
| `obfuscation.txt` | HAR/HSP 构建产物,自动生成 | ✗ | 依赖模块 |
|
||||
|
||||
## 常用混淆选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `-enable-property-obfuscation` | 混淆对象属性名 |
|
||||
| `-enable-toplevel-obfuscation` | 混淆顶层作用域的变量和函数名 |
|
||||
| `-enable-filename-obfuscation` | 混淆文件名 |
|
||||
| `-enable-export-obfuscation` | 混淆导入导出的名称 |
|
||||
| `-disable-obfuscation` | 临时禁用混淆(用于调试) |
|
||||
|
||||
## 白名单选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `-keep-property-name <name>` | 保留指定属性名不被混淆 |
|
||||
| `-keep-global-name <name>` | 保留指定全局名称不被混淆 |
|
||||
| `-keep-file-name <name>` | 保留指定文件名不被混淆 |
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 排查步骤
|
||||
|
||||
1. **确认是否与混淆相关**: 临时添加 `-disable-obfuscation` 禁用混淆,验证问题是否消失
|
||||
2. **定位问题字段**: 根据崩溃日志定位被混淆的关键字段
|
||||
3. **添加白名单**: 将问题字段加入 `-keep-property-name` 白名单
|
||||
|
||||
### 常见需要保留的场景
|
||||
|
||||
- **网络请求**: 接口传参字段名、响应数据字段名
|
||||
- **数据库操作**: 表字段名
|
||||
- **系统 API**: 系统回调参数
|
||||
- **三方库接口**: 三方库要求的字段名
|
||||
|
||||
### 示例:网络请求字段保留
|
||||
|
||||
```text
|
||||
# API 请求/响应字段
|
||||
-keep-property-name code
|
||||
-keep-property-name message
|
||||
-keep-property-name data
|
||||
-keep-property-name token
|
||||
-keep-property-name userId
|
||||
```
|
||||
|
||||
## 验证混淆效果
|
||||
|
||||
1. 切换到 **Release** 模式编译
|
||||
2. 检查构建产物
|
||||
3. 使用反编译工具验证类名/方法名/属性名是否已混淆
|
||||
4. 测试应用功能是否正常
|
||||
|
||||
## 参考
|
||||
|
||||
- [华为官方文档 - ArkGuard](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-arkguard)
|
||||
160
arkts-development/references/codelinter.md
Normal file
160
arkts-development/references/codelinter.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# CodeLinter 代码检查工具
|
||||
|
||||
codelinter 是 HarmonyOS 的代码检查与修复工具,可集成到门禁或 CI/CD 环境中。
|
||||
|
||||
## 命令格式
|
||||
|
||||
```bash
|
||||
codelinter [options] [dir]
|
||||
```
|
||||
|
||||
- `options`: 可选配置参数
|
||||
- `dir`: 待检查的工程根目录(可选,默认为当前目录)
|
||||
|
||||
## 命令参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--config, -c <filepath>` | 指定规则配置文件 (code-linter.json5) |
|
||||
| `--fix` | 检查同时执行自动修复 |
|
||||
| `--format, -f <format>` | 输出格式: `default`/`json`/`xml`/`html` |
|
||||
| `--output, -o <filepath>` | 指定结果保存位置(不在命令行显示) |
|
||||
| `--version, -v` | 查看版本 |
|
||||
| `--product, -p <productName>` | 指定生效的 product |
|
||||
| `--incremental, -i` | 仅检查 Git 增量文件(新增/修改/重命名) |
|
||||
| `--help, -h` | 查询帮助 |
|
||||
| `--exit-on, -e <levels>` | 指定返回非零退出码的告警级别 |
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 在工程根目录下执行
|
||||
|
||||
```bash
|
||||
# 使用默认规则检查当前工程
|
||||
codelinter
|
||||
|
||||
# 指定规则配置文件
|
||||
codelinter -c ./code-linter.json5
|
||||
|
||||
# 检查并自动修复
|
||||
codelinter -c ./code-linter.json5 --fix
|
||||
```
|
||||
|
||||
### 在非工程目录下执行
|
||||
|
||||
```bash
|
||||
# 检查指定工程目录
|
||||
codelinter /path/to/project
|
||||
|
||||
# 检查多个目录或文件
|
||||
codelinter dir1 dir2 file1.ets
|
||||
|
||||
# 指定规则文件和工程目录
|
||||
codelinter -c /path/to/code-linter.json5 /path/to/project
|
||||
|
||||
# 检查并修复指定工程
|
||||
codelinter -c ./code-linter.json5 /path/to/project --fix
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
```bash
|
||||
# 默认文本格式输出到命令行
|
||||
codelinter /path/to/project
|
||||
|
||||
# JSON 格式输出
|
||||
codelinter /path/to/project -f json
|
||||
|
||||
# HTML 格式保存到文件
|
||||
codelinter /path/to/project -f html -o ./report.html
|
||||
|
||||
# XML 格式保存到文件
|
||||
codelinter /path/to/project -f xml -o ./report.xml
|
||||
```
|
||||
|
||||
## 增量检查
|
||||
|
||||
对 Git 工程中的增量文件执行检查(仅检查新增、修改、重命名的文件):
|
||||
|
||||
```bash
|
||||
codelinter -i
|
||||
codelinter --incremental
|
||||
```
|
||||
|
||||
## 指定 Product
|
||||
|
||||
当工程存在多个 product 时,指定生效的 product:
|
||||
|
||||
```bash
|
||||
codelinter -p free /path/to/project
|
||||
codelinter --product default
|
||||
```
|
||||
|
||||
## 退出码 (--exit-on)
|
||||
|
||||
用于 CI/CD 中根据告警级别控制流程。告警级别:`error`、`warn`、`suggestion`
|
||||
|
||||
退出码计算方式(3位二进制数,从高到低表示 error, warn, suggestion):
|
||||
|
||||
| 配置 | 检查结果包含 | 二进制 | 退出码 |
|
||||
|------|-------------|--------|--------|
|
||||
| `--exit-on error` | error, warn, suggestion | 100 | 4 |
|
||||
| `--exit-on error` | warn, suggestion | 000 | 0 |
|
||||
| `--exit-on error,warn` | error, warn | 110 | 6 |
|
||||
| `--exit-on error,warn,suggestion` | error | 100 | 4 |
|
||||
| `--exit-on error,warn,suggestion` | error, warn, suggestion | 111 | 7 |
|
||||
|
||||
```bash
|
||||
# 仅 error 级别返回非零退出码
|
||||
codelinter --exit-on error
|
||||
|
||||
# error 和 warn 级别返回非零退出码
|
||||
codelinter --exit-on error,warn
|
||||
|
||||
# 所有级别都返回非零退出码
|
||||
codelinter --exit-on error,warn,suggestion
|
||||
```
|
||||
|
||||
## CI/CD 集成示例
|
||||
|
||||
```bash
|
||||
# 完整的 CI 检查流程
|
||||
codelinter -c ./code-linter.json5 \
|
||||
-f json \
|
||||
-o ./codelinter-report.json \
|
||||
--exit-on error,warn
|
||||
|
||||
# 增量检查(仅检查变更文件)
|
||||
codelinter -i -c ./code-linter.json5 --exit-on error
|
||||
|
||||
# 检查并自动修复,生成 HTML 报告
|
||||
codelinter -c ./code-linter.json5 \
|
||||
--fix \
|
||||
-f html \
|
||||
-o ./codelinter-report.html
|
||||
```
|
||||
|
||||
## 规则配置文件 (code-linter.json5)
|
||||
|
||||
默认规则清单可在检查完成后,根据命令行提示查看生成的 `code-linter.json5` 文件。
|
||||
|
||||
示例配置:
|
||||
|
||||
```json5
|
||||
{
|
||||
"files": [
|
||||
"**/*.ets",
|
||||
"**/*.ts"
|
||||
],
|
||||
"ignore": [
|
||||
"**/node_modules/**",
|
||||
"**/oh_modules/**",
|
||||
"**/build/**"
|
||||
],
|
||||
"ruleSet": ["plugin:@ohos/recommended"],
|
||||
"rules": {
|
||||
"@ohos/no-any": "error",
|
||||
"@ohos/no-console": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
521
arkts-development/references/component-patterns.md
Normal file
521
arkts-development/references/component-patterns.md
Normal file
@@ -0,0 +1,521 @@
|
||||
# ArkUI Component Patterns
|
||||
|
||||
Advanced component patterns and best practices for ArkTS development.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Component Structure](#component-structure)
|
||||
2. [State Management Patterns](#state-management-patterns)
|
||||
3. [Parent-Child Communication](#parent-child-communication)
|
||||
4. [List Optimization](#list-optimization)
|
||||
5. [Custom Components](#custom-components)
|
||||
6. [Conditional Rendering](#conditional-rendering)
|
||||
|
||||
---
|
||||
|
||||
## Component Structure
|
||||
|
||||
### Basic Component
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct MyComponent {
|
||||
// Private properties
|
||||
private readonly TAG: string = 'MyComponent';
|
||||
|
||||
// State properties
|
||||
@State isLoading: boolean = false;
|
||||
|
||||
// Props from parent
|
||||
@Prop title: string = '';
|
||||
|
||||
// Lifecycle
|
||||
aboutToAppear(): void {
|
||||
console.log(this.TAG, 'aboutToAppear');
|
||||
}
|
||||
|
||||
// Build method (required)
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Entry Component (Page)
|
||||
|
||||
```typescript
|
||||
@Entry
|
||||
@Component
|
||||
struct HomePage {
|
||||
@State currentTab: number = 0;
|
||||
|
||||
onPageShow(): void {
|
||||
// Called when page becomes visible
|
||||
}
|
||||
|
||||
onPageHide(): void {
|
||||
// Called when page becomes hidden
|
||||
}
|
||||
|
||||
onBackPress(): boolean {
|
||||
// Return true to prevent default back behavior
|
||||
return false;
|
||||
}
|
||||
|
||||
build() {
|
||||
Navigation() {
|
||||
// Page content
|
||||
}
|
||||
.title('Home')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Patterns
|
||||
|
||||
### @State - Component Internal State
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct Counter {
|
||||
@State count: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(`Count: ${this.count}`)
|
||||
Button('Increment')
|
||||
.onClick(() => { this.count++; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Prop - One-Way Binding (Parent → Child)
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct DisplayCard {
|
||||
@Prop title: string = '';
|
||||
@Prop value: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title).fontSize(16)
|
||||
Text(`${this.value}`).fontSize(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct Dashboard {
|
||||
@State temperature: number = 25;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
DisplayCard({ title: 'Temperature', value: this.temperature })
|
||||
Button('Update')
|
||||
.onClick(() => { this.temperature++; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Link - Two-Way Binding
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct EditableInput {
|
||||
@Link inputValue: string;
|
||||
|
||||
build() {
|
||||
TextInput({ text: this.inputValue })
|
||||
.onChange((value: string) => {
|
||||
this.inputValue = value;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct FormPage {
|
||||
@State username: string = '';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(`Username: ${this.username}`)
|
||||
EditableInput({ inputValue: $username }) // Note: $ prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Provide/@Consume - Cross-Level State
|
||||
|
||||
```typescript
|
||||
// Ancestor component
|
||||
@Entry
|
||||
@Component
|
||||
struct App {
|
||||
@Provide('theme') theme: string = 'light';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
SettingsPage()
|
||||
Button('Toggle Theme')
|
||||
.onClick(() => {
|
||||
this.theme = this.theme === 'light' ? 'dark' : 'light';
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Descendant component (any depth)
|
||||
@Component
|
||||
struct ThemedCard {
|
||||
@Consume('theme') theme: string;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text('Card Content')
|
||||
.fontColor(this.theme === 'light' ? Color.Black : Color.White)
|
||||
}
|
||||
.backgroundColor(this.theme === 'light' ? Color.White : Color.Black)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Observed/@ObjectLink - Nested Object Observation
|
||||
|
||||
```typescript
|
||||
// Observable class
|
||||
@Observed
|
||||
class Task {
|
||||
id: number;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
|
||||
constructor(id: number, title: string) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Child component with object link
|
||||
@Component
|
||||
struct TaskItem {
|
||||
@ObjectLink task: Task;
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Checkbox()
|
||||
.select(this.task.completed)
|
||||
.onChange((value: boolean) => {
|
||||
this.task.completed = value;
|
||||
})
|
||||
Text(this.task.title)
|
||||
.decoration({
|
||||
type: this.task.completed ? TextDecorationType.LineThrough : TextDecorationType.None
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct TaskList {
|
||||
@State tasks: Task[] = [
|
||||
new Task(1, 'Buy groceries'),
|
||||
new Task(2, 'Read book')
|
||||
];
|
||||
|
||||
build() {
|
||||
List() {
|
||||
ForEach(this.tasks, (task: Task) => {
|
||||
ListItem() {
|
||||
TaskItem({ task: task })
|
||||
}
|
||||
}, (task: Task) => task.id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @StorageLink/@StorageProp - AppStorage Binding
|
||||
|
||||
```typescript
|
||||
// Initialize in EntryAbility
|
||||
AppStorage.setOrCreate('userToken', '');
|
||||
AppStorage.setOrCreate('isLoggedIn', false);
|
||||
|
||||
// Component with storage binding
|
||||
@Entry
|
||||
@Component
|
||||
struct ProfilePage {
|
||||
@StorageLink('userToken') token: string = ''; // Two-way
|
||||
@StorageProp('isLoggedIn') isLoggedIn: boolean = false; // One-way
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
if (this.isLoggedIn) {
|
||||
Text('Welcome!')
|
||||
Button('Logout')
|
||||
.onClick(() => {
|
||||
this.token = '';
|
||||
AppStorage.set('isLoggedIn', false);
|
||||
})
|
||||
} else {
|
||||
Text('Please login')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parent-Child Communication
|
||||
|
||||
### Events via Callback
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct SearchBar {
|
||||
private onSearch: (query: string) => void = () => {};
|
||||
@State query: string = '';
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
TextInput({ placeholder: 'Search...' })
|
||||
.onChange((value: string) => { this.query = value; })
|
||||
Button('Search')
|
||||
.onClick(() => { this.onSearch(this.query); })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct SearchPage {
|
||||
@State results: string[] = [];
|
||||
|
||||
handleSearch(query: string): void {
|
||||
// Perform search
|
||||
this.results = [`Result for: ${query}`];
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
SearchBar({ onSearch: (q: string) => this.handleSearch(q) })
|
||||
ForEach(this.results, (item: string) => {
|
||||
Text(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## List Optimization
|
||||
|
||||
### LazyForEach for Large Lists
|
||||
|
||||
```typescript
|
||||
// Data source implementing IDataSource
|
||||
class MyDataSource implements IDataSource {
|
||||
private data: string[] = [];
|
||||
private listeners: DataChangeListener[] = [];
|
||||
|
||||
constructor(data: string[]) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
totalCount(): number {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
getData(index: number): string {
|
||||
return this.data[index];
|
||||
}
|
||||
|
||||
registerDataChangeListener(listener: DataChangeListener): void {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
unregisterDataChangeListener(listener: DataChangeListener): void {
|
||||
const idx = this.listeners.indexOf(listener);
|
||||
if (idx >= 0) {
|
||||
this.listeners.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component with LazyForEach
|
||||
@Entry
|
||||
@Component
|
||||
struct LargeList {
|
||||
private dataSource: MyDataSource = new MyDataSource(
|
||||
Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
|
||||
);
|
||||
|
||||
build() {
|
||||
List() {
|
||||
LazyForEach(this.dataSource, (item: string, index: number) => {
|
||||
ListItem() {
|
||||
Text(item).fontSize(16).padding(10)
|
||||
}
|
||||
}, (item: string) => item)
|
||||
}
|
||||
.cachedCount(5) // Number of items to cache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ForEach Key Function
|
||||
|
||||
```typescript
|
||||
// Always provide a unique key function
|
||||
ForEach(this.items, (item: Item) => {
|
||||
ListItem() { ItemCard({ item: item }) }
|
||||
}, (item: Item) => item.id.toString()) // Unique key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Components
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
```typescript
|
||||
@Entry
|
||||
@Component
|
||||
struct BuilderExample {
|
||||
@Builder
|
||||
CardBuilder(title: string, content: string) {
|
||||
Column() {
|
||||
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
|
||||
Text(content).fontSize(14)
|
||||
}
|
||||
.padding(16)
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(8)
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 16 }) {
|
||||
this.CardBuilder('Card 1', 'Content 1')
|
||||
this.CardBuilder('Card 2', 'Content 2')
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### BuilderParam for Slot Pattern
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct Card {
|
||||
@BuilderParam content: () => void = this.defaultContent;
|
||||
|
||||
@Builder
|
||||
defaultContent() {
|
||||
Text('Default Content')
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
this.content()
|
||||
}
|
||||
.padding(16)
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
@Entry
|
||||
@Component
|
||||
struct SlotExample {
|
||||
build() {
|
||||
Column() {
|
||||
Card() {
|
||||
Column() {
|
||||
Text('Custom Title')
|
||||
Image($r('app.media.icon'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conditional Rendering
|
||||
|
||||
### if/else
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct ConditionalExample {
|
||||
@State isLoggedIn: boolean = false;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
if (this.isLoggedIn) {
|
||||
Text('Welcome back!')
|
||||
Button('Logout')
|
||||
} else {
|
||||
Text('Please login')
|
||||
Button('Login')
|
||||
.onClick(() => { this.isLoggedIn = true; })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Visibility Control
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct VisibilityExample {
|
||||
@State showDetails: boolean = false;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text('Summary')
|
||||
Text('Detailed information...')
|
||||
.visibility(this.showDetails ? Visibility.Visible : Visibility.None)
|
||||
Button(this.showDetails ? 'Hide' : 'Show')
|
||||
.onClick(() => { this.showDetails = !this.showDetails; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Minimize @State scope** - Keep state as close to where it's used as possible
|
||||
2. **Use @Prop for read-only data** - Prevents accidental modifications
|
||||
3. **Prefer @Link for form inputs** - Enables two-way binding
|
||||
4. **Use LazyForEach for lists > 100 items** - Improves performance
|
||||
5. **Always provide key functions** - Enables efficient list updates
|
||||
6. **Use @Builder for reusable UI blocks** - Reduces duplication
|
||||
7. **Clean up in aboutToDisappear** - Cancel timers, unsubscribe events
|
||||
153
arkts-development/references/hstack.md
Normal file
153
arkts-development/references/hstack.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# 堆栈解析工具 (hstack)
|
||||
|
||||
hstack 是用于将 Release 应用混淆后的 crash 堆栈解析为源码对应堆栈的工具,支持 Windows、Mac、Linux 三个平台。
|
||||
|
||||
## 命令格式
|
||||
|
||||
```bash
|
||||
hstack [options]
|
||||
```
|
||||
|
||||
## 命令参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-i, --input` | 指定 crash 文件归档目录 |
|
||||
| `-c, --crash` | 指定一条 crash 堆栈 |
|
||||
| `-o, --output` | 指定解析结果输出目录(使用 `-c` 时指定输出文件) |
|
||||
| `-s, --sourcemapDir` | 指定 sourcemap 文件归档目录 |
|
||||
| `--so, --soDir` | 指定 shared object (.so) 文件归档目录 |
|
||||
| `-n, --nameObfuscation` | 指定 nameCache 文件归档目录 |
|
||||
| `-v, --version` | 查看版本 |
|
||||
| `-h, --help` | 查询帮助 |
|
||||
|
||||
## 参数约束
|
||||
|
||||
- crash 文件目录 (`-i`) 与 crash 堆栈 (`-c`) **必须且只能提供一项**
|
||||
- sourcemap (`-s`) 与 shared object (`--so`) 目录**至少提供一项**
|
||||
- 如需还原混淆的方法名,需**同时提供** sourcemap 和 nameCache 文件
|
||||
- 路径参数不支持特殊字符:`` `~!@#$^&*=|{};,\s\[\]<>? ``
|
||||
|
||||
## 环境配置
|
||||
|
||||
1. 将 Command Line Tools 的 `bin` 目录配置到 PATH 环境变量
|
||||
2. 配置 Node.js 到环境变量
|
||||
3. 解析 C++ 异常需配置 SDK 的 `native\llvm\bin` 目录到环境变量 `ADDR2LINE_PATH`
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 解析 crash 文件目录
|
||||
|
||||
```bash
|
||||
# 完整解析命令
|
||||
hstack -i crashDir -o outputDir -s sourcemapDir --so soDir -n nameCacheDir
|
||||
|
||||
# 仅使用 sourcemap 解析 (ArkTS)
|
||||
hstack -i crashDir -o outputDir -s sourcemapDir
|
||||
|
||||
# 仅使用 so 文件解析 (C++)
|
||||
hstack -i crashDir -o outputDir --so soDir
|
||||
|
||||
# 包含方法名还原
|
||||
hstack -i crashDir -o outputDir -s sourcemapDir -n nameCacheDir
|
||||
```
|
||||
|
||||
### 解析单条堆栈
|
||||
|
||||
```bash
|
||||
# 输出到控制台
|
||||
hstack -c "at har (entry|har|1.0.0|src/main/ets/pages/Index.ts:58:58)" -s sourcemapDir
|
||||
|
||||
# 输出到文件
|
||||
hstack -c "at har (entry|har|1.0.0|src/main/ets/pages/Index.ts:58:58)" -s sourcemapDir -o result.txt
|
||||
```
|
||||
|
||||
## 输出说明
|
||||
|
||||
- 解析结果输出到 `-o` 指定目录,文件以原始 crash 文件名加 `_` 前缀命名
|
||||
- 不指定 `-o` 时:
|
||||
- 使用 `-i` 输入:输出到 crashDir 目录
|
||||
- 使用 `-c` 输入:直接输出到控制台
|
||||
|
||||
## 文件获取
|
||||
|
||||
### Sourcemap 文件
|
||||
|
||||
构建产物中的 sourcemap 文件,包含:
|
||||
- 路径信息映射
|
||||
- 行列号映射 (mappings 字段)
|
||||
- package-info 信息
|
||||
|
||||
### NameCache 文件
|
||||
|
||||
构建产物中的 nameCache 文件,包含:
|
||||
- `IdentifierCache`: 标识符混淆映射
|
||||
- `MemberMethodCache`: 成员方法混淆映射,格式为 `"源码方法名:起始行:结束行": "混淆后方法名"`
|
||||
|
||||
### Shared Object (.so) 文件
|
||||
|
||||
构建 Release 应用时,默认 so 文件不包含符号表。如需生成包含符号表的 so 文件,在模块 `build-profile.json5` 中配置:
|
||||
|
||||
```json5
|
||||
{
|
||||
"buildOption": {
|
||||
"externalNativeOptions": {
|
||||
"arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 堆栈解析原理
|
||||
|
||||
### Crash 堆栈格式
|
||||
|
||||
```
|
||||
at har (entry|har|1.0.0|src/main/ets/components/mainpage/MainPage.js:58:58)
|
||||
at i (entry|entry|1.0.0|src/main/ets/pages/Index.ts:71:71)
|
||||
```
|
||||
|
||||
路径格式:`引用方packageName|被引用方packageName|version|源码相对路径`
|
||||
|
||||
### 解析步骤
|
||||
|
||||
1. **根据路径信息找到 sourcemap**
|
||||
- 从路径 `entry|har|1.0.0|src/main/ets/...` 在 entry 模块 sourcemap 中查找对应字段
|
||||
|
||||
2. **利用 sourcemap 还原路径和行列号**
|
||||
- 根据 `sources` 和 `mappings` 字段解析
|
||||
- 如包含 `package-info`,可进行二次解析获取更准确的源码位置
|
||||
|
||||
3. **利用 nameCache 还原方法名**
|
||||
- 查找混淆后方法名对应的所有条目
|
||||
- 根据还原后的行号范围匹配正确的源码方法名
|
||||
|
||||
### 解析示例
|
||||
|
||||
原始堆栈:
|
||||
```
|
||||
at i (entry|entry|1.0.0|src/main/ets/pages/Index.ts:71:71)
|
||||
```
|
||||
|
||||
还原后:
|
||||
```
|
||||
at callHarFunction (entry/src/main/ets/pages/Index.ets:25:3)
|
||||
```
|
||||
|
||||
## CI/CD 集成
|
||||
|
||||
```bash
|
||||
# 自动化解析脚本示例
|
||||
hstack \
|
||||
-i ./crash-logs \
|
||||
-o ./parsed-logs \
|
||||
-s ./build/sourcemap \
|
||||
--so ./build/libs \
|
||||
-n ./build/nameCache
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
1. **方法名未还原**: 确保同时提供 `-s` 和 `-n` 参数
|
||||
2. **C++ 堆栈未解析**: 检查 `ADDR2LINE_PATH` 环境变量配置
|
||||
3. **so 文件无符号表**: 配置 `RelWithDebInfo` 构建选项
|
||||
179
arkts-development/references/hvigor-commandline.md
Normal file
179
arkts-development/references/hvigor-commandline.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Hvigor 命令行构建工具 (hvigorw)
|
||||
|
||||
hvigorw 是 Hvigor 的 wrapper 包装工具,支持自动安装 Hvigor 构建工具和相关插件依赖,以及执行 Hvigor 构建命令。
|
||||
|
||||
## 命令格式
|
||||
|
||||
```bash
|
||||
hvigorw [taskNames...] <options>
|
||||
```
|
||||
|
||||
## 编译构建任务
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| `clean` | 清理构建产物 build 目录 |
|
||||
| `assembleHap` | 构建 Hap 应用 |
|
||||
| `assembleApp` | 构建 App 应用 |
|
||||
| `assembleHsp` | 构建 Hsp 包 |
|
||||
| `assembleHar` | 构建 Har 包 |
|
||||
| `collectCoverage` | 基于打点数据生成覆盖率统计报表 |
|
||||
|
||||
## 常用构建参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-p buildMode={debug\|release}` | 指定构建模式。默认:Hap/Hsp/Har 为 debug,App 为 release |
|
||||
| `-p debuggable=true/false` | 覆盖 buildOption 中的 debuggable 配置 |
|
||||
| `-p product={ProductName}` | 指定 product 进行编译,默认为 default |
|
||||
| `-p module={ModuleName}@{TargetName}` | 指定模块及 target 编译(需配合 `--mode module`) |
|
||||
| `-p ohos-test-coverage={true\|false}` | 执行测试框架代码覆盖率插桩编译 |
|
||||
| `-p parameterFile=param.json` | 设置 oh-package.json5 的参数配置文件 |
|
||||
|
||||
## 构建示例
|
||||
|
||||
```bash
|
||||
# 清理构建产物
|
||||
hvigorw clean
|
||||
|
||||
# Debug 模式构建 Hap
|
||||
hvigorw assembleHap -p buildMode=debug
|
||||
|
||||
# Release 模式构建 App
|
||||
hvigorw assembleApp -p buildMode=release
|
||||
|
||||
# 构建指定 product
|
||||
hvigorw assembleHap -p product=free
|
||||
|
||||
# 构建指定模块
|
||||
hvigorw assembleHap -p module=entry@default --mode module
|
||||
|
||||
# 构建多个模块
|
||||
hvigorw assembleHar -p module=library1@default,library2@default --mode module
|
||||
```
|
||||
|
||||
## 测试命令
|
||||
|
||||
### Instrument Test (设备测试)
|
||||
|
||||
```bash
|
||||
hvigorw onDeviceTest -p module={moduleName} -p coverage={true|false} -p scope={suiteName}#{methodName}
|
||||
```
|
||||
|
||||
- `module`: 执行测试的模块,缺省执行所有模块
|
||||
- `coverage`: 是否生成覆盖率报告,默认 true
|
||||
- `scope`: 测试范围,格式 `{suiteName}#{methodName}` 或 `{suiteName}`
|
||||
- `ohos-debug-asan`: 是否启用 ASan 检测,默认 false (5.19.0+)
|
||||
|
||||
**输出路径:**
|
||||
- 覆盖率报告: `<module-path>/.test/default/outputs/ohosTest/reports`
|
||||
- 测试结果: `<project>/<module>/.test/default/intermediates/ohosTest/coverage_data/test_result.txt`
|
||||
|
||||
### Local Test (本地测试)
|
||||
|
||||
```bash
|
||||
hvigorw test -p module={moduleName} -p coverage={true|false} -p scope={suiteName}#{methodName}
|
||||
```
|
||||
|
||||
**输出路径:**
|
||||
- 覆盖率报告: `<module-path>/.test/default/outputs/test/reports`
|
||||
- 测试结果: `<project>/<module>/.test/default/intermediates/test/coverage_data/test_result.txt`
|
||||
|
||||
## 日志级别
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-e, --error` | 设置日志级别为 error |
|
||||
| `-w, --warn` | 设置日志级别为 warn |
|
||||
| `-i, --info` | 设置日志级别为 info |
|
||||
| `-d, --debug` | 设置日志级别为 debug |
|
||||
| `--stacktrace` | 开启打印异常堆栈信息 |
|
||||
|
||||
## 构建分析 (Build Analyzer)
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--analyze=normal` | 普通模式分析 |
|
||||
| `--analyze=advanced` | 进阶模式,更详细的任务耗时数据 |
|
||||
| `--analyze=ultrafine` | 超精细化模式,ArkTS 编译详细打点 (6.0.0+) |
|
||||
| `--analyze=false` | 不启用构建分析 |
|
||||
| `--config properties.hvigor.analyzeHtml=true` | 生成 HTML 可视化报告到 `.hvigor/report` |
|
||||
|
||||
## 守护进程 (Daemon)
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--daemon` | 启用守护进程 |
|
||||
| `--no-daemon` | 关闭守护进程(命令行模式推荐) |
|
||||
| `--stop-daemon` | 关闭当前工程的守护进程 |
|
||||
| `--stop-daemon-all` | 关闭所有工程的守护进程 |
|
||||
| `--status-daemon` | 查询所有 Hvigor 守护进程信息 |
|
||||
| `--max-old-space-size=12345` | 设置老生代内存大小 (MB) |
|
||||
| `--max-semi-space-size=32` | 设置新生代半空间大小 (MB, 5.18.4+) |
|
||||
|
||||
## 性能与内存优化
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--parallel` / `--no-parallel` | 开启/关闭并行构建(默认开启) |
|
||||
| `--incremental` / `--no-incremental` | 开启/关闭增量构建(默认开启) |
|
||||
| `--optimization-strategy=performance` | 性能优先模式,加快构建但占用更多内存 (5.19.2+) |
|
||||
| `--optimization-strategy=memory` | 内存优先模式(默认)(5.19.2+) |
|
||||
|
||||
## 公共命令
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| `tasks` | 打印工程各模块包含的任务信息 |
|
||||
| `taskTree` | 打印工程各模块的任务依赖关系 |
|
||||
| `prune` | 清除 30 天未使用的缓存并删除 pnpm 未引用包 |
|
||||
| `buildInfo` | 打印 build-profile.json5 配置信息 (5.18.4+) |
|
||||
|
||||
### buildInfo 扩展参数
|
||||
|
||||
```bash
|
||||
# 打印工程级配置
|
||||
hvigorw buildInfo
|
||||
|
||||
# 打印指定模块配置
|
||||
hvigorw buildInfo -p module=entry
|
||||
|
||||
# 包含 buildOption 配置
|
||||
hvigorw buildInfo -p buildOption
|
||||
|
||||
# JSON 格式输出
|
||||
hvigorw buildInfo -p json
|
||||
```
|
||||
|
||||
## 其他参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-h, --help` | 打印帮助信息 |
|
||||
| `-v, --version` | 打印版本信息 |
|
||||
| `-s, --sync` | 同步工程信息到 `./hvigor/outputs/sync/output.json` |
|
||||
| `-m, --mode` | 指定执行目录级别 (如 `-m project`) |
|
||||
| `--type-check` | 开启 hvigorfile.ts 类型检查 |
|
||||
| `--watch` | 观察模式,用于预览和热加载 |
|
||||
| `--node-home <string>` | 指定 Node.js 路径 |
|
||||
| `--config, -c` | 指定 hvigor-config.json5 参数 |
|
||||
|
||||
## CI/CD 常用命令组合
|
||||
|
||||
```bash
|
||||
# 完整的 Release 构建流程
|
||||
hvigorw clean && hvigorw assembleApp -p buildMode=release --no-daemon
|
||||
|
||||
# 带构建分析的 Debug 构建
|
||||
hvigorw assembleHap -p buildMode=debug --analyze=advanced --no-daemon
|
||||
|
||||
# 运行测试并生成覆盖率报告
|
||||
hvigorw onDeviceTest -p coverage=true --no-daemon
|
||||
|
||||
# 内存受限环境构建
|
||||
hvigorw assembleHap --optimization-strategy=memory --no-daemon
|
||||
|
||||
# 清理缓存
|
||||
hvigorw prune
|
||||
hvigorw --stop-daemon-all
|
||||
```
|
||||
407
arkts-development/references/migration-guide.md
Normal file
407
arkts-development/references/migration-guide.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# TypeScript to ArkTS Migration Guide
|
||||
|
||||
Complete guide for migrating TypeScript code to ArkTS, covering all language constraints and adaptation rules.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Constraint Categories](#constraint-categories)
|
||||
3. [Prohibited Features](#prohibited-features)
|
||||
4. [Migration Examples](#migration-examples)
|
||||
5. [Migration Checklist](#migration-checklist)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
ArkTS is based on TypeScript but enforces stricter rules for:
|
||||
- **Performance**: Static analysis enables AOT compilation
|
||||
- **Type Safety**: Eliminates runtime type errors
|
||||
- **Predictability**: Fixed object structures at compile time
|
||||
|
||||
Constraints are categorized as:
|
||||
- **Error**: Must fix, blocks compilation
|
||||
- **Warning**: Should fix, may become errors in future
|
||||
|
||||
---
|
||||
|
||||
## Constraint Categories
|
||||
|
||||
### 1. Type System Constraints
|
||||
|
||||
#### Prohibited: `any` and `unknown`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
let value: any = getData();
|
||||
let result: unknown = parse(input);
|
||||
|
||||
// ✅ ArkTS
|
||||
interface Data { id: number; name: string; }
|
||||
let value: Data = getData();
|
||||
let result: Data | null = parse(input);
|
||||
```
|
||||
|
||||
#### Prohibited: Type assertions to `any`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
(obj as any).dynamicProp = value;
|
||||
|
||||
// ✅ ArkTS - Define complete interface
|
||||
interface MyObject {
|
||||
existingProp: string;
|
||||
dynamicProp?: number;
|
||||
}
|
||||
let obj: MyObject = { existingProp: 'test' };
|
||||
obj.dynamicProp = value;
|
||||
```
|
||||
|
||||
### 2. Variable Declaration
|
||||
|
||||
#### Prohibited: `var`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
var count = 0;
|
||||
var name = "hello";
|
||||
|
||||
// ✅ ArkTS
|
||||
let count = 0;
|
||||
const name = "hello";
|
||||
```
|
||||
|
||||
### 3. Object Structure Constraints
|
||||
|
||||
#### Prohibited: Runtime property modification
|
||||
|
||||
```typescript
|
||||
class Point {
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
}
|
||||
|
||||
let p = new Point();
|
||||
|
||||
// ❌ All prohibited
|
||||
p['z'] = 99; // Dynamic property
|
||||
delete p.x; // Property deletion
|
||||
Object.assign(p, {z: 1}); // Runtime extension
|
||||
|
||||
// ✅ Define all properties upfront
|
||||
class Point3D {
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
z: number = 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### Prohibited: Structural typing (duck typing)
|
||||
|
||||
```typescript
|
||||
interface Named { name: string; }
|
||||
|
||||
// ❌ TypeScript allows structural matching
|
||||
let obj = { name: "Alice", age: 25 };
|
||||
let named: Named = obj; // Works in TS, fails in ArkTS
|
||||
|
||||
// ✅ ArkTS requires explicit implementation
|
||||
class Person implements Named {
|
||||
name: string = "";
|
||||
age: number = 0;
|
||||
}
|
||||
let named: Named = new Person();
|
||||
```
|
||||
|
||||
### 4. Private Fields
|
||||
|
||||
#### Prohibited: `#` private fields
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
class MyClass {
|
||||
#secret: string = "";
|
||||
#getValue(): string { return this.#secret; }
|
||||
}
|
||||
|
||||
// ✅ ArkTS
|
||||
class MyClass {
|
||||
private secret: string = "";
|
||||
private getValue(): string { return this.secret; }
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Symbol Properties
|
||||
|
||||
#### Prohibited: Symbol as property key
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
const sym = Symbol('key');
|
||||
let obj = { [sym]: 'value' };
|
||||
|
||||
// ✅ ArkTS
|
||||
let obj = { key: 'value' };
|
||||
```
|
||||
|
||||
### 6. Prohibited Statements
|
||||
|
||||
#### `for...in`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
for (let key in obj) {
|
||||
console.log(obj[key]);
|
||||
}
|
||||
|
||||
// ✅ ArkTS - Use Object.keys with forEach
|
||||
Object.keys(obj).forEach((key: string) => {
|
||||
// Access via typed interface
|
||||
});
|
||||
|
||||
// ✅ ArkTS - Use for...of for arrays
|
||||
let arr: string[] = ['a', 'b', 'c'];
|
||||
for (let item of arr) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
#### `delete`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
delete obj.property;
|
||||
|
||||
// ✅ ArkTS - Use optional properties
|
||||
interface Config {
|
||||
name: string;
|
||||
value?: number; // Optional, can be undefined
|
||||
}
|
||||
let config: Config = { name: 'test', value: undefined };
|
||||
```
|
||||
|
||||
#### `with`
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
with (obj) {
|
||||
console.log(property);
|
||||
}
|
||||
|
||||
// ✅ ArkTS - Use explicit references
|
||||
console.log(obj.property);
|
||||
```
|
||||
|
||||
#### `in` operator for type checking
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
if ('name' in person) {
|
||||
console.log(person.name);
|
||||
}
|
||||
|
||||
// ✅ ArkTS - Use instanceof
|
||||
if (person instanceof Person) {
|
||||
console.log(person.name);
|
||||
}
|
||||
|
||||
// ✅ ArkTS - Use discriminated unions
|
||||
interface Person { type: 'person'; name: string; }
|
||||
interface Animal { type: 'animal'; species: string; }
|
||||
type Entity = Person | Animal;
|
||||
|
||||
function getName(e: Entity): string {
|
||||
if (e.type === 'person') {
|
||||
return e.name;
|
||||
}
|
||||
return e.species;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Interface Constraints
|
||||
|
||||
#### Prohibited: Call signatures and construct signatures
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
interface Callable {
|
||||
(x: number): number;
|
||||
new (s: string): Object;
|
||||
}
|
||||
|
||||
// ✅ ArkTS - Use classes
|
||||
class Calculator {
|
||||
calculate(x: number): number {
|
||||
return x * 2;
|
||||
}
|
||||
}
|
||||
|
||||
class Factory {
|
||||
create(s: string): Object {
|
||||
return { value: s };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Other Restrictions
|
||||
|
||||
| Feature | Status | Alternative |
|
||||
|---------|--------|-------------|
|
||||
| Comma expressions | Prohibited (except in `for`) | Separate statements |
|
||||
| Computed property names | Limited | String literal keys |
|
||||
| Spread on non-arrays | Limited | Explicit copying |
|
||||
| `eval()` | Prohibited | Avoid |
|
||||
| `Function()` constructor | Prohibited | Arrow functions |
|
||||
| Prototype modification | Prohibited | Class inheritance |
|
||||
|
||||
---
|
||||
|
||||
## Migration Examples
|
||||
|
||||
### Example 1: Dynamic Configuration Object
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
let config: any = {};
|
||||
config.apiUrl = 'https://api.example.com';
|
||||
config.timeout = 5000;
|
||||
config.retry = true;
|
||||
|
||||
// ✅ ArkTS
|
||||
interface AppConfig {
|
||||
apiUrl: string;
|
||||
timeout: number;
|
||||
retry: boolean;
|
||||
}
|
||||
|
||||
let config: AppConfig = {
|
||||
apiUrl: 'https://api.example.com',
|
||||
timeout: 5000,
|
||||
retry: true
|
||||
};
|
||||
```
|
||||
|
||||
### Example 2: Object Iteration
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
interface User { name: string; age: number; }
|
||||
let user: User = { name: 'John', age: 30 };
|
||||
|
||||
for (let key in user) {
|
||||
console.log(`${key}: ${user[key]}`);
|
||||
}
|
||||
|
||||
// ✅ ArkTS
|
||||
interface User {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
let user: User = { name: 'John', age: 30 };
|
||||
console.log(`name: ${user.name}`);
|
||||
console.log(`age: ${user.age}`);
|
||||
|
||||
// Or use explicit property list
|
||||
const props: (keyof User)[] = ['name', 'age'];
|
||||
for (let prop of props) {
|
||||
// Handle each known property
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Optional Property Handling
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
let obj: any = { a: 1 };
|
||||
if (obj.b) {
|
||||
delete obj.b;
|
||||
}
|
||||
obj.c = 3;
|
||||
|
||||
// ✅ ArkTS
|
||||
interface MyObj {
|
||||
a: number;
|
||||
b?: number;
|
||||
c?: number;
|
||||
}
|
||||
|
||||
let obj: MyObj = { a: 1 };
|
||||
if (obj.b !== undefined) {
|
||||
obj.b = undefined; // Set to undefined instead of delete
|
||||
}
|
||||
obj.c = 3;
|
||||
```
|
||||
|
||||
### Example 4: Type Guards
|
||||
|
||||
```typescript
|
||||
// ❌ TypeScript
|
||||
function process(input: unknown) {
|
||||
if (typeof input === 'string') {
|
||||
return input.toUpperCase();
|
||||
}
|
||||
if ('length' in input) {
|
||||
return (input as any[]).length;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ ArkTS
|
||||
function processString(input: string): string {
|
||||
return input.toUpperCase();
|
||||
}
|
||||
|
||||
function processArray(input: string[]): number {
|
||||
return input.length;
|
||||
}
|
||||
|
||||
// Use union types with type narrowing
|
||||
type Input = string | string[];
|
||||
|
||||
function process(input: Input): string | number {
|
||||
if (typeof input === 'string') {
|
||||
return input.toUpperCase();
|
||||
}
|
||||
return input.length;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### Phase 1: Enable Strict Mode
|
||||
- [ ] Enable `strict: true` in tsconfig.json
|
||||
- [ ] Enable `noImplicitAny: true`
|
||||
- [ ] Enable `strictNullChecks: true`
|
||||
- [ ] Fix all resulting errors
|
||||
|
||||
### Phase 2: Remove Prohibited Keywords
|
||||
- [ ] Replace all `var` with `let`/`const`
|
||||
- [ ] Remove all `any` type annotations
|
||||
- [ ] Remove all `unknown` type annotations
|
||||
- [ ] Replace `#` private fields with `private`
|
||||
|
||||
### Phase 3: Fix Object Patterns
|
||||
- [ ] Replace dynamic property access with typed interfaces
|
||||
- [ ] Remove `delete` statements
|
||||
- [ ] Remove `for...in` loops
|
||||
- [ ] Remove `with` statements
|
||||
- [ ] Replace `in` operator type checks
|
||||
|
||||
### Phase 4: Update Interfaces
|
||||
- [ ] Remove call signatures from interfaces
|
||||
- [ ] Remove construct signatures from interfaces
|
||||
- [ ] Replace structural typing with explicit implements
|
||||
|
||||
### Phase 5: Validate
|
||||
- [ ] Build with ArkTS compiler
|
||||
- [ ] Fix remaining errors
|
||||
- [ ] Test all functionality
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Official Migration Guide](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/typescript-to-arkts-migration-guide)
|
||||
- [ArkTS Language Reference](https://developer.huawei.com/consumer/cn/arkts/)
|
||||
Reference in New Issue
Block a user