鸿蒙的状态管理

45 阅读3分钟

​##鸿蒙核心技术##ArkTS##状态管理#

任何语言都有自己的声明数据的方式,比如C语言中 int a=1;JavaScript中let arr=[1,2,3]

这篇文章主要探讨鸿蒙中的数据声明以及数据传递。

鸿蒙的基础知识可以参考:Harmony OS NEXT初学笔记-CSDN博客

一.@state与private


@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  private str="你好"
  build() {
    Column() {
      Text(`message:${this.message}`)
      Text(`str:${this.str}`)
      Button("按钮")
        .onClick(()=>{
          this.message="你好世界"
          this.str="hello "
          console.log(`点击按钮后,message:${this.message},str:${this.str}`)
        })
    }
    .height('100%')
    .width('100%')
  }
}

在这部分代码中用@State和private声明了两个变量,下面看效果

编辑

在页面上显示出来了,再看按钮,绑定了点击事件,点击按钮改变两个声明的变量,下面我们点击按钮,看看结果

编辑

编辑

发现两个变量的值都改变了,但是在页面上只改变了用@state声明的值

那么我们可以得出结论:用@state声明的状态变量改变后,页面会随之改变,但是用private声明的变量,或者不用private声明直接写变量的例如 num=1,这种改变不会使页面刷新但是这种变量的值也确实会改变

那么我们用private声明的值计算,把值赋给@State声明的呢?


@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  private str="你好"
  @State sum:number=0
  private num1:number=0
  private num2:string="0"
  build() {
    Column() {
      Text(`message:${this.message}`)
      Text(`str:${this.str}`)
      Button("按钮")
        .onClick(()=>{
          this.message="你好世界"
          this.str="hello "
          console.log(`点击按钮后,message:${this.message},str:${this.str}`)
        })
      TextInput({placeholder:"请输入num1"}).onChange((value)=>{
        this.num1=Number(value)
        console.log(`nmum1:${this.num1.toString()}`)
      })
      TextInput({placeholder:"请输入num2",text:$$this.num2})
      Text(`num1+num2= ${this.num1+Number(this.num2)}`)
      Text(`sum= ${this.sum}`)
      Button("按钮").onClick(()=>{
        this.sum=this.num1+Number(this.num2)
        console.log(`num1+num2= ${this.num1+Number(this.num2)}`)
      })
    }
    .height('100%')
    .width('100%')
  }
}

这里我们声明两个变量进行输入,注意到num1是用的textinput的onchange函数来赋值,而num2则是用进行双向绑定,在这里进行双向绑定,在这里相当于onchange的简写

那么我们看看效果 image.png

发现,两个private数据相加也不会改变,但是点击按钮后会触发@State声明的变量改变来触发页面刷新

image.png

同样有输出结果

那么我们想一想,现在是每次点击按钮页面才会刷新,那么有没有一种方法不用点击按钮自动刷新呢?

那就是我们下面介绍的

二.@Watch

因为@Watch必须与装饰器绑定,因此@Watch不能装饰private


@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  private str="你好"
  @State sum:number=0
  private num1:number=0
  @State @Watch("change") num2:string="0"
  change(){
    this.sum=this.num1+Number(this.num2)
  }
  build() {
    Column() {
      Text(`message:${this.message}`)
      Text(`str:${this.str}`)
      Button("按钮")
        .onClick(()=>{
          this.message="你好世界"
          this.str="hello "
          console.log(`点击按钮后,message:${this.message},str:${this.str}`)
        })
      TextInput({placeholder:"请输入num1"}).onChange((value)=>{
        this.num1=Number(value)
        console.log(`nmum1:${this.num1.toString()}`)
      })
      TextInput({placeholder:"请输入num2",text:$$this.num2})
      Text(`num1+num2= ${this.num1+Number(this.num2)}`)
      Text(`sum= ${this.sum}`)
      Button("按钮").onClick(()=>{
        this.sum=this.num1+Number(this.num2)
        console.log(`num1+num2= ${this.num1+Number(this.num2)}`)
      })
    }
    .height('100%')
    .width('100%')
  }
}

注意看,我们在@Watch后面加了一个change函数,也是必须加的,名字无所谓,每次改变num2的值会执行change函数,那么,现在我们每次改变输入num2的值就会重新计算sum,也就不需要点击按钮了。效果如下。

image.png 除此之外,还意外发现了num1+num2也变化了,那也就是:private声明的与@State声明的值进行操作会改变页面,触发页面刷新??

三.父子传递

页面之间的跳转携带参数我们知道是在router的params里面,那么现在,如果两个Component要传递传递参数呢?

下面先介绍一种通用的方法,也就是get set,本质上是通过一个中间人,这种方法适用于绝大部分的数据传递

首先新建一个类:

export class MyStore{
  static str:string

  static setStr(str:string){
    MyStore.str=str
  }
  static getStr(){
    return MyStore.str
  }
}

再来看set:

import { MyStore } from '../store/myStore';
import { router } from '@kit.ArkUI';

@Entry
@Component
struct GetAndSet {
  @State message: string = 'Hello World';

  build() {
    Column() {
      Button("Set").onClick(()=>{
        MyStore.setStr(this.message)
      })
      Button("Router").onClick(()=>{
        router.pushUrl({
          url:"pages/Get"
        })
      })
      Button("ReSet").onClick(()=>{
        MyStore.setStr("123")
      })
    }
    .height('100%')
    .width('100%')
  }
}

在这里,我们使用set,把message的值赋给了对象中的str

再来看get

import { MyStore } from '../store/myStore';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Get {
  @State message: string = 'Hello World';

  build() {
    Column() {
      Button("Get").onClick(()=>{
        promptAction.showToast({
          message:`${MyStore.getStr()}`
        })
        setTimeout(()=>{
          AlertDialog.show({
            message:`${MyStore.getStr()}`
          })
        },2000)
      })
    }
    .height('100%')
    .width('100%')
  }
}

在这边我们取出了str的值。

这种get/set的模式和鸿蒙的持久化存储中的首选项类似

那么有没有简单一点的?当然有!!!

@Prop父子单向传递

不多说,先上代码

@Entry
@Component
struct PropAndLink {
  @State message: string = 'Hello World';
  @State num:number=1
  build() {
    Column() {
      Column() {
        Text("我是主页面")
        Text(`num= ${this.num}`)
        Button("主页面的按钮").onClick(()=>{
          this.num++
        })
      }.height('50%')
      .width('100%')
      Column(){
        child({num:this.num})
      }.height('50%')
      .width('100%')
    }.height('100%')
    .width('100%')
  }
}

@Component
export struct child {
  @Prop num:number
  build() {
    Column() {
      Text("我是子页面")
      Text(`num= ${this.num}`)
      Button("子页面的按钮").onClick(()=>{
        this.num++
      })
    }
  }
}

在这个页面有两个component,在主页面上显示效果如下:

image.png

在两个component中,都分别有一个按钮,都是给对应的component中的num+1,

现在我们点击下面的按钮两下:

image.png

发现,只有下面的变化了,上面的不会变化,

那么我们在点击上面的按钮一下:

image.png

发现上下都变成2了,下面变成2是因为上面的变成2之后,把2传给了下面,因此下面才显示2

由此我们得出结论: @Prop修饰的变量只能父组件改变子组件,在子组件改变不影响父组件的改变。

注意:@Prop的传值方式,调用组件的时候传值,以对象的形式:像这里的

child({num:this.num})

@Link 父子双向传递

一样,先上代码:

@Entry
@Component
struct PropAndLink {
  @State message: string = 'Hello World';
  @State num: number = 1
  @State num2: number = 100

  build() {
    Column() {
      Column() {
        Text("我是主页面")
        Text(`num= ${this.num}`)
        Button("主页面的按钮1").onClick(() => {
          this.num++
        })
        Text(`num2= ${this.num2}`)
        Button("主页面的按钮12").onClick(() => {
          this.num2++
        })
      }.height('50%')
      .width('100%')

      Column() {
        child({ num: this.num, num2: this.num2 })
      }.height('50%')
      .width('100%')
    }.height('100%')
    .width('100%')
  }
}

@Component
export struct child {
  @Prop num: number
  @Link num2: number

  build() {
    Column() {
      Text("我是子页面")
      Text(`num= ${this.num}`)
      Button("子页面的按钮").onClick(() => {
        this.num++
      })
      Text(`num2= ${this.num2}`)
      Button("子页面的按钮12").onClick(() => {
        this.num2++
      })
    }
  }
}

查看效果:

image.png

我们先点击下面的按钮:

image.png

发现父组件,子组件都发生改变,

我们再点击父组件的按钮

image.png

发现也是,父组件改变,子组件也改变

因此我们得出结论: @Link修饰的变量可以在父组件改变子组件的值,也可以在子组件改变父组件的值,也就是父子双向传递

那么我们思考,有父子,有没有爷孙呢?但是本质上来说刚刚已经讲过了,也就是get/set方法,不过,我们介绍更简单方便的。

@Provide装饰器和@Consume装饰器:与后代组件双向同步

其实可不可以不用这两个,当然可以,我们给出以下例子

@Entry
@Component
struct GrandFather {
  @State message: string = 'Hello World';

  build() {
    Column() {
      Column() {
        Text("GrandFather")
      }.layoutWeight(1)

      Father().layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct Father {
  build() {
    Column() {
      Column() {
        Text("Father")
      }.layoutWeight(1)

      Son().layoutWeight(1)
    }.width("100%")
    .height("100%")
  }
}

@Component
export struct Son {
  build() {
    Column() {
      Text("Son")
    }
  }
}

这是一个最基本的爷孙类型的组件,来看页面效果:

image.png

下面我们在里面声明数据:

@Entry
@Component
struct GrandFather {
  @State message: string = 'Hello World';
  @State num: number = 1

  build() {
    Column() {
      Column() {
        Text("GrandFather")
        Text(`num= ${this.num}`)
        Button("按钮").onClick(() => {
          this.num++
        })
      }.layoutWeight(1)

      Father({ num: this.num }).layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct Father {
  @Link num: number

  build() {
    Column() {
      Column() {
        Text("Father")
        Text(`num= ${this.num}`)
      }.layoutWeight(1)

      Son({ num: this.num }).layoutWeight(1)
    }.width("100%")
    .height("100%")
  }
}

@Component
export struct Son {
  @Link num: number

  build() {
    Column() {
      Text("Son")
      Text(`num= ${this.num}`)
      Button("按钮").onClick(() => {
        this.num++
      })
    }
  }
}

我们利用father这个组件做一个桥梁,吧爷爷Grandfather和孙子Son连接起来了

效果如下:

image.png

这就是初始情况,

我们点击一下爷爷的按钮:

image.png

发现孙子的也在变化,那么我们点击孙子的按钮:

image.png

发现爷爷也变化了,其实也正常,毕竟@link是双向传递嘛

那么有没有简单易达的办法,当然有咯,那就是@Provide和@Consume

先看代码:

@Entry
@Component
struct GrandFather {
  @State message: string = 'Hello World';
  @State num: number = 1
  @Provide num2: number = 100

  build() {
    Column() {
      Column() {
        Text("GrandFather")
        Text(`num= ${this.num}`)
        Button("按钮").onClick(() => {
          this.num++
        })
        Text(`num2= ${this.num2}`)
        Button("按钮").onClick(() => {
          this.num2++
        })
      }.layoutWeight(1)

      Father({ num: this.num }).layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct Father {
  @Link num: number

  build() {
    Column() {
      Column() {
        Text("Father")
        Text(`num= ${this.num}`)
      }.layoutWeight(1)

      Son({ num: this.num }).layoutWeight(1)
    }.width("100%")
    .height("100%")
  }
}

@Component
export struct Son {
  @Link num: number
  @Consume num2: number

  build() {
    Column() {
      Text("Son")
      Text(`num= ${this.num}`)
      Button("按钮").onClick(() => {
        this.num++
      })
      Text(`num2= ${this.num2}`)
      Button("按钮").onClick(() => {
        this.num2++
      })
    }
  }
}

其实也就是在GrandFather和Son对应的加了@provide和@consume,由爷爷提供(provide)由孙子接收(consume)

下面给出初始状态,点击爷爷的按钮,点击孙子的按钮三种操作

image.png

image.png

image.png

发现和@link一样是双向绑定的

那么这个时候有同学就要问了,老师老师,你给的数据类型都太简单了,有没有复杂一点的,比如对象,看看这些能不能做到双向传递。

好,那就满足这位同学的好奇心

export interface Person {
  name: string,
  age: number
}

@Entry
@Component
struct GrandFather {
  @State message: string = 'Hello World';
  @State num: number = 1
  @Provide num2: number = 100
  @Provide person: Person = { name: "张三", age: 20 }

  build() {
    Column() {
      Column() {
        Text("GrandFather")
        Text(`num= ${this.num}`)
        Button("按钮").onClick(() => {
          this.num++
        })
        Text(`num2= ${this.num2}`)
        Button("按钮").onClick(() => {
          this.num2++
        })
        Text(`我叫${this.person.name},年龄是${this.person.age}`)
        Button("按钮").onClick(() => {
          this.person.name = "李四"
          //this.person={name:"李四",age:30}
        })
      }.layoutWeight(1)

      Father({ num: this.num }).layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct Father {
  @Link num: number

  build() {
    Column() {
      Column() {
        Text("Father")
        Text(`num= ${this.num}`)
      }.layoutWeight(1)

      Son({ num: this.num }).layoutWeight(1)
    }.width("100%")
    .height("100%")
  }
}

@Component
export struct Son {
  @Link num: number
  @Consume num2: number
  @Consume person: Person

  build() {
    Column() {
      Text("Son")
      Text(`num= ${this.num}`)
      Button("按钮").onClick(() => {
        this.num++
      })
      Text(`num2= ${this.num2}`)
      Button("按钮").onClick(() => {
        this.num2++
      })
      Text(`我叫${this.person.name},年龄是${this.person.age}`)
      Button("按钮").onClick(() => {
        this.person = { name: "王五", age: 40 }
      })
    }
  }
}

这边声明一个Person类,在爷爷组件中赋初值,然后按钮改变,传递给孙子组件,在孙子组件中也可以改变,让我们同样看看三种效果:

image.png

image.png

image.png

我们看到,这种对象也能被捕获,同样能够进行双向传递

那么复杂的数据类型呢,对象里面的某一个属性是另一个对象?

那么就是:

@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

先看代码:

class Person {
  public username: string
  public userage: number

  constructor(username: string, userage: number) {
    this.username = username;
    this.userage = userage;
  }
}

@Entry
@Component
struct ObjectLinkLayout {
  @State message: string = 'Hello World'
  @State P: Person = new Person("张三", 18);

  build() {
    Column() {
      Row() {
        Text(`多层级数据${this.P.userage}`)
      }

      MyChild5({ P: $P })


    }.width("100%")
    .height("100%")
  }
}

@Component
export default struct MyChild5 {
  @Link P: Person;
  build() {
    Row() {
      Text(`父组件的数据为${this.P.userage}`)
      Button("修改父组件数据").onClick(() => {
        this.P.userage++;
      })
    }
  }
}

这是一个最简单的父子的传递对象的,那么我们变复杂一点

@Observed
class Info {
  public info: number = 0;

  constructor(info: number) {
    this.info = info;
  }
}

@Component
struct ObjectLinkChild {
  @ObjectLink testNum: Info;

  build() {
    Text(`ObjectLinkChild testNum ${this.testNum.info}`)
      .onClick(() => {
        // 可以对ObjectLink装饰对象的属性赋值
        this.testNum.info = 47;
      })
  }
}

@Entry
@Component
struct Parent {
  @State testNum: Info[] = [new Info(1)];

  build() {
    Column() {
      Text(`Parent testNum ${this.testNum[0].info}`)
        .onClick(() => {
          this.testNum[0].info += 1;
        })

      ObjectLinkChild({ testNum: this.testNum[0] })
    }
  }
}

父组件中是一个对象数组,那么我们点击子组件对应的值会相对应的变化

image.png

说实话,感觉这个用的并不多,也就没有详细写