【HarmonyOS】ArkUI - 状态管理

在声明式 UI 中,是以状态驱动视图更新,如图1所示:
图1

图1

其中核心的概念就是状态(State)和视图(View):

  • 状态(State):指驱动视图更新的数据(被装饰器标记的变量)

    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World'
    
      build() {
        Column() {
          Text(this.message)
            .fontSize(50)
            .onClick(() => {
              this.message = 'Hello ArkTS'
            })
        }
        .width('100%')
        .height('100%')
      }
    }
    

    Index 组件里定义了 message 变量,而 message 前面就加了 @State 装饰器,如果没有这个装饰器,message 就是一个普通的变量,但是呢,正是我们给它加上了 @State 装饰器,所以,它就变成了一个状态变量。

  • 视图(View):基于 UI 描述渲染得到的用户界面

    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World'
    
      build() {
        Column() {
          Text(this.message)
            .fontSize(50)
            .onClick(() => {
              this.message = 'Hello ArkTS'
            })
        }
        .width('100%')
        .height('100%')
      }
    }
    

    build 函数内部就是 UI 的描述,我们这里就描述了一个列式的容器,容器里有一个普通的文本,文本的内容就是 message 的值,所以最终渲染出来的视图就是在屏幕上显示一个 Hello World。

视图渲染好了以后,用户就可以对视图中的页面元素产生交互,比如去触摸、点击、拖拽等事件。这些互动事件就有可能改变状态变量的值,比如说我们这个示例里,给 Text 绑定了一个点击事件,一旦用户点击,就会修改 message 的值,而在 ArkUI 的内部,有一种机制去监控状态变量的值,一旦发现发生了变更,就会触发视图的重新渲染,所以,按照我们这个示例来看,如果现在去点击这个 Hello World 文字,就会触发点击事件,修改 message 的值,把它变成 Hello ArkTS,而一旦这个变量值发生变更,视图重新渲染,于是,屏幕上显示的文字从 Hello World 变成 Hello ArkTS。

所以像这种状态视图之间相互作用的机制,我们就称之为状态管理机制。有了这种机制以后,我们将来开发的时候,不需要自己操作页面,只需要描述页面的结构,然后定义好对应的事件,在事件里面去操作状态,就可以了,这样每当用户去产生互动时,自然就会引起页面的动态刷新。所以一个动态页面就很容易的实现了。这也就是状态管理的好处。

一、@State 装饰器

  1. @State 装饰器标记的变量必须初始化,不能为空值。

    比如上面的示例代码,message 一声明,就给它初始化了一个 Hello World。

  2. @State 装饰器支持的类型是有限制的。

    支持 Object、class、string、number、boolean、enum 类型以及这些类型的数组。

    注:虽然以上这些类型都是允许的,但是有两个特殊场景:

    1. 嵌套类型:@State 修饰的变量是 Object,如果 Object 里面的属性发生了变更其实是能触发视图的更新,但是如果 Object 里面的某个属性它又是一个 Object,也就是 Object 套 Object,那就是嵌套类型,那么内部嵌套的那个 Object 它里面的属性再发生变更,就无法触发视图更新。

    2. 数组:数组中的元素不是简单类型,而是一个对象,那么对象里面的属性发生变更,同样无法触发视图更新。

二、@Prop 和 @Link 装饰器

  1. 首先看一段代码,这是实现任务统计的示例代码:

     // 任务类
     class Task {
       static id: number = 1
       // 任务名称
       name: string = `任务${Task.id++}`
       // 任务状态
       finished: boolean = false
     }
    
     // 统一的卡片样式
     @Styles function card() {
       .width('95%')
       .padding(20)
       .backgroundColor(Color.White)
       .borderRadius(15)
       .shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })
     }
    
     // 任务完成样式
     @Extend(Text) function finishedTask() {
       .decoration({ type: TextDecorationType.LineThrough })
       .fontColor('#B2B2B1')
     }
    
     @Entry
     @Component
     struct PropPage {
       // 总任务数量
       @State totalTask: number = 0
       // 已完成任务数量
       @State finishTask: number = 0
       // 任务数组
       @State tasks: Task[] = []
    
       build() {
         Column({ space: 10 }) {
           // 任务进度卡片
           Row() {
             Text('任务进度:')
               .fontSize(30)
               .fontWeight(FontWeight.Bold)
             Stack() {
               Progress({
                 value: this.finishTask,
                 total: this.totalTask,
                 type: ProgressType.Ring
               })
                 .width(100)
               Row() {
                 Text(this.finishTask.toString())
                   .fontSize(24)
                   .fontColor('#0000FF')
                 Text(' / ' + this.totalTask.toString())
                   .fontSize(24)
               }
             }
           }
           .card()
           .margin({ top: 20, bottom: 10 })
           .justifyContent(FlexAlign.SpaceEvenly)
    
           // 新增任务按钮
           Button('新增任务')
             .width(200)
             .margin({ top: 10 })
             .onClick(() => {
               // 新增任务数据
               this.tasks.push(new Task())
               // 更新任务总数量
               this.totalTask = this.tasks.length
             })
    
           // 任务列表
           List({ space: 10 }) {
             ForEach(
               this.tasks,
               (item: Task, index) => {
                 ListItem() {
                   Row() {
                     Text(item.name)
                       .fontSize(20)
                     Checkbox()
                       .select(item.finished)
                       .onChange(val => {
                         // 更新当前任务状态
                         item.finished = val
                         // 更新已完成任务数量
                         this.finishTask = this.tasks.filter(item => item.finished).length
                       })
                   }
                   .card()
                   .justifyContent(FlexAlign.SpaceBetween)
                 }
                 .swipeAction({ end: this.DeleteButton(index) })
               }
             )
           }
           .width('100%')
           .layoutWeight(1)
           .alignListItem(ListItemAlign.Center)
         }
         .width('100%')
         .height('100%')
         .backgroundColor('#F1F2F3')
       }
    
       @Builder DeleteButton(index: number) {
         Button() {
           Image($r('app.media.delete'))
             .fillColor(Color.White)
             .width(20)
         }
         .width(40)
         .height(40)
         .type(ButtonType.Circle)
         .backgroundColor(Color.Red)
         .margin(5)
         .onClick(() => {
           this.tasks.splice(index, 1)
           this.totalTask = this.tasks.length
           this.finishTask = this.tasks.filter(item => item.finished).length
         })
       }
     }
    
  2. 概念

    当父子组件之间需要数据同步时,可以使用 @Prop 和 @Link 装饰器。

    Q:什么是父子组件?什么又是数据同步

    A:看上面这段示例代码,我们会发现代码是从上到下一股脑写的,写了上百行代码,整个代码的可读性是比较差的。要解决这个问题,可以把整个功能分成几个模块,然后按模块封装成一个一个的组件,这样在入口组件(@Entry)当中就不用写太多代码,而是去引用其他模块对应的组件。整个代码结构会更加清晰,复用性也会更好。所以,入口组件就是一个父组件,它引用了其他的组件,那么这些被引用的组件就是子组件。所以这时候组件之间就出现了这种引用关系,而组件之间引用的过程中可能就会有数据传递的需求。比如在父组件里定义了一些数据,然后在子组件里需要用,这时候就需要把父组件的数据传给子组件,单纯的传递还不够,每当数据发生变更,还要去通知子组件,这就叫数据同步。数据同步利用 @State 装饰器是实现不了的,那就需要用 @Prop 和 @Link 装饰器来实现。

  3. @Prop 和 @Link 装饰器对比

    @Prop@Link
    同步类型单向同步双向同步
    允许装饰的变量类型· 父子类型一致:string、number、boolean、enum
    · 父组件是对象类型,子组件是对象属性
    · 不可以是数组、any
    · 父子类型一致:string、number、boolean、enum、object、class,以及它们的数组
    · 数组中元素增、删、替换会引起刷新
    · 嵌套类型以及数组中的对象属性无法触发视图更新
    初始化方式允许子组件初始化父组件传递,禁止子组件初始化
  4. 使用 @Prop 对示例代码进行封装和改造

    假设父组件中的变量采用 @State 装饰器,与之对应的子组件采用 @Prop 装饰器,那这时候就可以实现单项同步,当父组件对 @State 装饰的变量进行任意的修改时,就会立刻把这个数据传递给子组件,但反过来,子组件如果对这个数据进行了修改,是不会反向传递到父组件那里。所以,这种同步被称之为单向同步。实现原理就是拷贝

     ...
    
     @Entry
     @Component
     struct PropPage {
       // 总任务数量
       @State totalTask: number = 0
       // 已完成任务数量
       @State finishTask: number = 0
       // 任务数组
       @State tasks: Task[] = []
    
       build() {
         Column({ space: 10 }) {
           // 任务进度卡片
           TaskStatistics({ finishTask: this.finishTask, totalTask: this.totalTask })
    
           ...
         }
         .width('100%')
         .height('100%')
         .backgroundColor('#F1F2F3')
       }
    
       ...
     }
    
     @Component
     struct TaskStatistics {
       @Prop finishTask: number
       @Prop totalTask: number
    
       ...
     }
    
  5. 使用 @Link 对示例代码进行封装和改造

    假设父组件中的变量采用 @State 装饰器,与之对应的子组件采用 @Link 装饰器,此时就是双向同步,当父组件对 @State 装饰的变量进行任意的修改时,就会立刻把这个数据传递给子组件,反过来,子组件如果对这个数据进行了修改,也会把这个数据传递给父组件。所以,这种同步被称之为双向同步。实现原理就是引用

     ...
    
     @Entry
     @Component
     struct PropPage {
       // 总任务数量
       @State totalTask: number = 0
       // 已完成任务数量
       @State finishTask: number = 0
    
       build() {
         Column({ space: 10 }) {
           // 任务进度卡片
           ...
    
           // 任务列表
           TaskList({ finishTask: $finishTask, totalTask: $totalTask })
         }
         .width('100%')
         .height('100%')
         .backgroundColor('#F1F2F3')
       }
     }
    
     ...
    
     @Component
     struct TaskList {
       // 总任务数量
       @Link totalTask: number
       // 已完成任务数量
       @Link finishTask: number
       // 任务数组
       @State tasks: Task[] = []
    
       ...
     }
    
  6. 使用数组对示例代码进行封装和改造

     ...
    
     // 任务统计信息
     class StatisticsInfo {
       totalTask: number = 0
       finishTask: number = 0
     }
    
     @Entry
     @Component
     struct PropPage {
       // 任务统计信息
       @State info: StatisticsInfo = new StatisticsInfo()
    
       build() {
         Column({ space: 10 }) {
           // 任务进度卡片
           TaskStatistics({ finishTask: this.info.finishTask, totalTask: this.info.totalTask })
    
           // 任务列表
           TaskList({ info: $info })
         }
         .width('100%')
         .height('100%')
         .backgroundColor('#F1F2F3')
       }
     }
    
     @Component
     struct TaskStatistics {
       @Prop finishTask: number
       @Prop totalTask: number
    
       ...
     }
    
     @Component
     struct TaskList {
       @Link info: StatisticsInfo
    
       ...
     }
    

    结论:@Prop 不支持对象类型,@Link 支持对象类型。@Prop 和 @Link 该怎么选?如果子组件拿到父组件的值以后,只是用来展示,不做修改,用 @Prop,如果子组件需要修改父组件的值,用 @Link。

四、@Provide 和 @Consume

@Provide 和 @Consume 可以跨组件提供类似于 @State 和 @Link 的双向同步。

使用 @Provide@Consume 对示例代码进行封装和改造:

...

// 任务统计信息
class StatisticsInfo {
  totalTask: number = 0
  finishTask: number = 0
}

@Entry
@Component
struct PropPage {
  // 任务统计信息
  @Provide info: StatisticsInfo = new StatisticsInfo()

  build() {
    Column({ space: 10 }) {
      // 任务进度卡片
      TaskStatistics()

      // 任务列表
      TaskList()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

@Component
struct TaskStatistics {
  @Consume info: StatisticsInfo

  ...
}

@Component
struct TaskList {
  @Consume info: StatisticsInfo

  ...
}

结论:@Provide@Consume 不需要显示的传参,内部会帮你去实现,但是代价是资源上面的损耗,所以,多数情况下,能用 @State@Prop@Link 就不要用 @Provide@Consume 了,除非是跨组件那种的场景。

五、@Observed 和 @ObjectLink

作用:@Observed 和 @ObjectLink 装饰器用于在涉及嵌套对象数组元素为对象的场景中进行双向数据同步。

  1. 嵌套对象

     class Person {
       name: string
       age: number
       friend: Person
    
       constructor(name: string, age: number, friend?: Person) {
         this.name = name
         this.age = age
         this.friend = friend
       }
     }
    
     @Entry
     @Component
     struct Parent {
       @State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))
    
       build() {
         Column() {
           Text(`${this.p.friend.name} : ${this.p.friend.age},`)
             .onClick(() => this.p.friend.age++)
         }
       }
     }
    

    通过上面这两段代码可以发现 Xxx 这个对象持有了 Yyy 对象,这就是嵌套对象。利用 Text 去渲染 Xxx 的 Friend 的 name 和 age,当发生点击事件时,去修改 Yyy 的 age,但是我们知道嵌套对象它的属性变更是无法被感知到,因此就无法触发视图的更新。要解决这个问题,需要做两件事:

    (1)需要给嵌套对象它所对应的类型上面加上 @Observed 装饰器

     @Observed
     class Person {
       ...
     }
    

    (2)需要给嵌套对象内部的对象加上 @ObjectLink 装饰器

     @Component
     struct Child {
       @ObjectLink p: Person
    
       build() {
         Column() {
           Text(`${this.p.name} : ${this.p.age}`)
         }
       }
     }
    
     @Entry
     @Component
     struct Parent {
       @State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))
    
       build() {
         Column() {
           Child({ p: this.p.friend })
             .onClick(() => this.p.friend.age++)
         }
       }
     }
    
  2. 数组元素为对象

     @Observed
     class Person {
       name: string
       age: number
       friend: Person
    
       constructor(name: string, age: number, friend?: Person) {
         this.name = name
         this.age = age
         this.friend = friend
       }
     }
    
     @Component
     struct Child {
       @ObjectLink p: Person
    
       build() {
         Column() {
           Text(`${this.p.name} : ${this.p.age}`)
         }
       }
     }
    
     @Entry
     @Component
     struct Parent {
       @State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))
       @State ps: Person[] = [new Person('Aaa', 20), new Person('Bbb', 20)]
    
       build() {
         Column() {
           Child({ p: this.p.friend })
             .onClick(() => this.p.friend.age++)
           Text('==== 朋友列表 ====')
           ForEach(
             this.ps,
             p => {
               Child({ p: p }).onClick(() => p.age++)
             }
           )
         }
       }
     }
    

    只要有了 @Observed,然后传递子组件的属性时,加上 @ObjectLink,那么,也能够触发视图的更新了。

  3. 示例代码

     // 任务类
     @Observed
     class Task {
       static id: number = 1
       // 任务名称
       name: string = `任务${Task.id++}`
       // 任务状态
       finished: boolean = false
     }
    
     // 统一的卡片样式
     @Styles function card() {
       .width('95%')
       .padding(20)
       .backgroundColor(Color.White)
       .borderRadius(15)
       .shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })
     }
    
     // 任务完成样式
     @Extend(Text) function finishedTask() {
       .decoration({ type: TextDecorationType.LineThrough })
       .fontColor('#B2B2B1')
     }
    
     // 任务统计信息
     class StatisticsInfo {
       totalTask: number = 0
       finishTask: number = 0
     }
    
     @Entry
     @Component
     struct PropPage {
       // 任务统计信息
       @Provide info: StatisticsInfo = new StatisticsInfo()
    
       build() {
         Column({ space: 10 }) {
           // 任务进度卡片
           TaskStatistics()
    
           // 任务列表
           TaskList()
         }
         .width('100%')
         .height('100%')
         .backgroundColor('#F1F2F3')
       }
     }
    
     @Component
     struct TaskStatistics {
       @Consume info: StatisticsInfo
    
       build() {
         Row() {
           Text('任务进度:')
             .fontSize(30)
             .fontWeight(FontWeight.Bold)
           Stack() {
             Progress({
               value: this.info.finishTask,
               total: this.info.totalTask,
               type: ProgressType.Ring
             })
               .width(100)
             Row() {
               Text(this.info.finishTask.toString())
                 .fontSize(24)
                 .fontColor('#0000FF')
               Text(' / ' + this.info.totalTask.toString())
                 .fontSize(24)
             }
           }
         }
         .card()
         .margin({ top: 20, bottom: 10 })
         .justifyContent(FlexAlign.SpaceEvenly)
       }
     }
    
     @Component
     struct TaskList {
       @Consume info: StatisticsInfo
       // 任务数组
       @State tasks: Task[] = []
    
       handleTaskChange() {
         // 更新任务总数量
         this.info.totalTask = this.tasks.length
         // 更新已完成任务数量
         this.info.finishTask = this.tasks.filter(item => item.finished).length
       }
    
       build() {
         Column() {
           // 新增任务按钮
           Button('新增任务')
             .width(200)
             .margin({ top: 10, bottom: 10 })
             .onClick(() => {
               // 新增任务数据
               this.tasks.push(new Task())
               // 更新任务总数量
               this.handleTaskChange()
             })
    
           // 任务列表
           List({ space: 10 }) {
             ForEach(
               this.tasks,
               (item: Task, index) => {
                 ListItem() {
                   TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this) })
                 }
                 .swipeAction({ end: this.DeleteButton(index) })
               }
             )
           }
           .width('100%')
           .layoutWeight(1)
           .alignListItem(ListItemAlign.Center)
         }
       }
    
       @Builder DeleteButton(index: number) {
         Button() {
           Image($r('app.media.delete'))
             .fillColor(Color.White)
             .width(20)
         }
         .width(40)
         .height(40)
         .type(ButtonType.Circle)
         .backgroundColor(Color.Red)
         .margin(5)
         .onClick(() => {
           this.tasks.splice(index, 1)
           this.handleTaskChange()
         })
       }
     }
    
     @Component
     struct TaskItem {
       @ObjectLink item: Task
       onTaskChange: () => void
    
       build() {
         Row() {
           if (this.item.finished) {
             Text(this.item.name)
               .finishedTask()
           } else {
             Text(this.item.name)
           }
           Checkbox()
             .select(this.item.finished)
             .onChange(val => {
               // 更新当前任务状态
               this.item.finished = val
               // 更新已完成任务数量
               this.onTaskChange()
             })
         }
         .card()
         .justifyContent(FlexAlign.SpaceBetween)
       }
     }
    
  4. 运行效果,如图2所示:
    图2

    图2

  5. 总结

    @Observed 和 @ObjectLink 主要用来解决嵌套对象里面,对象属性变更无法触发数组刷新和数组里的元素式对象属性变更无法触发视图更新的问题。解决方案是给对象上面添加 @Observed 装饰器,同时给嵌套的对象或数组元素对象的变量上加 @ObjectLink 装饰器;当子组件调用父组件方法,我们的办法是把父组件的方法作为参数传递进来,但是传递过程中会有 this 的丢失,解决办法是传递这个函数过程当中,用 bind 把这个 this 绑定进去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/482684.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BI技巧丨个性化视觉对象

BOSS:那个,那个谁,最近用户反映了,说是你们做的报表不太行啊?! 白茶:(???)老板,怎么说? BOSS:就是…

pytest之统一接口请求封装

pytest之统一接口请求封装 pytest的requests_util.pyrequests_util.py 接口自动化测试框架的封装yaml文件如何实现接口关联封装yaml文件如何实现动态参数的处理yaml文件如何实现文件上传有参数化时候,怎么实现断言yaml的数据量大怎么处理接口自动化框架的扩展&#…

CSK6 接入聆思平台(LSPlatform)

一、开发环境 硬件:视觉语音大模型AI开发套件 二、使用大语言模型 官方指导文档: 开始使用 | 聆思文档中心 获取API密钥 | 聆思文档中心 1、注册 提交申请之后需要将注册电话号码通过微信发送给聆思科技工作人员,工作人员授权后&#xff…

阿里云4核16G服务器价格26.52元1个月、149.00元半年,ECS经济型e实例

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年,配置为阿里云服务器ECS经济型e实例ecs.e-c1m4.xlarge,4核16G、按固定带宽 10Mbs、100GB ESSD Entry系统盘,活动链接 aliyunfuwuqi.com/go/aliyun 活动链接打开如下图&a…

Docker搭建LNMP环境实战(02):Win10下安装VMware

实战开始,先安装 VMware 虚拟机。话不多说,上手就干! 1、基本环境检查 1.1、本机Bios是否支持虚拟化 进入:任务管理器- 性能,查看“虚拟化”是否启用,如果已启用,则满足要求,如果未…

Linux 中的vim和gdb

目录 vim命令模式(常用)nyy-----复制n次np------黏贴n次u------撤销dd-----剪切/删除$-----将光标定位到当前行结尾^-----将光标定位到最开始。gg------将光标定位文本开始shiftg-----将光标定位文件尾。nshiftg----将光标定位到第n行上下左右键:h j k l (左下上右)…

故障诊断 | 一文解决,CNN-BiLSTM卷积神经网络-双向长短期记忆神经网络组合模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,CNN-BiLSTM卷积神经网络-双向长短期记忆神经网络组合模型的故障诊断(Matlab) 模型描述 CNN-BiLSTM卷积神经网络-双向长短期记忆神经网络组合模型是一种深度学习模型,结合了卷积神经网络(CNN)和双向长短期记忆网络(BiLSTM)的优点…

【Linux】详谈命令行参数环境变量

目录 一、浅谈命令行参数 二、环境变量 2.1环境变量的内涵以及理解 2.2PATH环境变量: 2.3输入程序名就能运行我们的程序 2.4系统中的环境变量 2.5导出环境变量 三、main函数的第三个参数 3.1获得环境变量的三种方法 四、本地变量 一、浅谈命令行参数 我们的m…

ubuntu arm qt 读取execl xls表格数据

一,ubuntu linux pc编译读取xls的库 1,安装libxls(读取xls文件 电脑版) 确保你已经安装了基本的编译工具,如gcc和make。如果没有安装,可以使用以下命令安装: sudo apt-update sudo apt-get install build-essentia…

C++ vector容器类型

vector类为内置数组提供了一种替代表示&#xff0c;与string类一样 vector 类是随标准 C引入的标准库的一部分 &#xff0c;为了使用vector 我们必须包含相关的头文件 &#xff1a; #include <vector> 使用vector有两种不同的形式&#xff0c;即所谓的数组习惯和 STL习…

基于python+vue超市货品信息管理系统flask-django-php-nodejs

在此基础上&#xff0c;结合现有超市货品信息管理体系的特点&#xff0c;运用新技术&#xff0c;构建了以 python为基础的超市货品信息管理信息化管理体系。首先&#xff0c;以需求为依据&#xff0c;根据需求分析结果进行了系统的设计&#xff0c;并将其划分为管理员和用户二种…

android.os.TransactionTooLargeException解决方案,Kotlin

android.os.TransactionTooLargeException解决方案&#xff0c;Kotlin 首先&#xff0c;特意制造一个让Android发生TransactionTooLargeException的场景&#xff0c;一个Activity启动另外一个Activity&#xff0c;在Intent的Bundle里面塞入一个大的ArrayList: import android.…

鸿蒙一次开发,多端部署(十四)一多开发实例(短信)

本章从系统预置的应用中&#xff0c;选择短信应用作为典型的案例&#xff0c;从页面开发和工程结构的角度&#xff0c;介绍"一多"的具体实践。系统的产品形态在不断丰富中&#xff0c;当前主要有默认设备和平板两种产品形态&#xff0c;本章的具体实践也将围绕这两种…

react基本使用

事件处理 react事件和DOM事件 react事件原生事件onClickonclickonClick{eventListener}οnclick"eventListener()"e.preventDefalutοnclick"javascript" class ListItem extends Component {constructor(props){super(props) //子类中调用父类构造函数}…

STM32---DHT11采集与BH1750FVI光照传感器(HAL库、含源码)

写在前面&#xff1a;本节我们学习使用两个常见的传感器模块&#xff0c;分别为DHT11温湿度传感器以及BH1750FVI光照传感器,这两种传感器在对于环境监测中具有十分重要的作用&#xff0c;因为其使用简单方便&#xff0c;所以经常被用于STM32的项目之中。今天将使用分享给大家&a…

错误centos docker版本过低导致 is not a valid repository/tag: invalid reference format

文章目录 错误centos docker版本过低导致 is not a valid repository/tag: invalid reference format1、查看免费主机刚才下载的docker版本2、卸载旧版本3、安装yum依赖包4、安装镜像信息5、安装docker CE6、查看docker版本7、再次运行就成功了&#xff01;&#xff01;&#x…

科技革新背后:码垛机器人在不同领域的实践应用

随着科技的进步&#xff0c;机器人技术已经渗透到各个行业之中&#xff0c;成为提高生产效率、减少人工成本的重要工具。码垛机器人作为自动化技术的杰出代表&#xff0c;其在各个行业中的应用场景日益广泛&#xff0c;从食品饮料到化工产品&#xff0c;再到物流仓储&#xff0…

【国家计算机二级考试C语言.2024】学习备忘录

说明 分值 4060100 40分&#xff1a; 这里面有一大堆程序结果选这题&#xff0c;如果手速还可以。那遇到有疑问的情况就自己去倒计算器的ad E上面去打一打。能够跑出来&#xff0c;结果那是100%的没问题。 有些概念题比较讨厌&#xff0c;只能自己去记忆了。要去背诵熟熟的。…

取消springboot中的Test类中的日志打印

使用SpringBootTest注解进行单元测试时, 打印东西的时候,总会伴随很多的无关紧要的日志信息&#xff0c;影响观感。去掉这些日志应该怎么做呢 ? 两个步骤: 第一步、修改application.properties logging.level.rootoff logging.level.org.springframeworkoff # 关闭baner信息…

阿猪写作能用吗 #媒体#微信

阿猪写作是一个非常实用的论文写作工具&#xff0c;它不仅能帮助用户快速完成论文写作&#xff0c;还可以提供查重降重的功能&#xff0c;帮助用户确保论文的原创性。在当今社会&#xff0c;论文写作是学术界和科研领域最重要的工作之一&#xff0c;而阿猪写作的出现无疑是给这…