【鸿蒙应用开发】知识点总结(零废话无敌精简版、Stage模型)

哪些人适合看这篇笔记 ?

  • 如果你想通过这篇笔记了解鸿蒙应用开发的全貌。
  • 如果你在看鸿蒙的面试题,但是不知道从哪里开始看。
  • 如果你只是想复习一下鸿蒙的知识。
  • 如果你想简单了解一下鸿蒙应用开发的基础知识。

非常不适合看这篇笔记 

  • 试图通过该笔记彻底学会鸿蒙应用开发。
  • 试图找到开发过程中遇到问题的具体解决方案。
  • 几乎无任何基础。

相较于看官网文档的好处

官网文档的主要风格围绕 “指南” 和 “API” 进行陈述,这篇笔记主要目的是为你进一步梳理知识体系,强调 “骨架清晰” 且不废话,会着重强调某些实践过程中的坑。

指南https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/application-dev-guide-V5?catalogVersion=V5API参考https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/development-intro-api-V5?catalogVersion=V5在某些知识梯度比较大的地方,我会尽可能附上链接供你直接跳转到官网。

我会较少的出现代码和图片(因为这并不是直接教会你的文章),但我也会尽可能的使用它们来解释重点部分和难以理解的内容。

(好多代码演示部分都是直接从官网拿下来的,叠甲中 - - )

UI范式的基础

声明式的语法规则

如果熟悉ts、js等的开发人员更容易理解其基本用法。ArkTS 基本的组成部分有:

  • 装饰器 (用来定义或增强组件行为)
  • 自定义组件(允许用户来自定义组件,使得符合组件化思想,强调可复用性)
  • UI描述 (描述一个UI 的基本结构,写在build 方法中的代码块, 其中主要包含了系统组件 和 自定义组件)
  • 系统组件(Button,Row,Column等,系统提供的基本组件,帮助你构建容器或其他基础功能,开发者可以直接使用)
  • 属性方法(通过链式调用的方式为组件配置多个属性,fontSize()、width()等)
  • 事件方法(同样的也是通过链式调用的方式为组件设置事件的响应,比如 onClick 是为了响应点击事件)

UI范式基本语法https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-ui-paradigm-basic-syntax-V5

细粒度的封装和复用UI或样式 (很重要)

  • @Builder 和 BuilderParam,通过标记一个函数,来封装一些UI,使得UI可以细粒度的被封装在组件内部,最终完成复用。
  • @Extend 和 @Styles,拓展内置组件和样式的封装,前面说到了,无论是系统组件还是自定义组件。其样式都是通过链式方法调用设置的,区别于 Css 那种通过选择器绑定的方式设置样式,因此,样式的复用需要使用@Styles 来进行封装。

  • stateStyles,依据组件内部状态来应用不同样式。

自定义组件

其主要目的是为了可组合和可复用,其次通过状态的改变来驱动UI的重新渲染。

  • 使用@Component 来定义一个自定义组件
  • 编写build 函数的实现,在其内部进行UI描述(相当于编写html)
  • 在其内部定义成员变量,函数都没问题,但要定义组件受控状态需要使用 @State (该装饰器属于状态管理V1,建议选择V2)
  • 如果自定义组件作为了页面入口,需要增加@Entry
  • 如果需要调试预览组件,需要增加@Preview
  • 组件拥有生命周期,主要地: aboutToAppear (组件即将出现回调该函数,你可以在此初始化你的状态来进行预备),onDidBuild(组件build()函数执行结束回调该函数,这里只能做不影响UI 的一些事,比如埋点等)、aboutToDisappear 自定义组件在销毁之前执行。

创建自定义组件https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-create-custom-components-V5

另外在官网的自定义组件介绍中,强调了页面与组件的关系,还联系说明了页面的生命周期函数:

  • onPageShow: 页面每次显示都会触发。
  • onPageHide:页面每次隐藏都会触发。
  • onBackPress:用户按下了返回按钮会触发。

页面和自定义组件生命周期https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-page-custom-components-lifecycle-V5

这里其实还涉及一个知识点:如果你的自定义组件不是作为入口页面(没添加@Entry装饰器),但你又想要监听页面的生命周期(未增加@Entry没办法直接获得生命周期函数的执行),那么你就需要 通过 listener来实现监听。

自定义组件的拓展

自定义组件还可以有更加丰富的拓展,通过使用某些扩展装饰器来增强组件功能。

  • @Builder 装饰器:自定义构建函数 (是在组件内部维持UI复用)
  • @LocalBuilder 装饰器:维持组件父子关系,解决 函数内部的 this 指向问题。(实践下来用的频率不高)
  • @BuilderParam 装饰器:在父组件中向子组件传递UI时,子组件使用该装饰器装饰 @Builder 函数,可获得对 UI 的引用,相当于子组件内的一个 插槽。父组件传递UI时,常见的可使用尾随闭包的方式传递(在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。)
  • @Styles 装饰器:预声明一些样式的函数调用,供自定义组件直接使用(参见细粒度的封装和复用UI或样式 )。
  • @Require 装饰器:子组件可使用该装饰器来强调某一参数对于父组件来说是必须的。
  • @Reusable 装饰器:被该装饰器标记的组件在组件卸载时,会将组件放入缓存,下次创建的时候会优先从缓存复用,加快性能(面试会问到性能优化,那你就可以拿这个说)。

渲染控制 (if else forEach 等)

这部分其实挺重要的,对于一些前端过度到鸿蒙的开发者他们会经常在这部分摸不着头脑,但总体属于一看就懂的部分。

if、else 条件渲染

最好理解的一部分了,你可以直接在build 函数中使用 if else,甚至是嵌套 if(之前写react 写多了,很羡慕这种写法,因为 jsx 不在函数里的话不能这么干,都是写表达式来达到同等效果)。

@Entry
@Component
struct MainView {
  @State toggle: boolean = true;

  build() {
    Column() {
      if (this.toggle) {
        CounterView({ label: 'CounterView #positive' })
      } else {
        CounterView({ label: 'CounterView #negative' })
      }
      Button(`toggle ${this.toggle}`)
        .onClick(() => {
          this.toggle = !this.toggle;
        })
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

ForEach 循环渲染

循环渲染某些UI的时候使用,但是这里开始有些和 if 不一样的地方了,你不能直接使用  for 了(至少大部分语言是 for),你得需要ForEach (注意了,这里 F 可是大写,别掉坑里),之所以不直接使用 ts 的 for 很大概率是因为鸿蒙底层对于 ForEach 进行了性能增强,优化了非首次渲染的一些情况。

@Entry
@Component
struct Parent {
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Row() {
      Column() {
        ForEach(this.simpleList, (item: string) => {
          ChildItem({ item: item })
        }, (item: string) => item)
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ChildItem {
  @Prop item: string;

  build() {
    Text(this.item)
      .fontSize(50)
  }
}

这里其实还有些大坑,就是比如数据源发生了变化,或者数据源数组里的某个元素的属性发生了变化,如何重新渲染、拖拽排序如何解决等等问题。

直接看官网吧,这里不再赘述:ForEach:循环渲染https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-rendering-control-foreach-V5

LazyForEach 懒加载

按需迭代数据,不是一次性加载的场景,另外它具备一定的性能优化,比如当组件超出可视范围了,框架会先回收它们,降低内存的占用。

使用限制

官网介绍了很多,我这里挑几点说一下:

  1. 只能在容器组件内使用。
  2. 一个容器组件里只能有一个 LazyForEach
  3. 每次迭代,只能创建一个子组件。
  4. 你可以把 LazyForEach 写到 条件渲染(if)里。
  5. 别试图重新赋值 dataSource,你得蛹 DataChangeListener 来进行更新。
  6. 如果你想节点复用,请加上 @Reusable 装饰器。

LazyForEach 懒加载https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-rendering-control-lazyforeach-V5

状态管理

V2版本的状态管理 , 其装饰器的命名上更加语义化, 深化了其具体作用, 但是其数量上有所增加,学习难度上也比V1版本更加陡峭; 但是最好是拥抱V2 的状态管理,,官方强调它是V1的增强版本, 为开发者提供了更多的功能和灵活性。
对于新手来说,官网也强调了建议直接使用V2版本的范式来进行开发。
如果你之前是已经使用过 V1 的开发者,官方也出了迁移指导来帮助你快速过度到V2版本,猛戳:

V1V2混用和迁移指导https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-v1-v2-migration-V5另外对于V2 版本的装饰器学习,官网围绕着大量V1 版本的混用进行了说明,比如相较于V1 的限制条件等的解释,其实很难全部记起来,所以最好别混搭,V2用多了最好直接忘记V1的特性。

@Local装饰器:组件内部状态

表示内部状态,无法从外部或者说父组件进行初始化。

观察Array 等复杂类型时,你只能观察到API调用带来的变化。

观察复杂类型时的变化https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-new-local-V5#%E8%A7%82%E5%AF%9F%E5%8F%98%E5%8C%96观察基本类型时(number、boolean、string、Object、class),几乎符合你的直觉。

使用限制
  • 确保你是v2 版本的状态管理(@ComponentV2 装饰器装饰的组件,否则编译报错)
  • 不允许外部(父组件)传入初始化
@ComponentV2
struct ChildComponent {
  @Local message: string = "Hello World";
  build() {
  }
}
@ComponentV2
struct MyComponent {
  build() {
    ChildComponent({ message: "Hello" }) // 错误用法,编译时报错
  }
}

@Local 装饰器:组件内部状态https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-new-local-V5

@Param: 组件外部输入

相较于@Local 更加复杂一些,但也具有一些交叉特性。

  • 可以本地初始化,也可以从外部(父组件)初始化。
  • 组件内部不能直接修改。
  • 除了基本类型的变量,也可以接受函数作为值。
  • 变化时会刷新关联的组件(由父组件传入初始化的,父组件一旦变化了值,所有引用的组件都会刷新)
  • 与@Local 相比,它也可以直接观测 number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。
  • 在响应 Array 等内嵌类型时,也是仅能观察API调用带来的变化(上面@Local部分有它的详细官网解释)。

@Param 与 @Local 一样,对于一些类对象的观察,你仅能够观察到对象整体赋值的变化,对于类成员属性的观察你可能更需要 @ObservedV2 和 @Trace 装饰器,这里有一个例子。

class RawObject {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@ObservedV2
class ObservedObject {
  @Trace name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@Entry
@ComponentV2
struct Index {
  @Local rawObject: RawObject = new RawObject("rawObject");
  @Local observedObject: ObservedObject = new ObservedObject("observedObject");
  build() {
    Column() {
      Text(`${this.rawObject.name}`)
      Text(`${this.observedObject.name}`)
      Button("change object")
        .onClick(() => {
          // 对类对象整体的修改均能观察到
          this.rawObject = new RawObject("new rawObject");
          this.observedObject = new ObservedObject("new observedObject");
      })
      Button("change name")
        .onClick(() => {
          // @Local与@Param均不具备观察类对象属性的能力,因此对rawObject.name的修改无法观察到
          this.rawObject.name = "new rawObject name";
          // 由于ObservedObject的name属性被@Trace装饰,因此对observedObject.name的修改能被观察到
          this.observedObject.name = "new observedObject name";
      })
      Child({
        rawObject: this.rawObject,
        observedObject: this.observedObject
      })
    }
  }
}
@ComponentV2
struct Child {
  @Require @Param rawObject: RawObject;
  @Require @Param observedObject: ObservedObject;
  build() {
    Column() {
      Text(`${this.rawObject.name}`)
      Text(`${this.observedObject.name}`)
    }
  }
  
}

这里有关于@Param更多解释的官网链接:

@Param:组件外部输入https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-new-param-V5

@Once:初始化同步一次

目的是增强@Param 的仅一次初始化的能力。

  • 搭配@Param 使用,单独使用不可以。
  • 不影响@Param 的观测能力,只是为了防止被二次初始化。
  • 编写代码的顺序无关紧要,和@Param 一起使用时,它俩你随便排列。
  • 它和@Param 搭配时,变量就可以在本地修改了。

没太多限制条件,一个代码演示帮你避开所有的坑:

@ComponentV2
struct MyComponent {
  @Param @Once onceParam: string = "onceParam"; // 正确用法
  @Once onceStr: string = "Once"; // 错误用法,@Once无法单独使用
  @Local @Once onceLocal: string = "onceLocal"; // 错误用法,@Once不能与@Local一起使用
}
@Component
struct Index {
  @Once @Param onceParam: string = "onceParam"; // 错误用法
}

@Event装饰器:规范组件输出(要求父组件更新@Param变量)

子组件某些事件发生了,要求父组件重新更新传入的@Param 变量,这时候你就得用它了。

换句话说:你想更新@Param 装饰的值,但是组件自身肯定是不行的(为啥不行? 回去看下@Param),你就得在组件自身里声明@Event 标记的变量(实际值是个函数),由父组件传入,当你想要更改@Param 的值,调用一下@Event 标记的函数,父组件为该函数的调用做出响应(比如更改你想更改的@Param 的值)。!!废话有点多了这里!!

它的具体特征有:

  • 装饰的函数完全你自己决定, 可以有参数和返回值。
  • 别拿来装饰非函数类型。
  • 没初始化的话默认为空函数。
  • 外部(父组件)没初始化时,使用本地默认的函数处理。

一个代码示例向你完全解释错误的用法:

@ComponentV2
struct Index {
  @Event changeFactory: ()=>void = ()=>{}; //正确用法
  @Event message: string = "abcd"; // 错误用法,装饰非方法类型变量,@Event无作用
}
@Component
struct Index {
  @Event changeFactory: ()=>void = ()=>{}; // 错误用法,编译时报错
}

一个代码示例向你解释常见的用法:

@Entry
@ComponentV2
struct Index {
  @Local title: string = "Title One";
  @Local fontColor: Color = Color.Red;

  build() {
    Column() {
      Child({
        title: this.title,
        fontColor: this.fontColor,
        changeFactory: (type: number) => {
          if (type == 1) {
            this.title = "Title One";
            this.fontColor = Color.Red;
          } else if (type == 2) {
            this.title = "Title Two";
            this.fontColor = Color.Green;
          }
        }
      })
    }
  }
}

@ComponentV2
struct Child {
  @Param title: string = '';
  @Param fontColor: Color = Color.Black;
  @Event changeFactory: (x: number) => void = (x: number) => {};

  build() {
    Column() {
      Text(`${this.title}`)
        .fontColor(this.fontColor)
      Button("change to Title Two")
        .onClick(() => {
          this.changeFactory(2);
        })
      Button("change to Title One")
        .onClick(() => {
          this.changeFactory(1);
        })
    }
  }
}

正在疯狂的往下写- - - 

别走呀,有用的话点个赞吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值