OpenHarmony状态管理
响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在 React/Vue(两者都是支持响应式编程的 Web 开发框架),还是 AarkUI开发框架都提供了多维度的状态管理机制,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分为只读的单向传递和可变更的双向传递。如下图所示,开发框架提供了多种应用程序状态管理的能力。那么在使用的时候如何选择呢?答案是取决于实际情况!以下是管理状态的最常见的方法:
-
Widget 管理自己的状态。
-
Widget 管理子 Widget 状态。
-
混合管理(父 Widget 和子 Widget 都管理状态)。
-
跨组件管理状态,爷孙组件
如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:
-
如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
-
如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
-
如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。
在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。有些时候,如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显得更重要一些)。
状态变量装饰器的作用
管理组件内部的状态数据,有指向性的更新目标视图。
-
@State: 组件拥有的状态属性,当@State装饰的变量更改时,组件会重新渲染更新视图。
-
@Prop: 依赖父组件的状态属性,但是子组件所做的更改不会引起到父组件的视图刷新,属于单向传递。
-
@Link: 和@Prop相似,但是不同之处在于,当任何一个组件中的数据更新时,另一个组件的状态都会更新,父子组件重新渲染。
三种状态变量装饰器的异同点
这三种装饰器都有各自的特点,主要总结如下:
相同点:只能在组件内使用。
异同点:1)@Prop只支持基本类型number,string,boolean( 注意点:必须小写 ), @State和@Link除了支持上述的类型外,还支持上述类型的数组,但是不支持复杂类型object和any( *注意点*:官网介绍的不支持,但是实际用是可以支持的( 除了@State的number类型 ),);
2)@State组件内不同实例的状态数据是独立的;
3)初始化数据方式不同,@State可以在自己的组件内初始化,也可以为自定义的组件提供传值的方式进行初始化;@Prop和@Link初始化则是父组件里面@State定义的变量( 注意点:不能在组件内部初始化,只能定义类型;
4)@Prop是单向,内部修改不会更新父组件);@Link则是双向,会通知父组件@State变量。
5)如果是多层嵌套(也就是子组件里面再嵌套孙子组件),那里面所有的组件定义的状态名必须保持和父级组件的传值参数名一致 。
@State修饰符
@State
装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build()
方法刷新UI。 @State
状态数据具有以下特征:
-
支持多种数据类型:允许
class
、number
、boolean
、string
强类型的按值和按引用类型。允许这些强类型构成的数组,即Array<class>
、Array<string>
、Array<boolean>
、Array<number>
。不允许object
和any
。 -
内部私有:标记为
@State
的属性是私有变量,只能在组件内访问。 -
支持多个实例:组件不同实例的内部状态数据独立。
-
需要本地初始化:必须为所有
@State
变量分配初始值,将变量保持未初始化可能导致框架行为未定义,初始值需要是有意义的值,比如设置class
类型的值为null
就是无意义的,会导致编译报错。 -
创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定
@State
状态属性的初始值。简单样例如下所示:
/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @Entry @Component struct ComponentTest { @State content: string = "父组件"; // data变化会触发build方法执行 build() { Column({ space: 10 }) { Text(`${this.content}`) .fontSize(36) Item() // 子组件 Item() // 子组件 Button('更新内容') .onClick(() => { this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行 }) } .width('100%') .height('100%') .padding(10) } } // 自定义子组件 @Component struct Item { @State content: string = "子组件" ; build() { Text(`${this.content}`) .fontSize(36) .onClick(() => { this.content = "修改为新内容"; // 点击更新内容,执行build方法 }) } }
样例运行结果如下图所示:
@Prop修饰符
@Prop
与 @Stat
有相同的语义,但初始化方式不同, @Prop
装饰的变量可以和父组件的 @State
变量建立单向的数据绑定。即 @Prop
修饰的变量必须使用其父组件提供的 @State
变量进行初始化,允许组件内部修改 @Prop
变量值但更改不会通知给父组件。 @State
状态数据具有以下特征:
-
支持简单数据类型:仅支持
number
、string
、boolean
简单类型; -
内部私有:标记为
@Prop
的属性是私有变量,只能在组件内访问。 -
支持多个实例:组件不同实例的内部状态数据独立。
-
不支持内部初始化:在创建组件的新实例时,必须将值传递给 @Prop 修饰的变量进行初始化,不支持在组件内部进行初始化。
简单样例如下所示:
/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @Entry @Component struct ComponentTest { @State content: string = "父组件"; // data变化会触发build方法执行 build() { Column({ space: 10 }) { Text(`${this.content}`) .fontSize(36) Item({ content:this.content}) // 子组件 Item({ content:this.content}) // 子组件 Button('更新内容') .onClick(() => { this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行 }) } .width('100%') .height('100%') .padding(10) } } // 自定义子组件 @Component struct Item { @Prop content: string ;// // 不允许本地初始化 build() { Text(`${this.content}`) .fontSize(36) .onClick(() => { this.content = "修改为新内容"; // 点击更新内容,执行build方法 }) } }
样例运行结果如下图所示:
@Link修饰符
@Link
与 @State
有相同的语义,但初始化方式不同, @Link
装饰的变量可以和父组件的 @State
变量建立双向的数据绑定。即 @Link
修饰的变量必须使用其父组件提供的 @State
变量进行初始化,允许组件内部修改 @Link
变量值且更改会通知给父组件。 @Link
状态数据具有以下特征:
-
支持多种数据类型:
@Link
变量的值与@State
变量的类型相同,即class
、number
、string
、boolean
或这些类型的数组。 -
内部私有:标记为
@Link
的属性是私有变量,只能在组件内访问。 -
支持多个实例:组件不同实例的内部状态数据独立。
-
不支持内部初始化:在创建组件的新实例时,必须将值传递给
@Prop
修饰的变量进行初始化,不支持在组件内部进行初始化。初始化使用$
符号,例如:$propertiesName。
样例如下:
/*
* Copyright (c) 2021 JianGuo Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Entry
@Component
struct ComponentTest {
@State content: string = "父组件"; // data变化会触发build方法执行
build() {
Column({ space: 10 }) {
Text(`${this.content}`)
.fontSize(36)
Item({ content:$content}) // 子组件
Item({ content:$content}) // 子组件
Button('更新内容')
.onClick(() => {
this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
// 自定义子组件
@Component
struct Item {
@Link content: string ;// 变更time,父组件的对应属性也变化
build() {
Text(`${this.content}`)
.fontSize(36)
.onClick(() => {
this.content = "修改新内容"; // 点击更新内容,执行build方法
})
}
}
样例运行结果如下图所示:
@StorageLink修饰符
@StorageLink(key)
装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build()
方法进行UI刷新。组件通过使用 @StorageLink(key)
装饰的状态变量与 AppStorage
建立双向数据绑定。当创建包含 @StorageLink
的状态变量的组件时,该状态变量的值将使用 AppStorage
中的值进行初始化,在UI组件中对 @StorageLink
的状态变量所做的更改将同步到 AppStorage
,并从 AppStorage
同步到任何其他绑定实例中,如 PersistentStorage
或其他绑定的UI组件。 @StorageLink
状态数据具有以下特征:
-
支持多种数据类型:支持的数据类型和
@State
一致且支持object
。 -
需要本地初始化:必须为所有
@StorageLink
变量分配初始值。 -
数据状态全局化:使用
@StorageLink
修饰的数据变化后全局都会改变。 -
数据持久化:通过搭配
PersistentStorage
接口实现数据持久化。-
双向绑定数据
简单样例如下所示:
/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @Entry @Component struct ComponentTest { @StorageLink('content') content: string = "父组件"; // 使用StorageLink标记并初始化 build() { Column({ space: 10 }) { Text(`${this.content}`) .fontSize(36) Item1(); Item1(); Button('更新内容') .onClick(() => { this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行 }) } .width('100%') .height('100%') .padding(10) } } // 自定义子组件 @Component struct Item1 { @StorageLink('content') content: string ="子组件" ;// 变更time,父组件的对应属性也变化 build() { Text(`${this.content}`) .fontSize(36) .onClick(() => { this.content = "修改新内容"; // 点击更新内容,执行build方法 }) } }
运行结果如下图所示:
-
页面间数据绑定
简单样例如下图所示:
/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import router from '@ohos.router'; @Entry @Component struct ComponentTest { @StorageLink('content') content: string = "父组件"; // 使用StorageLink标记并初始化 build() { Column({ space: 10 }) { Text(`${this.content}`) .fontSize(36) Item1(); Item1(); Button('更新内容') .onClick(() => { this.content = "修改为新内容"; // 更改content的值,所有使用key的页面都会刷新 }) Button('跨页面更新数据') .onClick(() => { router.push({url: "pages/StorageLink2"}) }) } .width('100%') .height('100%') .padding(10) } } // 自定义子组件 @Component struct Item1 { @StorageLink('content') content: string ="子组件" ;// 变更time,父组件的对应属性也变化 build() { Text(`${this.content}`) .fontSize(30) .onClick(() => { this.content = "我是第一个页面的子组件"; //更改content的值,所有使用key的页面都会刷新 }) } }
第二个页面
/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import router from '@ohos.router' // 第二个页面 @Entry @Component struct Setting { @StorageLink('content') content: string = "我是第二个页面"; // content的值以'key'第一次出现的为准 build() { Column({space: 10}) { Text(this.content) // tips的值以'key'第一次出现的为准 .fontSize(20) .margin(20) .onClick(() => { this.content = "我是第二个页面的数据" // 更改content的值,所有使用key的页面都会更新 }) Button('返回') .onClick(() => { router.back()// 点击返回,首页的数据会更改 }) } .width('100%') .height('100%') } }
运行结果如下图所示:
-
持久化数据
@StorageLink
搭配PersistentStorage
接口可以实现数据本地持久化,简单样例如下图所示:/* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 2021 JianGuo Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import router from '@ohos.router'; // 持久化存储key并设置默认值 PersistentStorage.PersistProp("content", "Hello, OpenHarmony") @Entry @Component struct ComponentTest { @StorageLink('content') content: string = "父组件"; // 使用StorageLink标记并初始化 build() { Column({ space: 10 }) { Text(`${this.content}`) .fontSize(36) Item1(); Item1(); Button('更新内容') .onClick(() => { this.content = "修改为新内容"; // 更改content的值,所有使用key的页面都会刷新 }) Button('跨页面更新数据') .onClick(() => { router.push({url: "pages/StorageLink2"}) }) } .width('100%') .height('100%') .padding(10) } } // 自定义子组件 @Component struct Item1 { @StorageLink('content') content: string ="子组件" ;// 变更time,父组件的对应属性也变化 build() { Text(`${this.content}`) .fontSize(30) .onClick(() => { this.content = "我是第一个页面的子组件"; //更改content的值,所有使用key的页面都会刷新 }) } }
运行结果如下图所示:
-
- 点赞
- 收藏
- 关注作者
评论(0)