HarmonyOS开发实例:【状态管理】

news2024/11/24 22:42:49

 状态管理

ArkUI开发框架提供了多维度的状态管理机制,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分为只读的单向传递和可变更的双向传递。如下图所示,开发框架提供了多种应用程序状态管理的能力。

2_5_1

@State修饰符

@State 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build() 方法刷新UI。 @State 状态数据具有以下特征:

  • 支持多种数据类型:允许 class 、 number 、 boolean 、 string 强类型的按值和按引用类型。允许这些强类型构成的数组,即Array<class>Array<string>Array<boolean>Array<number>。不允许 object 和 any

  • 内部私有:标记为 @State 的属性是私有变量,只能在组件内访问。

  • 支持多个实例:组件不同实例的内部状态数据独立。

  • 需要本地初始化:必须为所有 @State 变量分配初始值,将变量保持未初始化可能导致框架行为未定义,初始值需要是有意义的值,比如设置 class 类型的值为 null 就是无意义的,会导致编译报错。

  • 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定 @State 状态属性的初始值。

鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术
鸿蒙技术文档开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这

简单样例如下所示:

    @Entry @Component struct ComponentTest {

      @State date: string = "时间:" + new Date().getTime(); // data变化会触发build方法执行

      build() {
        Column({space: 10}) {

          Text(`父组件【${this.date}】`)                     // 显示时间
            .fontSize(20)
            .backgroundColor(Color.Pink)

          Item()                                            // 子组件
          Item()                                            // 子组件

          Button('更新时间')
            .onClick(() => {
              this.date = "时间:" + new Date().getTime();   // 点击按钮,date变化,会触发build方法执行
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }

    // 自定义子组件
    @Component struct Item {

      @State time: string = "时间:" + new Date().getTime();

      build() {
        Text(`子组件【${this.time}】`)
          .fontSize(20)
          .backgroundColor(Color.Grey)
          .onClick(() => {
            this.time = "时间:" + new Date().getTime();     // 点击更新时间,执行build方法
          })
      }
    }

样例运行结果如下图所示:

2_5_1_1

@Prop修饰符

开发应用知识已更新gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md参考前往。

@Prop 与 @State 有相同的语义,但初始化方式不同, @Prop 装饰的变量可以和父组件的 @State 变量建立单向的数据绑定。即 @Prop 修饰的变量必须使用其父组件提供的 @State 变量进行初始化,允许组件内部修改 @Prop 变量值但更改不会通知给父组件。 @Prop 状态数据具有以下特征:

  • 支持简单数据类型:仅支持 number 、 string 、 boolean 简单类型;

  • 内部私有:标记为 @Prop 的属性是私有变量,只能在组件内访问。

  • 支持多个实例:组件不同实例的内部状态数据独立。

  • 不支持内部初始化:在创建组件的新实例时,必须将值传递给 @Prop 修饰的变量进行初始化,不支持在组件内部进行初始化。

    简单样例如下所示:

    @Entry @Component struct ComponentTest {
    
      @State date: string = "时间:" + new Date().getTime();
    
      build() {
        Column({space: 10}) {
    
          Text(`父组件【${this.date}】`)
            .fontSize(20)
            .backgroundColor(Color.Pink)
    
          Item({time: this.date})                        // 必须初始化子组件的time字段
          Item({time: this.date})                        // 必须初始化子组件的time字段
    
          Button('更新时间')
            .onClick(() => {
              this.date = "时间:" + new Date().getTime();// 父组件的更改影响子组件
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }
    
    @Component struct Item {
    
      @Prop time: string;                                // 不允许本地初始化
    
      build() {
        Text(`子组件【${this.time}】`)
          .fontSize(20)
          .backgroundColor(Color.Grey)
          .onClick(() => {
            this.time = "时间:" + new Date().getTime();  // 子组件的更改不影响父组件
          })
      }
    
    }

    样例运行结果如下图所示:

    2_5_2_1

@Link修饰符

@Link 与 @State 有相同的语义,但初始化方式不同, @Link 装饰的变量可以和父组件的 @State 变量建立双向的数据绑定。即 @Link 修饰的变量必须使用其父组件提供的 @State 变量进行初始化,允许组件内部修改 @Link 变量值且更改会通知给父组件。 @Link 状态数据具有以下特征:

  • 支持多种数据类型: @Link 变量的值与 @State 变量的类型相同,即 class 、 number 、 string 、 boolean 或这些类型的数组。
  • 内部私有:标记为 @Link 的属性是私有变量,只能在组件内访问。
  • 支持多个实例:组件不同实例的内部状态数据独立。
  • 不支持内部初始化:在创建组件的新实例时,必须将值传递给 @Link 修饰的变量进行初始化,不支持在组件内部进行初始化。初始化使用 $ 符号,例如:$propertiesName。

样例如下:

@Entry @Component struct ComponentTest {

  @State date: string = "时间:" + new Date().getTime(); // 定义@State变量

  build() {
    Column({space: 10}) {

      Text(`父组件【${this.date}】`)
        .fontSize(20)
        .backgroundColor(Color.Pink)

      Item({time: $date})                               // 初始化子组件time属性使用$符号
      Item({time: $date})                               // 初始化子组件time属性使用$符号

      Button('更新时间')
        .onClick(() => {
          this.date = "时间:" + new Date().getTime();   // 变更date,子组件的对应属性也变化
        })
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}

@Component struct Item {

  @Link time: string;

  build() {
    Text(`子组件【${this.time}】`)
      .fontSize(20)
      .backgroundColor(Color.Grey)
      .onClick(() => {
        this.time = "时间:" + new Date().getTime();     // 变更time,父组件的对应属性也变化
      })
  }
}

样例运行结果如下图所示:

2_5_3_1

@StorageLink修饰符

@StorageLink(key) 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build() 方法进行UI刷新。组件通过使用 @StorageLink(key) 装饰的状态变量与 AppStorage 建立双向数据绑定。当创建包含 @StorageLink 的状态变量的组件时,该状态变量的值将使用 AppStorage 中的值进行初始化,在UI组件中对 @StorageLink 的状态变量所做的更改将同步到 AppStorage ,并从 AppStorage 同步到任何其他绑定实例中,如 PersistentStorage 或其他绑定的UI组件。 @StorageLink 状态数据具有以下特征:

  • 支持多种数据类型:支持的数据类型和 @State 一致且支持 object 。

  • 需要本地初始化:必须为所有 @StorageLink 变量分配初始值。

  • 数据状态全局化:使用 @StorageLink 修饰的数据变化后全局都会改变。

  • 数据持久化:通过搭配 PersistentStorage 接口实现数据持久化。

    • 绑定数据

      简单样例如下所示:

      @Entry @Component struct ComponentTest {
      
        @StorageLink('time') time: string = "1648643734154";// 使用StorageLink标记并初始化
      
        build() {
          Column({space: 10}) {
      
            Text(`父组件【${this.time}】`) // 使用time值
              .fontSize(20)
              .backgroundColor(Color.Pink)
      
            Button('更新时间')
              .onClick(() => {
                this.time = new Date().getTime().toString();// 更改time的值
              })
          }
          .width('100%')
          .height('100%')
          .padding(10)
        }
      }

运行结果如下图所示:

2_5_4_1

 
-   **双向绑定数据**

    简单样例如下所示:

    ```
    @Entry @Component struct ComponentTest {

      @StorageLink('time') time1: string = "1648643734154";
      @StorageLink('time') time2: string = "abcdefefwefwewee";

      build() {
        Column({space: 10}) {

          Text(`父组件【${this.time1}】`)
            .fontSize(20)
            .backgroundColor(Color.Pink)

          Item();
          Item();

          Button('更新时间')
            .onClick(() => {
              this.time2 = new Date().getTime().toString();
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }

    @Component struct Item {

      @StorageLink('time') time: string = "OpenHarmony";

      build() {
        Text(`子组件【${this.time}】`)
          .fontSize(20)
          .backgroundColor(Color.Grey)
          .onClick(() => {
            this.time = new Date().getTime().toString();
          })
      }
    }
    ```

运行结果如下图所示:

2_5_4_2

  • 页面间数据绑定

简单样例如下图所示:

 
 ```
    // 第一个页面
    @Entry @Component struct ComponentTest {

      @StorageLink('time') time1: string = "1648643734154";// 应用key的值以首次初始化的值为准
      @StorageLink('time') time2: string = "abcdefefwefwewee";// time2以time1的值为准

      build() {
        Column({space: 10}) {

          Text(`父组件【${this.time1}】`)
            .fontSize(20)
            .backgroundColor(Color.Pink)

          Item();// 使用自定义组件
          Item();// 使用自定义组件

          Button('更新时间')
            .onClick(() => {
              this.time2 = new Date().getTime().toString();// 更改time2的值,所有使用key的页面都会刷新
            })

          Button('跨页面数据绑定')
            .onClick(() => {
              router.push({uri: "pages/test/setting"})// 打开第二个页面
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }

    // 自定义个组件
    @Component struct Item {

      @StorageLink('time') time: string = "OpenHarmony";// time的值以key第一次出现的初始化为准

      build() {
        Text(`子组件【${this.time}】`)
          .fontSize(20)
          .backgroundColor(Color.Grey)
          .onClick(() => {
            this.time = new Date().getTime().toString();// 更改time的值,所有使用key的页面都会刷新
          })
      }
    }


    // 第二个页面
    @Entry @Component struct Setting {

      @StorageLink('time') tips: string = "我是第二个页面"; // tips的值以'key'第一次出现的为准

      build() {
        Column({space: 10}) {
          Text(this.tips) // tips的值以'key'第一次出现的为准
            .fontSize(20)
            .margin(20)
            .onClick(() => {
              this.tips = "0000000000000" // 更改tips的值,所有使用key的页面都会更新
            })

          Button('返回')
            .onClick(() => {
              router.back()// 点击返回,首页的数据会更改
            })
        }
        .width('100%')
        .height('100%')
      }
    }
    ```

运行结果如下图所示:

2_5_4_3

  • 持久化数据

@StorageLink 搭配 PersistentStorage 接口可以实现数据本地持久化,简单样例如下图所示:

 ```
    // 持久化存储key并设置默认值
    PersistentStorage.PersistProp("time", "Hello, OpenHarmony")

    @Entry @Component struct ComponentTest {

      // 初始化time1,如果AppStorage
      @StorageLink('time') time1: string = "1648643734154";
      @StorageLink('time') time2: string = "OpenHarmony";

      build() {
        Column({space: 10}) {

          Text(`父组件【${this.time1}】`)
            .fontSize(20)
            .backgroundColor(Color.Pink)

          Item();
          Item();

          Button('更新时间')
            .onClick(() => {
              this.time2 = new Date().getTime().toString();
            })

          Button('跨页面数据绑定')
            .onClick(() => {
              router.push({uri: "pages/test/setting"})
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }

    // 自定义组件
    @Component struct Item {

      @StorageLink('time') time: string = "OpenHarmony";

      build() {
        Text(`子组件【${this.time}】`)
          .fontSize(20)
          .backgroundColor(Color.Grey)
          .onClick(() => {
            this.time = new Date().getTime().toString();
          })
      }
    }
    ```

运行结果如下图所示:

2_5_4_4

@Watch修饰符

@Watch 用来监听状态变量的变化,当它修饰的状态变量发生变更时,回调相应的方式,语法结构为:

@State @Watch("function_name") count : number = 0;

上述语句表示:给状态变量 count 增加一个 @Watch 装饰器,通过 @Watch 注册一个回调方法 function_name , 当状态变量 count 被改变时, 触发 function_name 回调。

简单样例如下所示:

@Entry @Component struct WatchTest {

  @State @Watch("onBasketUpdated") shopBasket: Array<number> = [7, 12, 47, 3];
  @State totalPurchase: number = 0;

  updateTotal(): number {
    let sum = 0;
    this.shopBasket.forEach((i) => {
      sum += i;
    });
    // 计算新的购物篮总价值,如果超过100RMB,则适用折扣
    this.totalPurchase = (sum < 100) ? sum : 0.9 * sum;
    return this.totalPurchase;
  }

  onBasketUpdated(propName: string): void {
    this.updateTotal();
  }

  build() {
    Column({space: 10}) {
      Text(`${this.totalPurchase}`)
        .fontSize(30)

      Button("add to basket")
        .onClick(() => {
          this.shopBasket.push(Math.round(100 * Math.random()))
        })
    }
    .width("100%")
    .height("100%")
    .padding(10)
  }
}

样例运行结果如下图所示:

2_5_5_1

集合 shopBasket 是一个状态变量,它被 @Watch 修饰符修饰并绑定了 onBasketUpdated() 方法回调,当点击按钮往 shopBasket 里添加数据时会触发 onBasketUpdated() 方法的调用,该方法里边执行了 totalPurchase 的数据计算,最后页面刷新。

@Watch 装饰器只能监听 @State 、 @Prop 、 @Link 、 @ObjectLink 、 @Provide 、 @Consume 、 @StorageProp 以及 @StorageLink 装饰的变量。

小结

通过对ArkUI三种状态管理的介绍,可以根据具体的业务场景选择不同的状态管理模式。

鸿蒙开发岗位需要掌握那些核心要领?

目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。

自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。

废话就不多说了,接下来好好看下这份资料。

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。

针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

其中内容包含:

《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往

  1. ArkTS语言
  2. 安装DevEco Studio
  3. 运用你的第一个ArkTS应用
  4. ArkUI声明式UI开发
  5. .……

《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往

  1. Stage模型入门
  2. 网络管理
  3. 数据管理
  4. 电话服务
  5. 分布式应用开发
  6. 通知与窗口管理
  7. 多媒体技术
  8. 安全技能
  9. 任务管理
  10. WebGL
  11. 国际化开发
  12. 应用测试
  13. DFX面向未来设计
  14. 鸿蒙系统移植和裁剪定制
  15. ……

《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往

  1. ArkTS实践
  2. UIAbility应用
  3. 网络案例
  4. ……

最后

鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

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

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

相关文章

【考研数学】1800还是660还是880?

关于这几本习题册如何选择&#xff0c;肯定是根据他们的不同特点以及我们的需求结合选择&#xff0c;给大家的建议如下&#xff1a; 1800适合初期&#xff0c;可以帮助你熟悉数学公式和基础定义&#xff0c;迅速上手用。刚开始觉得难很正常&#xff0c;存在一个上手的过程&…

VRRP虚拟路由实验(思科)

一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器共享一个虚拟IP地址&#xff0c;其中一台路由器被选为Master&#xff0c;负…

【Erlang】【RabbitMQ】Linux(CentOS7)安装Erlang和RabbitMQ

一、系统环境 查版本对应&#xff0c;CentOS-7&#xff0c;选择Erlang 23.3.4&#xff0c;RabbitMQ 3.9.16 二、操作步骤 安装 Erlang repository curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash安装 Erlang package s…

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开

扫描IP开放端口该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开 #/bin/bash #该脚本用于对特定目标主机进行常见端口扫描(加载端口字典)或者指定端口扫描,判断目标主机开放来哪些端口 #用telnet方式 IP$1 #IP119.254.3.28 #获得IP的前…

【STL】顺序容器与容器适配器

文章目录 1顺序容器概述1.1array1.2forward_list1.3deque 2.如何确定使用哪种顺序容器呢&#xff1f;3.容器适配器的概念4.如何定义适配器呢&#xff1f; 1顺序容器概述 给出以下顺序容器表&#xff1a; 顺序容器类型作用vector可变大小的数组&#xff0c;支持快速访问&#…

UML学习

UML(Unified Modeling Language)&#xff1a;统一建模语言&#xff0c;提供了一套符号和规则来帮助分析师和设计师表达系统的架构、行为和交互 类图&#xff1a;描绘类、接口之间的关系(继承、实现、关联、依赖等)以及类的内部结构(属性和方法)&#xff0c;直观展现系统的静态…

2024年3月电子学会青少年软件编程 中小学生Python编程等级考试一级真题解析(判断题)

2024年3月Python编程等级考试一级真题解析 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、turtle 画布的坐标系原点是在画布的左上角 答案&#xff1a;错 考点分析&#xff1a;考查turtle相关知识&#xff0c;turtle画布坐标系是在画布的…

c# wpf LiveCharts 饼图 简单试验

1.概要 c# wpf LiveCharts 饼图 简单试验 2.代码 <Window x:Class"WpfApp3.Window5"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…

openharmony launcher 调研笔记(03)UI 数据装配

最近在看launcher&#xff0c;把自己调研的点做个笔记&#xff0c;持续修改更新中&#xff0c;个人笔记酌情参考。 桌面上半部分包含父子逻辑&#xff1a; Column() { PageDesktopLayout(); } PageDesktopLayout->GridSwiper->Swiper->SwiperPage 1.PageDe…

即插即用篇 | RTDETR引入Haar小波下采样 | 一种简单而有效的语义分割下采样模块

本改进已集成到 RT-DETR-Magic 框架。 下采样操作如最大池化或步幅卷积在卷积神经网络(CNNs)中被广泛应用,用于聚合局部特征、扩大感受野并减少计算负担。然而,对于语义分割任务,对局部邻域的特征进行池化可能导致重要的空间信息丢失,这有助于逐像素预测。为了解决这个问…

Collection与数据结构 二叉树(一):二叉树的性质与基本操作

1. 树形结构 1.1 概念1 (了解) 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#…

已解决:windows 下无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本

目录 1&#xff0c;问题描述2&#xff0c;问题解决 1&#xff0c;问题描述 当通过 npm 全局安装依赖后&#xff08;比如 ts 对应的 tsc 命令&#xff0c;还有 pnpm&#xff09;&#xff0c;想直接使用安装的命令&#xff0c;就会报错&#xff1a; 2&#xff0c;问题解决 以管…

《QT实用小工具·二十二》多种样式导航按钮控件

1、概述 源码放在文章末尾 该项目实现了多种样式的导航按钮控件 可设置文字的左侧、右侧、顶部、底部间隔。 可设置文字对齐方式。 可设置显示倒三角、倒三角边长、倒三角位置、倒三角颜色。 可设置显示图标、图标间隔、图标尺寸、正常状态图标、悬停状态图标、选中状态图标…

P5356 [Ynoi2017] 由乃打扑克

我手把手教她打扑克 qwq 综合分析一下2个操作&#xff0c;查找区间第k小的值&#xff0c;感觉可以用主席树&#xff0c;区间修改那没事了 考虑分块做法,块长B 分析第一个操作 只需要维护数列的单调性&#xff0c;然后二分答案上二分就ok了 分析第二个操作 维护一个加法懒…

比较好玩的车子 高尔夫6

https://www.sohu.com/a/484063087_221273 四万多如愿收获手动挡高尔夫6&#xff0c;可靠性、经济性、操控性兼顾_搜狐汽车_搜狐网 2.基本上其他人也不知道到底是什么相关的车子信息

HarmonyOS 开发-自定义视图实现Tab效果

介绍 本示例介绍使用Text、List等组件&#xff0c;添加点击事件onclick,动画&#xff0c;animationTo实现自定义Tab效果。 效果预览图 使用说明 点击页签进行切换&#xff0c;选中态页签字体放大加粗&#xff0c;颜色由灰变黑&#xff0c;起到强调作用&#xff0c;同时&…

鸿蒙HarmonyOS开发实例:【简单时钟】

简单时钟 介绍 本示例通过使用[ohos.display]接口以及Canvas组件来实现一个简单的时钟应用。 效果预览 主页 使用说明 1.界面通过setInterval实现周期性实时刷新时间&#xff0c;使用Canvas绘制时钟&#xff0c;指针旋转角度通过计算得出。 例如&#xff1a;"2 * M…

第十课 Excel

最上方标题栏&#xff1a; 显示共工作薄名称&#xff0c;如果显示兼容模式是没有办法使用高级功能的。分辨高版本和低版本可以通过后缀名进行分辨&#xff1b;显示xlsx就是高版本工作薄&#xff0c;如果显示xls的话就是低版本工作薄了。如果同事老板都使用的是低版本的话我们发…

【攻防世界】Web_python_template_injection

{{}}是变量包裹标识符&#xff0c;里面存放的是一个变量&#xff0c;当你输入 http://61.147.171.105:55121/{{8*8}} 执行成功&#xff0c;说明存在模版注入。接下来&#xff0c;开始想办法编代码拿到服务器的控制台权限 。 首先&#xff0c;题目告诉我们这是一个 python 注入…

机器学习(五) -- 监督学习(2) -- k近邻

系列文章目录及链接 目录 前言 一、K近邻通俗理解及定义 二、原理理解及公式 1、距离度量 四、接口实现 1、鸢尾花数据集介绍 2、API 3、流程 3.1、获取数据 3.2、数据预处理 3.3、特征工程 3.4、knn模型训练 3.5、模型评估 3.6、结果预测 4、超参数搜索-网格搜…