【HarmonyOS】ArkUI - 状态管理

news2024/11/17 14:33:13

在声明式 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 绑定进去。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux安装Nginx及配置TCP负载均衡

目录 1、安装编译工具及库文件2、下载解压Nginx压缩包3、Ngnix配置Tcp负载均衡4、配置Ngnix的文件5、Nginx启动 1、安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel pcre-devel2、下载解压Nginx压缩包 wget https://nginx.o…

腾讯云服务器如何购买?图文全流程,2024最新整理

腾讯云服务器购买流程很简单,有两种购买方式,直接在官方活动上购买比较划算,在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵,但是自定义购买云服务器CPU内存带宽配置选择范围广,活动上购买只能选择固定的活动…

“架构(Architecture)” 一词的定义演变历史(依据国际标准)

深入理解“架构”的客观含义,不仅能使IT行业的系统架构设计师提升思想境界,对每一个积极的社会行动者而言,也具有长远的现实意义,因为,“架构”一词,不只限于IT系统,而是指各类系统(包括社会系统…

ClickHouse部署安装

准备工作 确定防火墙处于关闭状态 CentOS取消打开文件数限制 在hadoop102的 /etc/security/limits.conf文件的末尾加入以下内容 注意:以下操作会修改 Linux 系统配置,如果操作不当可能导致虚拟机无法启动,建议在执行以下操作之前给…

鸿蒙一次开发,多端部署(十五)常见问题

如何查询设备类型 设备类型分为default(默认设备)、tablet、tv、wearable、2in1等,有多种查询设备类型的方式。 通过命令行的方式查询设备类型。 通过命令行查询指定系统参数(const.product.devicetype)进而确定设备…

手撕算法-三数之和

描述 分析 排序双指针直接看代码。 代码 public static List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);List<List<Integer>> res new ArrayList<>();for(int k 0; k < nums.length - 2; k){if(nums[k] > 0) break; …

pandas的综合练习

事先说明&#xff1a; 由于每次都要导入库和处理中文乱码问题&#xff0c;我都是在最前面先写好&#xff0c;后面的代码就不在写了。要是copy到自己本地的话&#xff0c;就要把下面的代码也copy下。 # 准备工作import pandas as pd import numpy as np from matplotlib impor…

【Java程序设计】【C00341】基于Springboot的药品管理系统(有论文)

基于Springboot的药品管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 &#x1f345;文末点击卡片获取源码&#x1f345; 开发环境 运行环境&#xff1a;推荐jdk1.8&#xff1b; 开发工具&#xff1a;eclipse以及idea&…

修复公众号订阅消息改版金媒v10.3_v10.4和奥壹oelove 10.1处理方式有不同

首先我截图让大家看下自从微信官方弃用历史消息模板改用订阅号消息模板后&#xff0c;两个厂家是怎么针对程序修复的&#xff01; 一.金媒v10.3_v10.4 列出了类目标注了说明&#xff0c;然后参数需要自己申请和对接&#xff0c;对于新手来说一头雾水比较懵&#xff0c;也就是说…

②零基础MySQL数据库-MySQL约束

作用 表在设计的时候加入约束的目的就是为了保证表中的记录完整性和有效性&#xff0c;比如用户表有些列的值&#xff08;手机号&#xff09;不能为空&#xff0c;有些列的值&#xff08;身份证号&#xff09;不能重复 分类 主键约束(primary key) PK 自增长约束(auto_increme…

LabVIEW比例流量阀自动测试系统

LabVIEW比例流量阀自动测试系统 开发了一套基于LabVIEW编程和PLC控制的比例流量阀自动测试系统。通过引入改进的FCMAC算法至测试回路的压力控制系统&#xff0c;有效提升了压力控制效果&#xff0c;展现了系统的设计理念和实现方法。 项目背景&#xff1a; 比例流量阀在液压…

docker desktop 登录不上账号

配置走代理&#xff08;系统全局&#xff09;也没用 解决方法 参考博文&#xff1a; https://blog.csdn.net/weixin_37477009/article/details/135797296 https://adoyle.me/Today-I-Learned/docker/docker-desktop.html 下载 Proxifiler 配置 Proxifiler

使用 Web Components 实现输入法更换皮肤 (vue)

更换皮肤 (界面外观) 是拼音输入法的常见功能. 要实现更换皮肤, 有许多种不同的具体技术方案可以使用. 本文选择 Web Components 技术 (vue) 来实现这个功能. 目录 1 效果展示 1.1 发布新版本 2 Web Components 简介3 vue 使用 Web Components 3.1 使用 vue 实现 Web Compon…

软件测试 -- Selenium常用API全面解答(java)

写在前面 // 如果文章有问题的地方, 欢迎评论区或者私信指正 目录 什么是Selenium 一个简单的用例 元素定位 id定位 xpath定位 name定位 tag name 定位和class name 定位 操作元素 click send_keys submit text getAttribute 添加等待 显示等待 隐式等待 显示等…

立体统计图表绘制方法(分离式环图)

立体统计图表绘制方法&#xff08;分离式环形图&#xff09; 记得我学统计学的时候&#xff0c;那些统计图表大都是平面的框框图&#xff0c;很呆板&#xff0c;就只是表现出统计的意义就好了。在网络科技发展进步的当下&#xff0c;原来一些传统的统计图表都有了进一步的创新。…

uni-app从零开始快速入门

教程介绍 跨端框架uni-app作为新起之秀&#xff0c;在不到两年的时间内&#xff0c;迅速被广大开发者青睐和推崇&#xff0c;得益于它颠覆性的优势“快”&#xff0c;快到可以节省7套代码。本课程由uni-app开发者团队成员亲授&#xff0c;带领大家无障碍快速掌握完整的uni-app…

【微服务】Gateway服务网关

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;微服务 ⛺️稳中求进&#xff0c;晒太阳 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响…

Spring Boot从入门到实战

课程介绍 本课程从SpringBoot的最基础的安装、配置开始到SpringBoot的日志管理、Web业务开发、数据存储、数据缓存&#xff0c;安全控制及相关企业级应用&#xff0c;全程案例贯穿&#xff0c;案例每一步的都会讲解实现思路&#xff0c;全程手敲代码实现。让你不仅能够掌Sprin…

七种查找方式(Java)

一、基本查找 也叫做顺序查找 说明&#xff1a;顺序查找适合于存储结构为数组或者链表。 基本思想&#xff1a;顺序查找也称为线形查找&#xff0c;属于无序查找算法。从数据结构线的一端开始&#xff0c;顺序扫描&#xff0c;依次将遍历到的结点与要查找的值相比较&#xff…

本人用编译

板子方 修改ip&#xff08;保证板子和主机在同一个网段&#xff09; mount -t nfs -o rw,nolock,nfsvers3 192.168.1.200:/home/violet/nfs get/ 互通的文件在~目录下get文件内 电脑方 使用arm-linux-gnueabihf-gcc 编译