状態共有 - 親子間の一方向共有
**@Prop** デコレータで修飾された変数は、親コンポーネントとの間で一方向の同期関係を構築できます。**@Prop** で修飾された変数は変更可能ですが、その変更は親コンポーネントには同期されません。
@Entry
@Component
struct ParentComponent {
@State
funds: number = 0
build() {
Column({ space: 20 }){
Text('親コンポーネント:' + this.funds)
.onClick(() => {
this.funds ++
})
ChildComponent({ funds: this.funds })
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
@Component
struct ChildComponent {
@Prop
funds: number
build() {
Text('子コンポーネント:' + this.funds)
.onClick(() => {
this.funds ++
})
}
}
- string、number、boolean、enum 型をサポート
- 子コンポーネントは Prop データ値を変更できますが、親コンポーネントには同期されません。親コンポーネントが更新されると、子コンポーネントの Prop データは上書きされます
- 子コンポーネントでデフォルト値を初期化できます。注意:現在、コンパイラはエラーを表示しますが、無視してください。次のバージョンで修正されます
状態共有 - 親子間の双方向共有
子コンポーネントで @Link デコレータが修飾された変数は、親コンポーネント内の対応するデータソースとの間で双方向データバインディングを確立します。
1)単純型 string、number、boolean、enum
@Entry
@Component
struct ParentComponent {
@State
funds: number = 0
build() {
Column({ space: 20 }){
Text('親コンポーネント:' + this.funds)
.onClick(() => {
this.funds ++
})
ChildComponent({ funds: $funds })
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
@Component
struct ChildComponent {
@Link
funds: number
build() {
Text('子コンポーネント:' + this.funds)
.onClick(() => {
this.funds ++
})
}
}
2)複合型 Object、class
class UserProfile {
name: string
age: number
}
@Entry
@Component
struct ParentComponent {
@State
user: UserProfile = { name: '田中', age: 25 }
build() {
Column({ space: 20 }){
Text('親コンポーネント:' + `${this.user.name} は ${ this.user.age } 歳です`)
.onClick(() => {
this.user.age ++
})
ChildComponent({ user: $user })
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
@Component
struct ChildComponent {
@Link
user: UserProfile
build() {
Text('子コンポーネント:' + `${this.user.name} は ${ this.user.age } 歳です`)
.onClick(() => {
this.user.age ++
})
}
}
- 親コンポーネントから値を渡す際には this. を $ に変更し、子コンポーネントでは @Link デコレータでデータを修飾します
3. 状態共有 - 後代コンポーネント
@Provide と @Consume は、後代コンポーネントとの双方向データ同期に使用され、状態データが複数の階層間で渡されるシナリオに適用されます。
1)同じ変数名でバインドする
@Entry
@Component
struct ParentComponent {
@Provide
funds: number = 0
build() {
Column({ space: 20 }) {
Text('親コンポーネント:' + this.funds)
.onClick(() => {
this.funds++
})
MiddleComponent()
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
@Component
struct MiddleComponent {
@Consume
funds: number
build() {
Column({ space: 20 }) {
Text('中間コンポーネント:' + this.funds)
.onClick(() => {
this.funds++
})
ChildComponent()
}
}
}
@Component
struct ChildComponent {
@Consume
funds: number
build() {
Text('子コンポーネント:' + this.funds)
.onClick(() => {
this.funds++
})
}
}
Object、class、string、number、boolean、enum 型をサポート
同じ変数エイリアスでバインドする @Provide('key') と @Consume('key')、キーは一致させる必要があります
- 状態共有 - 状態監視リスナー
開発者が特定の状態変数の値が変更されたかどうかを監視する必要がある場合、@Watch を使用して状態変数にコールバック関数を設定できます。
- @State、@Prop、@Link などのデコレータは @Watch デコレータの前に記述します
import promptAction from '@ohos.promptAction'
@Component
struct ChildComponent {
@Prop
@Watch('onIndexChanged')
currentIndex: number
onIndexChanged() {
promptAction.showToast({ message: '変更を検知' })
}
build() {
Column() {
Text('子コンポーネント:' + this.currentIndex)
}
}
}
@Entry
@Component
struct ParentComponent {
@State currentIndex: number = 0
onChange (index: number) {
this.currentIndex = index
promptAction.showToast({ message: 'クリック' })
}
build() {
Navigation() {
ChildComponent({ currentIndex: this.currentIndex })
}.toolBar({
items: [
{ value: 'ホーム', action: () => this.onChange(0) },
{ value: 'マイページ', action: () => this.onChange(1) },
]
})
}
}
- 初期化時には、@Watch デコレータが適用されたメソッドは呼び出されません
5. @Observedと@ObjectLink
以前は、ネストされたオブジェクトやオブジェクト配列のような複雑なデータを代入してUIを更新していましたが、これはオブジェクトに対応するすべてのUIの更新に影響します。@Observed と @ObjectLink を使用すると、この問題を最適化できます。
使用手順:
- クラス class データをシミュレートするには、コンストラクタを定義し、このクラスを @Observed で修飾する必要があります
- データの初期化:コンストラクタを初期化する方法で追加する必要があります
- @ObjectLink でオブジェクトを関連付けると、関連付けられたオブジェクトを直接変更してUIを更新できます
例:SNSコメントケースの改造
1)コンストラクタの定義と @Observed 修飾子の使用、およびデータの初期化
models/index.ets
@Observed
export class CommentItem {
id: number
avatar: string | Resource
username: string
content: string
timestamp: string
location: string
likeCount: number
isLiked?: boolean
constructor(item: CommentItem) {
for (const key in item) {
this[key] = item[key]
}
}
}
export const commentList: CommentItem[] = [
new CommentItem({
id: 1,
avatar: 'https://example.com/avatar1.jpg',
username: '技術愛好家',
content: 'HarmonyOSの新しい機能は本当に素晴らしいですね!',
timestamp: '11-30',
location: '東京',
likeCount: 34
}),
new CommentItem({
id: 2,
avatar: 'https://example.com/avatar2.jpg',
username: 'プログラマー',
content: 'コンポーネント間の状態共有は非常に直感的で使いやすいです',
timestamp: '11-29',
location: '大阪',
likeCount: 58
}),
new CommentItem({
id: 3,
avatar: 'https://example.com/avatar3.jpg',
username: 'デザイナー',
content: 'UIのパフォーマンスが大幅に向上しました',
timestamp: '11-28',
location: '札幌',
likeCount: 10
}),
new CommentItem({
id: 4,
avatar: 'https://example.com/avatar4.jpg',
username: 'プロダクトマネージャー',
content: '@Observedと@ObjectLinkの組み合わせはゲームチェンジャーです',
timestamp: '11-27',
location: '福岡',
likeCount: 139
}),
new CommentItem({
id: 5,
avatar: 'https://example.com/avatar5.jpg',
username: 'アーキテクト',
content: '大規模アプリケーションでも状態管理が非常に効率的になりました',
timestamp: '11-27',
location: '名古屋',
likeCount: 29
}),
new CommentItem({
id: 6,
avatar: 'https://example.com/avatar6.jpg',
username: 'テスター',
content: 'パフォーマンスの向上が顕著です。特にリストの更新がスムーズになりました',
timestamp: '11-26',
location: '仙台',
likeCount: 100
})
]
2)ネストされたオブジェクト、または配列内のオブジェクトを子コンポーネントに渡し、コンポーネントで @ObjectLink 修飾子を使用してデータを取得します
pages/Index.ets
import promptAction from '@ohos.promptAction'
import { CommentItem, commentList } from '../models'
@Entry
@Component
struct CommentPage {
@State
comments: CommentItem[] = commentList
@State
newComment: string = ''
onSubmitComment() {
const comment: CommentItem = new CommentItem({
id: Math.random(),
content: this.newComment,
username: '匿名ユーザー',
avatar: $r('app.media.default_avatar'),
timestamp: '12-01',
likeCount: 0,
location: '日本'
})
this.comments.unshift(comment)
this.newComment = ''
promptAction.showToast({ message: 'コメント投稿成功' })
}
build() {
Stack() {
Scroll() {
Column() {
HeaderComponent()
OriginalPost()
Divider()
.strokeWidth(8)
.color('#f5f5f5')
Column() {
Text('コメント ' + this.comments.length)
.width('100%')
.margin({ bottom: 15 })
.fontWeight(500)
ForEach(this.comments, (item: CommentItem) => {
CommentCard({ comment: item })
})
}
.padding({ left: 15, right: 15, top: 15 })
}
}
.padding({ bottom: 50 })
Row() {
TextInput({ placeholder: 'コメントを入力...', text: this.newComment })
.placeholderColor('#c3c4c5')
.layoutWeight(1)
.onChange((value) => {
this.newComment = value
})
Text('投稿')
.fontSize(14)
.fontColor('#09f')
.margin({ left: 15 })
.onClick(() => {
this.onSubmitComment()
})
}
.width('100%')
.height(50)
.padding({ left: 15, right: 15 })
.position({ y: '100%' })
.translate({ y: -50 })
.backgroundColor('#fff')
.border({ width: { top: 0.5 }, color: '#e4e4e4' })
}
}
}
@Component
struct CommentCard {
@ObjectLink
comment: CommentItem
onLike() {
if (this.comment.isLiked) {
this.comment.likeCount--
this.comment.isLiked = false
promptAction.showToast({ message: 'いいねを取り消しました' })
} else {
this.comment.likeCount++
this.comment.isLiked = true
promptAction.showToast({ message: 'いいねしました' })
}
}
build() {
Row() {
Image(this.comment.avatar)
.width(32)
.height(32)
.borderRadius(16)
Column() {
Text(this.comment.username)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Text(this.comment.content)
.margin({ bottom: 5 })
.fontColor('#565656')
.lineHeight(20)
Row() {
Text(`${this.comment.timestamp}•${this.comment.location}`)
.layoutWeight(1)
.fontSize(14)
.fontColor('#c3c4c5')
Row() {
Image($r('app.media.like_icon'))
.width(14)
.height(14)
.fillColor(this.comment.isLiked ? '#ff6600' : '#c3c4c5')
.margin({ right: 4 })
Text(this.comment.likeCount.toString())
.fontSize(14)
.fontColor(this.comment.isLiked ? '#ff6600' : '#c3c4c5')
}
.onClick(() => {
this.onLike()
})
}
}
.layoutWeight(1)
.padding({ left: 10 })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ bottom: 15 })
.alignItems(VerticalAlign.Top)
}
}
@Component
struct HeaderComponent {
build() {
Row() {
Row() {
Image($r('app.media.back_icon'))
.width(12)
.height(12)
.fillColor('#848484')
}
.width(24)
.height(24)
.borderRadius(12)
.backgroundColor('#f5f5f5')
.justifyContent(FlexAlign.Center)
.margin({ left: 13 })
Text('コメント')
.padding({ right: 50 })
.textAlign(TextAlign.Center)
.fontSize(18)
.layoutWeight(1)
}
.height(50)
}
}
@Component
struct OriginalPost {
build() {
Row() {
Image('https://example.com/official_account.jpg')
.width(32)
.height(32)
.borderRadius(16)
Column() {
Text('HarmonyOS開発チーム')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Text('HarmonyOSの新機能紹介:コンポーネント間の状態共有がより効率的になりました')
.margin({ bottom: 5 })
.fontColor('#565656')
.lineHeight(20)
Row() {
Text('10-21•IP属地:東京')
.layoutWeight(1)
.fontSize(14)
.fontColor('#c3c4c5')
Row() {
Image($r('app.media.like_icon'))
.width(14)
.height(14)
.fillColor('#c3c4c5')
.margin({ right: 4 })
Text('256')
.fontSize(14)
.fontColor('#c3c4c5')
}
}
}
.layoutWeight(1)
.padding({ left: 10 })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ left: 15, right: 15, bottom: 15 })
.alignItems(VerticalAlign.Top)
}
}
注意事項:
- オブジェクトはコンストラクタで初期化する必要があります
- @ObjectLink を使用する必要があるため、ネストされたコンポーネントが必要です
最後に
HarmonyOSをまだマスターしていない場合、最短時間で習得したい場合は、以下のHarmonyOS学習ロードマップと「HarmonyOS開発学習ノート」を参考にしてください。内容にはArkTS、ArkUI、Web開発、アプリケーションモデル、リソース分類などの知識が含まれています。
【以下のQRコードをスキャンして無料で入手!!】
クイックスタート
- 開発準備
- 最初のArkTSアプリを構築する(Stageモデル)
- 最初のArkTSアプリを構築する(FAモデル)
- 最初のJSアプリを構築する(FAモデル)
開発の基礎知識
- アプリケーションパッケージの基礎知識
- アプリケーション設定ファイル(Stageモデル)
- アプリケーション設定ファイルの概要(FAモデル)
リソースの分類とアクセス
- リソースの分類とアクセス
- リソースディレクトリとリソースファイルの作成
- リソースへのアクセス
ArkTs言語の学習
- ArkTs言語の基本
- 基本構文
- 状態管理
- その他の状態管理
- レンダリング制御
ArkTS宣言型開発パラダイムに基づいた開発
- UI開発(ArkTS宣言型開発パラダイム)の概要
- レイアウトの開発
- コンポーネントの追加
- 画像の表示
- アニメーションの使用
- インタラクティブイベントのサポート
- パフォーマンス向上のための推奨方法
JSに互換性のあるWeb開発パラダイム
- 概要
- フレームワークの説明
- ユーザーインターフェースの構築
- 一般的なコンポーネント開発ガイド
- アニメーション開発ガイド
- カスタムコンポーネント
Webコンポーネント
- 概要
- 基本属性とイベントの設定
- 並行処理
- ウィンドウ管理
- WebGL
- メディア
- セキュリティ
- ネットワークと接続
- 電話サービス
- データ管理
- …
アプリケーションモデル
- 概要
- Stageモデル開発ガイド
- FAモデル開発ガイド