华为仓颉鸿蒙HarmonyOS NEXT原生UI状态管理(类似SwiftUI和Flutter的状态管理)

本文篇幅较长,建议点赞收藏,以免找不到哟(^U^)ノ~YO

Flutter的状态管理:

setState()方法:Flutter中最简单的状态管理方式是使用setState()方法来通知Flutter框架重新构建UI以反映新的状态。

Provider:Provider是Flutter社区广泛使用的状态管理库,它提供了一种简单而有效的方式来管理应用的状态,并支持依赖注入和消费者模式。

Bloc:Bloc是一种基于事件驱动的状态管理库,它有助于将业务逻辑与UI分离,并使状态管理更加结构化。

GetX:GetX是另一个轻量级且高性能的状态管理工具,它提供了便捷的方式来管理状态、路由和依赖注入。

SwiftUI的状态管理:

@State和@Binding:SwiftUI通过@State和@Binding属性包装器来管理界面组件的状态,并实现UI的响应式更新。

ObservableObject和@ObservedObject:SwiftUI还支持ObservableObject协议和@ObservedObject属性包装器,用于管理应用的全局状态和数据流。

Combine:SwiftUI结合了Combine框架,可以使用Publishers和Subscribers来处理数据流和响应式编程。

Redux:虽然不是官方支持的方式,但在SwiftUI中也可以使用Redux架构来管理复杂的状态

仓颉的状态管理具体如下:

@Entry

@Entry 用于指明当前页面的入口组件,仅能装饰在被 @Component 修饰的子组件上。一个文件最多只能有一个 @Entry

示例

@Entry
    @Component
    class ParentComponent {
        func build() {
            Column {
                Text("")
            }
        }
    }

@Component

@Component 用于修饰可重用的 UI 单元,该单元必须为 Class 类型。被修饰的class能够成为一个单独的组件,也被称为自定义组件。在render函数中描述UI结构。

  • 自定义组件中的成员变量必须声明类型
  • 自定义组件必须实现render函数
  • 自定义组件禁止自定义构造函数
  • 自定义组件禁止继承
  • 自定义组件禁止使用泛型

示例:

@Component
    class ParentComponent {
        func build() {
            Column {
                Text("")
            }
        }
    }

自定义组件可以在其他组件中使用

@Component
    class SubComponent {
        func build() {
            Column {
                Text("")
            }
        }
    }


    @Component
    class ParentComponent {
        func build() {
            Column {
                Text("")
                SubComponent()
            }
        }
    }

如果类中有成员变量,可以通过命名参数传值。其中,对于没有初始值的成员变量,必须在创建实例的时候传值( @Consume 装饰的变量除外),有初始值的则可以选择性地传值;对于let修饰的且带有初始值的成员变量,则不可在创建实例的时候传值。

@Component
    class SubComponent {
        @Link var cnt: Int64
        let innerCnt = 0
        var count: Int64 = 0
        func build() {
            Column {
                Text("")
            }
        }
    }


    @Component
    class ParentComponent {
        @State var cnt: Int64 = 0
        func build() {
            Column {
                Text("")
                SubComponent() // Error cnt没有初始化
                SubComponent(cnt: cnt) // OK count有初始值,可以不传值
                SubComponent(cnt: cnt, innerCnt: cnt) // Error innerCnt不可通过构造传值
            }
        }
    }

@State

@State 修饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的render刷新。

被 @State 修饰的成员变量必须指明类型和初始值。由于状态是需要被更改的数据,所以对于被 @State 修饰的变量必须通过 var 来声明,不能使用 let。

  • 支持多种类型:支持基础数据类型:char、UInt8/16/32/64、Int8/16/32/64、Float16/32/64、Bool、String、Enum、Struct等;支持类类型和数组类型,但是如果要感知内部的变化,对于类类型,在定义的时候需要被@Observed修饰(详见@Observed和@Publish一节);对于数组类型则需要使用ObservedArray和ObservedArrayList,详见ObservedArray和ObservedArrayList一节。
  • 私有:仅在组件内访问;不可被 public protected 等修饰符修饰;
  • 支持多个实例:一个组件中可以定义多个标有 @State的属性;
  • 需要本地初始化:必须为所有 @State 变量分配初始值。
  • 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定 @State状态属性的初始值。

示例

@Entry
    @Component
    class ParentComponent {
        @State var count: ObservedArrayList<T> = 0
        func build() {
            Column {
                Button("This is a case of @State ${count}")
                .onClick({evt =>
                    count[0] = 9
                })
            }
        }
    }

@Link

@Link 与 @State 有相同的语义,但初始化方式不同。 @Link 装饰的变量必须使用其父组件提供的变量进行初始化,被 @Link 修饰的成员变量必须指明类型,且只能使用 var 来声明。允许组件内部修改 @Link 变量,且更改会通知给父组件,即 @Link 属于双向数据绑定。 @Link 修饰当前组件所拥有的状态,仅可在子组件中定义。

  • 支持多种类型: @Link 变量的值与 @State 变量的类型相同。
  • 私有:仅在组件内访问;不可被 public protected 等修饰符修饰;
  • 支持多个实例:一个组件中可以定义多个标有 @Link 的属性;
  • 双向通信:子组件对 @Link 变量的更改将同步修改父组件的 @State 变量;

示例

@Component
    class SubSubComponent {
        @Link var count: Int64
        var commonCount: Int64 = 0
        func build() {
            Column {
                Button("This is a case of @Link ${count} _____ SubSubComponent ${commonCount}").onClick({evt =>
                    count = count + 1
                })
            }
        }
    }


    @Component
    class SubComponent {
        @Link var count: Int64
        func build() {
            Column {
                Button("This is a case of @Link ${count} _____ SubComponent")
                .onClick({evt =>
                    count = count + 1
                })
                SubSubComponent(count: count, commonCount: count)
            }
        }
    }


    @Entry
    @Component
    class ParentComponent {
        @State var count: Int64 = 0
        func build() {
            Column {
                Button("This is a case of @Link ${count} _____ ParentComponent")
                .onClick({evt =>
                    count = count + 1
                })
                SubComponent(count: count)
            }
        }
    }

@Prop

@Prop 与 @State 有相同的语义,但初始化方式不同。 @Prop 装饰的变量必须使用其父组件提供的变量进行初始化,被 @Prop 修饰的成员变量必须指明类型,且只能使用 var 来声明。允许组件内部修改 @Prop 变量,但更改不会通知给父组件,即 @Prop 属于单向数据绑定。 @Prop 修饰当前组件所拥有的状态,仅可在子组件中定义。

@Prop 状态数据具有以下特征:

  • 支持类型: @Prop 变量的值与 @State 变量的类型相同。
  • 私有:仅在组件内访问;不可被 public protected 等修饰符修饰;
  • 支持多个实例:一个组件中可以定义多个标有 @Prop 的属性;
  • 创建自定义组件时将值传递给@Prop变量进行初始化:在创建组件的新实例时,必须初始化所有 @Prop 变量,不支持在组件内部进行初始化。

示例

@Component
    class SubComponent {
        @Prop var count: Int64
        func build() {
            Column {
                Button("This is a case of @Prop ${count} _____ SubComponent")
                .onClick({evt =>
                    count = count + 1
                })
            }
        }
    }


    @Entry
    @Component
    class ParentComponent {
        @State var count: Int64 = 0
        func build() {
            Column {
                Button("This is a case of @Prop ${count} _____ ParentComponent")
                .onClick({evt =>
                    count = count + 1
                })
                SubComponent(count: count)
            }
        }
    }

@Observed and @Publish

针对类类型的状态变量,引入 @Observed 和 @Publish。

  • @Observed 修饰类,被修饰的类表示可以被观察到。需要和 @Publish 一起使用。没有被 @Publish 修饰的属性,变化了也不会触发UI更新。 @Observed 的类中不能定义init函数。
  • @Publish 修饰属性,被修饰的属性能够被观察到,一旦属性发生变化,会自动修改页面。 @Publish 只能在 @Observed 修饰的类中使用。可以给属性赋初值,如果没有赋值,需要在构造时给值。
  • @Publish 修饰的变量只能由var声明。

注意

被 @Publish 修饰的成员变量必须指明类型和初始值。但对于String,Int64,Float64和Bool类型,如果变量的初始值是上述类型的字面量,则可以省略类型。

示例

@Observed
    class Book {
        @Publish var title: String = ""
        @Publish var price: Int64 = 9
        var id: Int64
    }


    @Component
    class Demo {
        @State var book: Book = Book(title: "myBook", id: 3214324)


        func build() {
            Column {
                Text("${book.title}, ${book.price}")
                Button("click").onClick({e =>
                    book.title = "noBook"
                })
            }
        }
    }

多层嵌套类

如果类中的属性也是类类型,且该属性需要被监听,那么这个类也要被@Observed修饰

@Observed
    class Directory {
        @Publish var title: String = ""
        @Publish var price: Int64 = 9
    }


    @Observed
    class Book {
        @Publish var dir: Directory = Directory()
        @Publish var title: String = ""
        @Publish var price: Int64 = 9
        var id: Int64
    }


    @Component
    class Demo {
        @State var book: Book = Book(title: "myBook", id: 3214324)


        func build() {
            Column {
                Text("${book.title}, ${book.price}")
                Button("click").onClick({e =>
                    book.title = "noBook"
                })
            }
        }
    }

@Provide and @Consume

@Provide 作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。 @Consume 在感知到 @Provide 数据的更新后,会触发当前view的重新渲染。

同样地, @Consume 发生变化后,也会触发 @Provide 所在的页面渲染,二者构成双向数据绑定。

  • 支持类型: @Provide 和 @Consume 变量的值与 @State 变量的类型相同。
  • 相互关联的 @Provide 和 @Consume 的变量名和类型必须相同,
  • @Provide 和 @Consume 修饰当前组件所拥有的状态,仅在当前组件内生效,且只可以用于修饰当前子组件的成员变量,不可被 public protected 等修饰符修饰;
  • @Provide 修饰的成员变量必须指明类型且有初始值,变量只能由var声明。
  • @Consume 修饰的成员变量必须指明类型且不能有初始值,变量只能由var声明。
  • 与 @Link 和 @Prop 不同的是, @Consume 的值不需要通过构造函数传递。只要保证 @Provide 和 @Consume 的类型和变量名一致,且 @Consume 声明的变量所在的组件是 @Provide 声明的变量所在的组件的子孙组件,就可以建立 @Provide 和 @Consume 之间的双向数据绑定。

如果只有 @Provide ,没有 @Consume,则该 @Provide可视作 @State。而 @Consume必须有对应的 @Provide,如果其父辈节点中没有相对应的 @Provide变量,则该@Consume变量定义所在的自定义组件将不会显示,且会在log中打印runtime报错。

示例

@Component
    class SubSubComponent {
        @Consume var stateContent: Int64


        func build() {
          Column {
            Text("this is SubSubComponent ${this.stateContent}")
            Button("${this.stateContent}").onClick({evt => this.stateContent += 1})
          }
        }
    }


    @Component
    class SubComponent {
        func build() {
          Column {
            Text("this is SubComponent")
            SubSubComponent()
          }
        }
    }


    @Entry
    @Component
    class ParentComponent {
        @Provide var stateContent: Int64 = 0
        func build() {
          Column {
            SubComponent()
            Button("${this.stateContent}").onClick({evt => this.stateContent += 1})
          }
        }
    }

@Builder

@Builder 装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。 @Builder 装饰方法的功能和语法规范与render函数相同。

  • 支持泛型参数、泛型约束、命名参数、命名参数默认值等仓颉函数基本功能;
  • 可以修饰全局函数,也可以修饰类的成员函数;
  • 不支持使用 open 修饰并被重写(我们建议 @Builder 函数只定义在全局或 @Component 修饰的类中);
  • 不支持使用 static 修饰作为类的静态方法;
  • 函数的返回值类型为 Unit,返回值类型可以省略;
  • 函数体中只允许包含 UI 组件声明(UI 组件声明包括:内置组件、@Component 修饰类的实例构造、@Builder 修饰函数的调用);
  • 可以使用导出普通仓颉函数同样的策略,导出 @Builder 修饰的函数。

示例

@Builder
    func f(fontSz: Int64, color: Color) {
        Text("hello world").fontSize(fontSz).fontColor(color)
    }


    @Entry
    @Component
    class MyView {
        func build() {
            Column(20) {
                f(20, Color.BLUE)
            }
        }
    }

@BuilderParam

@BuilderParam 用来装饰指向@Builder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。

  • 所修饰变量的类型为函数类型,且返回值类型为 Unit;
  • 所修饰的变量声明中,变量的类型需要显示标注;
  • 只能修饰类的成员变量声明,禁止修饰全局变量(否则将产生编译错误);
  • 所修饰的类成员变量遵循 private 修饰符的可见性(即:只允许在类内部使用);
  • 所修饰变量可以是可变的,也可以是不可变的,变量的可变性遵循仓颉语法由 let/var 关键字标识。

示例

@Entry
    @Component
    class MyView {
        @Builder
        func f1(): Unit {
            f<Int64>(100, 30, color: Color.BLACK)
        }


        @Builder
        func f2(): Unit {
            f<String>("hello", 30, color: Color.RED)
        }


        func build() {
            Column(10) {
                FrameView(ab: f1, color: Color.GREEN)
                FrameView(ab: f2, color: Color.GRAY)
            }
        }
    }


    @Component
    class FrameView {
        @BuilderParam let ab: () -> Unit
        let color: Color


        func build() {
            Column(10) {
                ab()
            }.backgroundColor(color)
        }
    }


    @Builder
    func f<T>(label: T, fontSz: Int64, color!: Color = Color.WHITE): Unit where T <: ToString {
        Text("${label}").fontSize(fontSz).fontColor(color)
    }

@Watch

@Watch 用于监听状态变量的变化。如下给状态变量增加一个@Watch装饰器,通过@Watch注册一个回调方法onChanged,当状态变量count被改变时,触发onChanged回调。

  • 可以监听 @State 、 @Prop 、 @Link 、 @Provide 、 @Consume 、 @StorageLink 和 @StorageProp 装饰的变量的变化。
  • 被监听的变量必须声明类型。

示例

@Component
    class MyView {
        @State @Watch[onChanged] count: Int64 = 0


        func onChanged() {}


        func build() {...}
    }

其中,onChanged函数是一个参数为空,且返回为空的函数。

ObservedArray and ObservedArrayList

提供ObservedArray和ObservedArrayList作为状态管理的数组类型,当其中数组发生变化时,如修改其中一项的值,删除或添加一项,就会触发UI更新。

ObservedArray

定义了状态管理的一个数组类型。

init(Array)

public init(initValue: Array<T>)

状态管理数组的构造函数。

参数名 参数类型 必填 默认值 描述
initValue Array - 状态管理数组的初始化数组。

subscribeInner(Observer)

public func subscribeInner(observer: Observer): Unit

对状态管理数组的每一项进行递归的观察绑定。

参数名 参数类型 必填 默认值 描述
observer Observer - 绑定的观察类。

unsubscribeInner(oObserver)

public func unsubscribeInner(observer: Observer): Unit

对状态管理数组的每一项进行递归的解绑。

参数名 参数类型 必填 默认值 描述
observer Observer - 解绑的观察类。

get()

public func get(): Array<T>

获取状态管理数组。

set(Array)

public func set(newValue: Array<T>): Unit

设置状态管理数组。

参数名 参数类型 必填 默认值 描述
newValue Array - 状态管理数组被设置的新数组值。

set(ObservedComplexAbstract)

public func set(newValue: ObservedComplexAbstract): Unit

设置状态管理数组。

参数名 参数类型 必填 默认值 描述
newValue ObservedComplexAbstract - 状态管理数组被设置的新值。

public operator func [](index: Int64): T

读取数组中指定索引对应的数组项。

参数名 参数类型 必填 默认值 描述
index Int64 - 指定读取数组项的索引。

[](Int64, T)

public operator func [](index: Int64, value!: T): Unit

改变数组中指定索引对应的数组项的值。

参数名 参数类型 必填 默认值 描述
index Int64 - 指定数组项的索引。
value T - 指定索引对应的数组项的新值。

size

public prop size: Int64

获取状态管理数组的大小。

ObservedArrayList

提供支持状态管理的可变长度的数组的功能,可以感知内部的变化。

构造函数

init(ArrayList)

public init(initValue: ArrayList<T>)

构造一个包含指定ArrayList数组中所有元素的 ObservedArrayList。

参数名 参数类型 必填 默认值 描述
initValue ArrayList - ArrayList数组,用来初始化ObservedArrayList。

init(initValue: Array)

public init(initValue: Array<T>)

构造一个包含指定Array数组中所有元素的 ObservedArrayList。

参数名 参数类型 必填 默认值 描述
initValue Array - Array数组,用来初始化ObservedArrayList。

函数

subscribeInner(Observer)

public func subscribeInner(observer: Observer): Unit

对状态管理数组的每一项进行递归的观察绑定。

参数名 参数类型 必填 默认值 描述
observer Observer - 绑定的观察类。

unsubscribeInner(Observer)

public func unsubscribeInner(observer: Observer): Unit

对状态管理数组的每一项进行递归的解绑。

参数名 参数类型 必填 默认值 描述
observer Observer - 解绑的观察类。

get()

public func get(): ArrayList<T>

返回一个ArrayList数组,其中包含此ObservedArrayList中按正确顺序排列的所有元素。

set(ArrayList)

public func set(newValue: ArrayList<T>): Unit

通过一个ArrayList数组重置当前ObservedArrayList的值。

参数名 参数类型 必填 默认值 描述
newValue ArrayList - ArrayList数组,用来设置ObservedArrayList的值。

set(Array)

public func set(newValue: Array<T>): Unit
参数名 参数类型 必填 默认值 描述
newValue Array - Array数组,用来设置ObservedArrayList的值。

通过一个Array数组重置当前ObservedArrayList的值。

set(ObservedComplexAbstract)

public func set(newValue: ObservedComplexAbstract): Unit

通过ObservedComplexAbstract重置当前ObservedArrayList的值。

参数名 参数类型 必填 默认值 描述
newValue ObservedComplexAbstract - ObservedComplexAbstract数据,用来设置ObservedArrayList的值。

public operator func [](index: Int64): T

操作符重载 - get。返回索引位置的元素的值。

参数名 参数类型 必填 默认值 描述
index Int64 - 表示 get 接口的索引。

[](Int64, T)

public operator func [](index: Int64, value!: T): Unit

操作符重载 - set,通过下标运算符用指定的元素替换此列表中指定位置的元素。

参数名 参数类型 必填 默认值 描述
index Int64 - 要设置的索引值。
value T - 要设置的 T 类型的值。

isEmpty()

public func isEmpty(): Bool

判断 ObservedArrayList 是否为空。

clone()

public func clone(): ObservedArrayList<T>

返回此ObservedArrayList实例的拷贝(浅拷贝)。

clear()

public func clear(): Unit

从此 ObservedArrayList 中删除所有元素。

append(T)

public func append(element: T): Unit

将指定的元素附加到此 ObservedArrayList 的末尾。

参数名 参数类型 必填 默认值 描述
element T - 插入的元素。

appendAll(Collection)

public func appendAll(elements: Collection<T>): Unit

将指定集合中的所有元素附加到此 ObservedArrayList 的末尾。

函数会按照迭代器顺序遍历入参中的集合,并且将所有元素插入到此 ObservedArrayList 的尾部。

参数名 参数类型 必填 默认值 描述
elements Collection - 需要插入的元素的集合。

insert(Int64, T)

public func insert(index: Int64, element: T): Unit

在此 ObservedArrayList 中的指定位置插入指定元素。

参数名 参数类型 必填 默认值 描述
index Int64 - 插入元素的目标索引。
element T - 要插入的 T 类型元素。

insertAll(Int64, Collection)

public func insertAll(index: Int64, elements: Collection<T>): Unit

从指定位置开始,将指定集合中的所有元素插入此 ObservedArrayList。

函数会按照迭代器顺序遍历入参中的集合,并且将所有元素插入到指定位置。

参数名 参数类型 必填 默认值 描述
index Int64 - 插入集合的目标索引。
elements Collection - 要插入的 T 类型元素集合。

prepend(T)

public func prepend(element: T): Unit

在起始位置,将指定元素插入此 ObservedArrayList。

参数名 参数类型 必填 默认值 描述
element T - 要插入 T 类型元素。

prependAll(Collection)

public func prependAll(elements: Collection<T>): Unit

从起始位置开始,将指定集合中的所有元素插入此 ObservedArrayList。

函数会按照迭代器顺序遍历入参中的集合,并且将所有元素插入到指定位置。

参数名 参数类型 必填 默认值 描述
elements Collection - 要插入的 T 类型元素集合。

remove(Int64)

public func remove(index: Int64): T

删除此 ObservedArrayList 中指定位置的元素。返回被移除的元素。

参数名 参数类型 必填 默认值 描述
index Int64 - 被删除元素的索引。

remove(Range)

public func remove(range: Range<Int64>): Unit

删除此 ObservedArrayList 中 Range 范围所包含的所有元素。

参数名 参数类型 必填 默认值 描述
range Range - 需要被删除的元素的范围。

removeIf((T) -> Bool)

public func removeIf(predicate: (T) -> Bool): Unit

删除此 ObservedArrayList 中满足给定 lambda 表达式或函数的所有元素。

参数名 参数类型 必填 默认值 描述
predicate (T) -> Bool - 判断删除的条件。

属性

size

public prop size: Int64

此 ObservedArrayList 中的元素个数。

示例如下

@Component
    class MyView {
        @State var arr: ObservedArray<Int64> = ObservedArray<Int64>([1, 2])


        func build() {
            Column {
                Text("arr[0] is ${arr[0]}")
                Button("click").onClick {
                    arr[0] = 0
                }
            }
        }
    }
    @Component
    class MyView {
        @State var arr: ObservedArrayList<Int64> = ObservedArrayList<Int64>([1, 2])


        func build() {
            Column {
                Text("arr[0] is ${arr[0]}")
                Button("click").onClick {
                    arr[0] = 0
                }
                Button("append").onClick {
                    arr.append(0)
                }
            }
        }
    }

ObservedProperty

get()

public func get(): T

读取同步属性的数据

返回值:

类型 描述
T 同步属性的数据。

示例:

AppStorage.setOrCreate<Int64>("propA", 47)
    let prop1: ObservedProperty<Int64> = AppStorage.`prop`<Int64>("propA")
    prop1.get() // 47

set(T)

public func set(newValue: T): Unit

设置同步属性的数据

参数:

参数名 参数类型 必填 默认值 描述
newValue T - 要设置的数据。

示例:

AppStorage.setOrCreate<Int64>("propA", 47)
    let prop1: ObservedProperty<Int64> = AppStorage.`prop`<Int64>("propA")
    prop1.set(1); //  prop1.get()=1

状态更新的注意事项

不允许在spawn表达式中对状态变量进行并发修改,会导致并发安全问题。建议当需要修改状态变量时,采用concurrency包提供的launch方法,将状态更新的步骤放回主线程中运行,以保证并发安全。如下实例演示如何在spawn表达式中更新变量状态:

import ohos.base.*
    import ohos.component.*
    import ohos.state_manage.*
    import ohos.state_macro_manage.*
    import ohos.concurrency.*
    import std.sync.*
    import std.collection.*
    import std.time.Duration


    @Entry
    @Component
    class MyView {
        @State var text: String = "begin"


        func build() {
            Column(30) {
                Button(text).onClick { evt =>
                    changeText({ p: String =>
                        // 使用launch表达式在主线程中更新状态变量
                        launch {
                            text = p
                        }
                    })
                }
            }.width(100.percent)
        }


        private func changeText(callback: (String) -> Unit): Unit {
            spawn {
                while (true) {
                    callback("blink 0")
                    sleep(Duration.millisecond * 100)
                    callback("blink 1")
                    sleep(Duration.millisecond * 100)
                }
            }
        }
    }

如对您有帮助,帮忙点个“在看 、关注” 让更多的人受益~!

技术交流群可加wx“LB-9191”备注cangjie

posted @ 2024-12-24 21:34  BisonLiu  阅读(180)  评论(0)    收藏  举报