diff --git a/arkts-development/SKILL.md b/skills/arkts-development/SKILL.md similarity index 100% rename from arkts-development/SKILL.md rename to skills/arkts-development/SKILL.md diff --git a/arkts-development/assets/component-template.ets b/skills/arkts-development/assets/component-template.ets similarity index 95% rename from arkts-development/assets/component-template.ets rename to skills/arkts-development/assets/component-template.ets index 9f9ff43..48140b7 100644 --- a/arkts-development/assets/component-template.ets +++ b/skills/arkts-development/assets/component-template.ets @@ -1,57 +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 { - 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) - } -} +// 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 { + 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) + } +} diff --git a/arkts-development/assets/list-page-template.ets b/skills/arkts-development/assets/list-page-template.ets similarity index 95% rename from arkts-development/assets/list-page-template.ets rename to skills/arkts-development/assets/list-page-template.ets index a647630..d190c0c 100644 --- a/arkts-development/assets/list-page-template.ets +++ b/skills/arkts-development/assets/list-page-template.ets @@ -1,129 +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 { - 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 { - 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') - } -} +// 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 { + 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 { + 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') + } +} diff --git a/arkts-development/references/api-reference.md b/skills/arkts-development/references/api-reference.md similarity index 95% rename from arkts-development/references/api-reference.md rename to skills/arkts-development/references/api-reference.md index dd7747a..891aeaf 100644 --- a/arkts-development/references/api-reference.md +++ b/skills/arkts-development/references/api-reference.md @@ -1,503 +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; - 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 { - 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 { - 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 { - 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 { - 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'; -``` +# 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; + 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 { + 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 { + 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 { + 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 { + 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'; +``` diff --git a/skills/arkts-development/references/arkguard-obfuscation.md b/skills/arkts-development/references/arkguard-obfuscation.md new file mode 100644 index 0000000..82cc737 --- /dev/null +++ b/skills/arkts-development/references/arkguard-obfuscation.md @@ -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 ` | 保留指定属性名不被混淆 | +| `-keep-global-name ` | 保留指定全局名称不被混淆 | +| `-keep-file-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) diff --git a/arkts-development/references/codelinter.md b/skills/arkts-development/references/codelinter.md similarity index 100% rename from arkts-development/references/codelinter.md rename to skills/arkts-development/references/codelinter.md diff --git a/arkts-development/references/component-patterns.md b/skills/arkts-development/references/component-patterns.md similarity index 94% rename from arkts-development/references/component-patterns.md rename to skills/arkts-development/references/component-patterns.md index 864e6c8..39db42f 100644 --- a/arkts-development/references/component-patterns.md +++ b/skills/arkts-development/references/component-patterns.md @@ -1,521 +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 +# 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 diff --git a/arkts-development/references/hstack.md b/skills/arkts-development/references/hstack.md similarity index 100% rename from arkts-development/references/hstack.md rename to skills/arkts-development/references/hstack.md diff --git a/arkts-development/references/hvigor-commandline.md b/skills/arkts-development/references/hvigor-commandline.md similarity index 100% rename from arkts-development/references/hvigor-commandline.md rename to skills/arkts-development/references/hvigor-commandline.md diff --git a/arkts-development/references/migration-guide.md b/skills/arkts-development/references/migration-guide.md similarity index 95% rename from arkts-development/references/migration-guide.md rename to skills/arkts-development/references/migration-guide.md index 41351e6..2ffced1 100644 --- a/arkts-development/references/migration-guide.md +++ b/skills/arkts-development/references/migration-guide.md @@ -1,407 +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/) +# 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/) diff --git a/harmonyos-build-deploy/SKILL.md b/skills/harmonyos-build-deploy/SKILL.md similarity index 100% rename from harmonyos-build-deploy/SKILL.md rename to skills/harmonyos-build-deploy/SKILL.md diff --git a/harmonyos-build-deploy/references/device-installation.md b/skills/harmonyos-build-deploy/references/device-installation.md similarity index 100% rename from harmonyos-build-deploy/references/device-installation.md rename to skills/harmonyos-build-deploy/references/device-installation.md