mcpField
H
harmonyos-app

harmonyos-app

github.com/majiayu000開発288491 回インストール今日更新
リポジトリ

harmonyos-app について

harmonyos-appは、ArkTSとArkUIを使うHarmonyOS開発者向けの規約と実装ガイドです。厳密な型付け、Stageモデル、宣言型UI、複数デバイスへの適応を重視します。

主な機能

  1. 01ArkTSの厳密な型付け規則
  2. 02ArkUIの宣言型インターフェース
  3. 03Stageモデルの指針
  4. 04状態管理パターン
  5. 05再利用可能なコンポーネント設計
  6. 06複数デバイスへの適応方針

活用シーン

  1. 01HarmonyOSアプリの構成設計
  2. 02ArkUIのページとコンポーネントの実装
  3. 03状態デコレーターの選択
  4. 04動的型と状態の直接変更の回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/majiayu000/spellbook --skill harmonyos-app

インストール後、Claude Code で/harmonyos-appを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

reference/
arkts.md
arkui.md
distributed.md
extended.md
stage-model.md
templates/
project-template.md

詳細

HarmonyOS アプリケーション開発

基本原則

  • ArkTS を優先 — 厳格な型安全性を備えた ArkTS を使用し、any や動的型は使用しません
  • 宣言型 UI — ArkUI の宣言型コンポーネントと状態管理で UI を構築します
  • Stage モデル — レガシーな FA モデルではなく、最新の Stage モデル(UIAbility)を使用します
  • 分散を前提とした設計 — 初期段階からデバイス間の機能を活用します
  • アトミックサービス — 軽量なエクスペリエンスには、アトミックサービスやカードの利用を検討します
  • ワンタイム開発 — マルチデバイスへの適応(スマートフォン、タブレット、ウォッチ、テレビ)を前提に設計します

厳守事項(必須)

これらのルールは必須です。違反すると、このスキルは正しく機能していません。

動的型は禁止

ArkTS では動的型付けが禁止されています。any、型アサーション、動的なプロパティアクセスは絶対に使用しないでください。

// ❌ FORBIDDEN: Dynamic types
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value;  // Dynamic property access
(someVar as SomeType).method();  // Type assertion

// ✅ REQUIRED: Strict typing
interface UserData {
  id: string;
  name: string;
}
let data: UserData = fetchData();

// Use Record for dynamic keys
let obj: Record<string, string> = {};
obj['key'] = value;  // OK with Record type
状態の直接変更は禁止

ネストされたオブジェクト内の @State/@Prop 変数を直接変更しないでください。不変更新を使用します。

// ❌ FORBIDDEN: Direct mutation
@State user: User = { name: 'John', age: 25 };

updateAge() {
  this.user.age = 26;  // UI won't update!
}

// ✅ REQUIRED: Immutable update
updateAge() {
  this.user = { ...this.user, age: 26 };  // Creates new object, triggers UI update
}

// For arrays
@State items: string[] = ['a', 'b'];

// ❌ FORBIDDEN
this.items.push('c');  // UI won't update

// ✅ REQUIRED
this.items = [...this.items, 'c'];
Stage モデルのみ使用

必ず Stage モデル(UIAbility)を使用してください。非推奨の FA モデル(PageAbility)は絶対に使用しないでください。

// ❌ FORBIDDEN: FA Model (deprecated)
// config.json with "pages" array
export default {
  onCreate() { ... }  // PageAbility lifecycle
}

// ✅ REQUIRED: Stage Model
// module.json5 with abilities configuration
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Modern Stage model lifecycle
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }
}
コンポーネントの再利用性

再利用可能な UI は @Component に切り出してください。build() メソッド内に複雑な UI を直接記述しないでください。

// ❌ FORBIDDEN: Monolithic build method
@Entry
@Component
struct MainPage {
  build() {
    Column() {
      // 200+ lines of inline UI...
      Row() {
        Image($r('app.media.avatar'))
        Column() {
          Text(this.user.name)
          Text(this.user.email)
        }
      }
      // More inline UI...
    }
  }
}

// ✅ REQUIRED: Extract components
@Component
struct UserCard {
  @Prop user: User;

  build() {
    Row() {
      Image($r('app.media.avatar'))
      Column() {
        Text(this.user.name)
        Text(this.user.email)
      }
    }
  }
}

@Entry
@Component
struct MainPage {
  @State user: User = { name: 'John', email: '[email protected]' };

  build() {
    Column() {
      UserCard({ user: this.user })
    }
  }
}

クイックリファレンス

用途別の選択
シナリオパターン例
コンポーネント内の状態@Stateカウンター、フォーム入力
親から子へのデータ@Prop読み取り専用の子データ
双方向バインディング@Link共有された変更可能な状態
コンポーネント間の状態@Provide/@Consumeテーマ、ユーザーコンテキスト
永続化された状態PersistentStorageユーザー設定
アプリ全体の状態AppStorageグローバル状態
複雑な状態ロジック@Observed/@ObjectLinkネストされたオブジェクトの更新
状態デコレーターの選択
@State        → Component owns the state, triggers re-render on change
@Prop         → Parent passes value, child gets copy (one-way)
@Link         → Parent passes reference, child can modify (two-way)
@Provide      → Ancestor provides value to all descendants
@Consume      → Descendant consumes value from ancestor
@StorageLink  → Syncs with AppStorage, two-way binding
@StorageProp  → Syncs with AppStorage, one-way binding
@Observed     → Class decorator for observable objects
@ObjectLink   → Links to @Observed object in parent

プロジェクト構成

推奨アーキテクチャ
MyApp/
├── entry/                          # Main entry module
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/       # UIAbility definitions
│   │   │   │   └── EntryAbility.ets
│   │   │   ├── pages/              # Page components
│   │   │   │   ├── Index.ets
│   │   │   │   └── Detail.ets
│   │   │   ├── components/         # Reusable UI components
│   │   │   │   ├── common/         # Common components
│   │   │   │   └── business/       # Business-specific components
│   │   │   ├── viewmodel/          # ViewModels (MVVM)
│   │   │   ├── model/              # Data models
│   │   │   ├── service/            # Business logic services
│   │   │   ├── repository/         # Data access layer
│   │   │   ├── utils/              # Utility functions
│   │   │   └── constants/          # Constants and configs
│   │   ├── resources/              # Resources (strings, images)
│   │   └── module.json5            # Module configuration
│   └── build-profile.json5
├── common/                         # Shared library module
│   └── src/main/ets/
├── features/                       # Feature modules
│   ├── feature_home/
│   └── feature_profile/
└── build-profile.json5             # Project configuration
レイヤーの分離
┌─────────────────────────────────────┐
│           UI Layer (Pages)          │  ArkUI Components
├─────────────────────────────────────┤
│         ViewModel Layer             │  State management, UI logic
├─────────────────────────────────────┤
│         Service Layer               │  Business logic
├─────────────────────────────────────┤
│        Repository Layer             │  Data access abstraction
├─────────────────────────────────────┤
│    Data Sources (Local/Remote)      │  Preferences, RDB, Network
└─────────────────────────────────────┘

ArkUI コンポーネントのパターン

基本的なコンポーネント構造
import { router } from '@kit.ArkUI';

@Component
export struct ProductCard {
  // Props from parent
  @Prop product: Product;
  @Prop onAddToCart: (product: Product) => void;

  // Local state
  @State isExpanded: boolean = false;

  // Computed values (use getters)
  get formattedPrice(): string {
    return `¥${this.product.price.toFixed(2)}`;
  }

  // Lifecycle
  aboutToAppear(): void {
    console.info('ProductCard appearing');
  }

  aboutToDisappear(): void {
    console.info('ProductCard disappearing');
  }

  // Event handlers
  private handleTap(): void {
    router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });
  }

  private handleAddToCart(): void {
    this.onAddToCart(this.product);
  }

  // UI builder
  build() {
    Column() {
      Image(this.product.imageUrl)
        .width('100%')
        .aspectRatio(1)
        .objectFit(ImageFit.Cover)

      Text(this.product.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      Text(this.formattedPrice)
        .fontSize(14)
        .fontColor('#FF6B00')

      Button('Add to Cart')
        .onClick(() => this.handleAddToCart())
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .onClick(() => this.handleTap())
  }
}
LazyForEach を使ったリスト
import { BasicDataSource } from '../utils/BasicDataSource';

class ProductDataSource extends BasicDataSource<Product> {
  private products: Product[] = [];

  totalCount(): number {
    return this.products.length;
  }

  getData(index: number): Product {
    return this.products[index];
  }

  addData(product: Product): void {
    this.products.push(product);
    this.notifyDataAdd(this.products.length - 1);
  }

  updateData(index: number, product: Product): void {
    this.products[index] = product;
    this.notifyDataChange(index);
  }
}

@Component
struct ProductList {
  private dataSource: ProductDataSource = new ProductDataSource();

  build() {
    List() {
      LazyForEach(this.dataSource, (product: Product, index: number) => {
        ListItem() {
          ProductCard({ product: product })
        }
      }, (product: Product) => product.id)  // Key generator
    }
    .lanes(2)  // Grid with 2 columns
    .cachedCount(4)  // Cache 4 items for smooth scrolling
  }
}
カスタムダイアログ
@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = 'Confirm';
  message: string = '';
  onConfirm: () => void = () => {};

  build() {
    Column() {
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      Text(this.message)
        .fontSize(16)
        .margin({ bottom: 24 })

      Row() {
        Button('Cancel')
          .onClick(() => this.controller.close())
          .backgroundColor(Color.Gray)
          .margin({ right: 16 })

        Button('Confirm')
          .onClick(() => {
            this.onConfirm();
            this.controller.close();
          })
      }
    }
    .padding(24)
  }
}

// Usage
@Entry
@Component
struct MainPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      title: 'Delete Item',
      message: 'Are you sure you want to delete this item?',
      onConfirm: () => this.deleteItem()
    }),
    autoCancel: true
  });

  private deleteItem(): void {
    // Delete logic
  }

  build() {
    Button('Delete')
      .onClick(() => this.dialogController.open())
  }
}

拡張リファレンス

このスキルを簡潔に保つため、## State Management Patterns 以降の詳細資料は reference/extended.md に移動しました。移動されたサンプル、コマンドカタログ、チェックリスト、プラットフォームの詳細、実装テンプレートが必要なタスクでは、このリファレンスを読み込んでください。

コメント