feat: arkts-development skills
This commit is contained in:
154
arkts-development/SKILL.md
Normal file
154
arkts-development/SKILL.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
name: arkts-development
|
||||||
|
description: HarmonyOS ArkTS application development with ArkUI declarative UI framework. Use when building HarmonyOS/OpenHarmony apps, creating ArkUI components, implementing state management with decorators (@State, @Prop, @Link), migrating from TypeScript to ArkTS, or working with HarmonyOS-specific APIs (router, http, preferences). Covers component lifecycle, layout patterns, and ArkTS language constraints.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ArkTS Development
|
||||||
|
|
||||||
|
Build HarmonyOS applications using ArkTS and the ArkUI declarative UI framework.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Create a basic component:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct HelloWorld {
|
||||||
|
@State message: string = 'Hello, ArkTS!';
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Text(this.message)
|
||||||
|
.fontSize(30)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
Button('Click Me')
|
||||||
|
.onClick(() => { this.message = 'Button Clicked!'; })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management Decorators
|
||||||
|
|
||||||
|
| Decorator | Usage | Description |
|
||||||
|
|-----------|-------|-------------|
|
||||||
|
| `@State` | `@State count: number = 0` | Component internal state |
|
||||||
|
| `@Prop` | `@Prop title: string` | Parent → Child (one-way) |
|
||||||
|
| `@Link` | `@Link value: number` | Parent ↔ Child (two-way, use `$varName`) |
|
||||||
|
| `@Provide/@Consume` | Cross-level | Ancestor → Descendant |
|
||||||
|
| `@Observed/@ObjectLink` | Nested objects | Deep object observation |
|
||||||
|
|
||||||
|
## Common Layouts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Vertical
|
||||||
|
Column({ space: 10 }) { Text('A'); Text('B'); }
|
||||||
|
.alignItems(HorizontalAlign.Center)
|
||||||
|
|
||||||
|
// Horizontal
|
||||||
|
Row({ space: 10 }) { Text('A'); Text('B'); }
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
|
||||||
|
// Stack (overlay)
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
Image($r('app.media.bg'))
|
||||||
|
Text('Overlay')
|
||||||
|
}
|
||||||
|
|
||||||
|
// List with ForEach
|
||||||
|
List({ space: 10 }) {
|
||||||
|
ForEach(this.items, (item: string) => {
|
||||||
|
ListItem() { Text(item) }
|
||||||
|
}, (item: string) => item)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Lifecycle
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct Page {
|
||||||
|
aboutToAppear() { /* Init data */ }
|
||||||
|
onPageShow() { /* Page visible */ }
|
||||||
|
onPageHide() { /* Page hidden */ }
|
||||||
|
aboutToDisappear() { /* Cleanup */ }
|
||||||
|
build() { Column() { Text('Page') } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { router } from '@kit.ArkUI';
|
||||||
|
|
||||||
|
// Push
|
||||||
|
router.pushUrl({ url: 'pages/Detail', params: { id: 123 } });
|
||||||
|
|
||||||
|
// Replace
|
||||||
|
router.replaceUrl({ url: 'pages/New' });
|
||||||
|
|
||||||
|
// Back
|
||||||
|
router.back();
|
||||||
|
|
||||||
|
// Get params
|
||||||
|
const params = router.getParams() as Record<string, Object>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Request
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { http } from '@kit.NetworkKit';
|
||||||
|
|
||||||
|
const req = http.createHttp();
|
||||||
|
const res = await req.request('https://api.example.com/data', {
|
||||||
|
method: http.RequestMethod.GET,
|
||||||
|
header: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
if (res.responseCode === 200) {
|
||||||
|
const data = JSON.parse(res.result as string);
|
||||||
|
}
|
||||||
|
req.destroy();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Storage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { preferences } from '@kit.ArkData';
|
||||||
|
|
||||||
|
const prefs = await preferences.getPreferences(this.context, 'store');
|
||||||
|
await prefs.put('key', 'value');
|
||||||
|
await prefs.flush();
|
||||||
|
const val = await prefs.get('key', 'default');
|
||||||
|
```
|
||||||
|
|
||||||
|
## ArkTS Language Constraints
|
||||||
|
|
||||||
|
ArkTS enforces stricter rules than TypeScript for performance and safety:
|
||||||
|
|
||||||
|
| Prohibited | Use Instead |
|
||||||
|
|------------|-------------|
|
||||||
|
| `any`, `unknown` | Explicit types, interfaces |
|
||||||
|
| `var` | `let`, `const` |
|
||||||
|
| Dynamic property access `obj['key']` | Fixed object structure |
|
||||||
|
| `for...in`, `delete`, `with` | `for...of`, array methods |
|
||||||
|
| `#privateField` | `private` keyword |
|
||||||
|
| Structural typing | Explicit `implements`/`extends` |
|
||||||
|
|
||||||
|
See [references/migration-guide.md](references/migration-guide.md) for complete TypeScript → ArkTS migration details.
|
||||||
|
|
||||||
|
## Reference Files
|
||||||
|
|
||||||
|
- **Migration Guide**: [references/migration-guide.md](references/migration-guide.md) - Complete TypeScript to ArkTS migration rules and examples
|
||||||
|
- **Component Patterns**: [references/component-patterns.md](references/component-patterns.md) - Advanced component patterns and best practices
|
||||||
|
- **API Reference**: [references/api-reference.md](references/api-reference.md) - Common HarmonyOS APIs
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
- **IDE**: DevEco Studio
|
||||||
|
- **SDK**: HarmonyOS SDK
|
||||||
|
- **Simulator**: Built-in DevEco Studio emulator
|
||||||
57
arkts-development/assets/component-template.ets
Normal file
57
arkts-development/assets/component-template.ets
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// ArkTS Basic Component Template
|
||||||
|
// Replace {{ComponentName}} with your component name
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct {{ComponentName}} {
|
||||||
|
// State management
|
||||||
|
@State isLoading: boolean = false;
|
||||||
|
@State errorMessage: string = '';
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutToDisappear(): void {
|
||||||
|
// Cleanup resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
async loadData(): Promise<void> {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
// Load data here
|
||||||
|
} catch (error) {
|
||||||
|
this.errorMessage = 'Failed to load data';
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
if (this.isLoading) {
|
||||||
|
LoadingProgress()
|
||||||
|
.width(50)
|
||||||
|
.height(50)
|
||||||
|
} else if (this.errorMessage) {
|
||||||
|
Text(this.errorMessage)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontColor(Color.Red)
|
||||||
|
Button('Retry')
|
||||||
|
.onClick(() => { this.loadData(); })
|
||||||
|
} else {
|
||||||
|
// Main content here
|
||||||
|
Text('{{ComponentName}}')
|
||||||
|
.fontSize(24)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.alignItems(HorizontalAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
129
arkts-development/assets/list-page-template.ets
Normal file
129
arkts-development/assets/list-page-template.ets
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// ArkTS List Page Template
|
||||||
|
// Replace {{PageName}}, {{ItemType}}, {{itemName}} with your values
|
||||||
|
|
||||||
|
import { router } from '@kit.ArkUI';
|
||||||
|
|
||||||
|
interface {{ItemType}} {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct {{PageName}} {
|
||||||
|
@State items: {{ItemType}}[] = [];
|
||||||
|
@State isLoading: boolean = false;
|
||||||
|
@State isRefreshing: boolean = false;
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.loadItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadItems(): Promise<void> {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
// TODO: Fetch items from API
|
||||||
|
this.items = [
|
||||||
|
{ id: '1', title: 'Item 1', description: 'Description 1' },
|
||||||
|
{ id: '2', title: 'Item 2', description: 'Description 2' },
|
||||||
|
];
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshItems(): Promise<void> {
|
||||||
|
this.isRefreshing = true;
|
||||||
|
await this.loadItems();
|
||||||
|
this.isRefreshing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToDetail(item: {{ItemType}}): void {
|
||||||
|
router.pushUrl({
|
||||||
|
url: 'pages/{{PageName}}Detail',
|
||||||
|
params: { id: item.id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
ItemCard(item: {{ItemType}}) {
|
||||||
|
Column() {
|
||||||
|
Text(item.title)
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor('#333333')
|
||||||
|
|
||||||
|
Text(item.description)
|
||||||
|
.fontSize(14)
|
||||||
|
.fontColor('#666666')
|
||||||
|
.margin({ top: 4 })
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.padding(16)
|
||||||
|
.width('100%')
|
||||||
|
.backgroundColor(Color.White)
|
||||||
|
.borderRadius(8)
|
||||||
|
.onClick(() => { this.navigateToDetail(item); })
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// Header
|
||||||
|
Row() {
|
||||||
|
Text('{{PageName}}')
|
||||||
|
.fontSize(24)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Button('Add')
|
||||||
|
.onClick(() => {
|
||||||
|
router.pushUrl({ url: 'pages/{{PageName}}Create' });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding(16)
|
||||||
|
|
||||||
|
// Content
|
||||||
|
if (this.isLoading && this.items.length === 0) {
|
||||||
|
Column() {
|
||||||
|
LoadingProgress().width(50).height(50)
|
||||||
|
Text('Loading...').margin({ top: 16 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
} else if (this.items.length === 0) {
|
||||||
|
Column() {
|
||||||
|
Text('No items found')
|
||||||
|
.fontSize(16)
|
||||||
|
.fontColor('#999999')
|
||||||
|
Button('Refresh')
|
||||||
|
.margin({ top: 16 })
|
||||||
|
.onClick(() => { this.loadItems(); })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
} else {
|
||||||
|
Refresh({ refreshing: $$this.isRefreshing }) {
|
||||||
|
List({ space: 12 }) {
|
||||||
|
ForEach(this.items, (item: {{ItemType}}) => {
|
||||||
|
ListItem() {
|
||||||
|
this.ItemCard(item)
|
||||||
|
}
|
||||||
|
}, (item: {{ItemType}}) => item.id)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 16, right: 16 })
|
||||||
|
}
|
||||||
|
.onRefreshing(() => { this.refreshItems(); })
|
||||||
|
.layoutWeight(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor('#f5f5f5')
|
||||||
|
}
|
||||||
|
}
|
||||||
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';
|
||||||
|
```
|
||||||
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
|
||||||
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