CPU都被干冒烟了,拥抱HarmonyOS第二天,自定义组件(下)

简介: CPU都被干冒烟了,拥抱HarmonyOS第二天,自定义组件

也可以传入状态变量作为参数,当状态改变时,UI 可以正常刷新渲染

@Extend(Text) 
function fancy(fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
  @State fontSizeValue: number = 20
  build() {
    Row({ space: 10 }) {
      Text('Fancy')
        .fancy(this.fontSizeValue)
        .onClick(() => {
          this.fontSizeValue = 30
        })
    }
  }
}

我们可以使用 @Extend 对下面的代码进行简化

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'
  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(100)
        .backgroundColor(Color.Blue)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(200)
        .backgroundColor(Color.Pink)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(300)
        .backgroundColor(Color.Orange)
    }.margin('20%')
  }
}

首先使用 @Extend 抽离 Text 的样式

@Extend(Text) 
function fancy(w: number, color: Color) {
  .fontStyle(FontStyle.Italic)
  .fontWeight(w)
  .backgroundColor(color)
}

然后在组件中使用他

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'
  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fancy(100, Color.Blue)
      Text(`${this.label}`)
        .fancy(200, Color.Pink)
      Text(`${this.label}`)
        .fancy(300, Color.Orange)
    }.margin('20%')
  }
}


stateStyles


类似于 css 的伪类,可以设置组件在不同状态时的样式,arkUI 提供了如下四种状态


  • normal 正常状态
  • focused 获得焦点
  • pressed 按下状态
  • disabled 禁用状态


使用方式如下

@Entry
@Component
struct StateStylesSample {
  build() {
    Column() {
      Button('Click me')
        .stateStyles({
          focused: {
            .backgroundColor(Color.Pink)
          },
          pressed: {
            .backgroundColor(Color.Black)
          },
          normal: {
            .backgroundColor(Color.Yellow)
          }
        })
    }.margin('30%')
  }
}


02自定义组件


自定义组件是逻辑复用的重要手段。最基本结构如下

@Component
struct MyComponent {
  build() {
    Text('hello world!')
  }
}

组件封装好之后,使用时只能用如下方式传参

MyComponent({ name: 'world' })

传入的参数中,key 值 name 会覆盖在组件内部定义的同名属性

@Component
struct MyComponent {
  private name = 'china'
  build() {
    Text(`hello ${this.name}!`)
  }
}

自定义组件的导出和引用,与 TS 模块的语法是一致的,这里不在扩展冗余介绍


03状态


和 React/Vue 一样,arkUI 也是基于数据驱动 UI 的核心思想来设计。不过 arkUI 中的数据状态非常不一样,它有更复杂的机制和逻辑


arkUI 中将会影响 UI 的数据称之为状态,他们常常需要特定的装饰器来声明


@State


先来实现一个经典的 count 案例

@Entry
@Component
struct MyComponent {
  @State
  private count: number = 0
  build() {
    Column() {
      Text(`hello ${this.count}!`)
      Button('++++')
      .onClick(() => this.count++)
    }
  }
}

@State 支持如下强类型的按值和按引用类型,及这些强类型构成的数组


  • class 、 Array<class>
  • number 、Array<number>
  • boolean 、Array<boolean>
  • string 、Array<string>
  • object 、Array<object>


不支持 any,不支持简单类型和复杂类型的联合类型,不允许使用 undefined 和 null


建议不要装饰 Date 类型,应用可能会产生异常行为。不支持 Length、ResourceStr、ResourceColor 类型,Length、ResourceStr、ResourceColor 为简单类型和复杂类型的联合类型。


@State 装饰的属性只能在组件内部访问,子组件也不能访问


讲道理,规则有点多,用的时候再说吧,如果用错了,也会报提示,也不用刻意去记


这里需要特别注意的是,@State 只能观察监听到数据的浅层「第一层」。无法观测到更深层次的数据变化,因此层级结构复杂的数据类型的变化无法使用 @State 监听到完整的数据变化


嵌套类对象的属性变化需要使用 @Observed 与 @ObjectLink 来观测数据的变化,具体的使用我们后面介绍


@prop


如果我们将父组件中,@State 定义的状态传递给子组件,默认情况下,父组件只会将当前的值传递子组件用于初始化,后续父组件的变化则与子组件无关


例如我们定义这样一个子组件

@Component
struct ChildComponent {
  private count: number
  build() {
    Text(`Child Count: ${this.count}}`)
  }
}

然后再父组件中,将 @State count 传递给子组件

@Entry
@Component
struct MyComponent {
  @State
  private count: number = 0
  build() {
    Column() {
      Text(`hello world ${this.count}!`)
      ChildComponent({ count: this.count })
      Button('++++')
      .onClick(() => this.count++)
    }
  }
}

当 count 发生变化时,子组件不会跟着变化。如果我们想要子组件的状态与父组件建立绑定关系,则可以在子组件中,使用 @Prop 装饰 count,这样一个单向的绑定关系就建立成功了


  • 单向关系表现为:
  • 父组件中修改 count,子组件会同步更新
  • 子组件中修改 count,父组件不会有反应
  • 子组件更新后,父组件再更新,子组件中的状态会被父组件最新的值覆盖


因此,在子组件中,给 count 字段添加一个 @Prop 装饰即可

@Component
struct ChildComponent {
  @Prop
  private count: number
  build() {
    Text(`Child Count: ${this.count}}`)
  }
}

当作为子组件时,@Prop 可以被父组件中的其他任意装饰器状态初始化。


当作为父组件时,@Prop 可以初始化子组件的常规变量、@State、@Link、@Prop、Provide


@Prop 装饰的变量是私有的,只能在组件内部访问


@Link


如果你想要和子组件建立双向绑定的关系,则需要使用 @Link


  • 双向关系表现为:
  • 父组件中修改 count,子组件会同步更新
  • 子组件中修改 count,父组件会同步更新
  • 子组件不能初始化,只能接收父组件的参数初始化
  • 父组件必须以按引用传递的方式传参


子组件代码,使用 @Link 装饰状态

@Component
struct ChildComponent {
  @Link
  private count: number
  build() {
    Column() {
      Text(`Child Count: ${this.count}}`)
      Button('ChildCount')
        .onClick(() => this.count++)
    }
  }
}

父组件代码,按引用传参

@Entry
@Component
struct MyComponent {
  @State
  private count: number = 0
  build() {
    Column() {
      Text(`hello world ${this.count}!`)
      ChildComponent({ count: $count })
      Button('++++').onClick(() => this.count++)
    }
  }
}

其实学习到这里,我已经逐渐有点裂开了。这规则也太多了吧 ~ 别急,还有一点,@Link 只能与父组件的 @State Link StorageLink 建立双向绑定关系


@Provide 与 @Consume


类似于 React 中的 context,用于跨组件层级传递参数。其中 @Provide 作用于约定范围内的根节点,@Consume 作用于后代组件中,他们之间的关系是双向绑定


这两个知识点的使用反而简单,看如下案例即可

@Entry
@Component
struct MyComponent {
  @Provide
  private count: number = 0
  build() {
    Column() {
      Text(`hello world ${this.count}!`)
      ChildComponent({ count: $count })
      Button('++++')
        .onClick(() => this.count++)
    }
  }
}
@Component
struct ChildComponent {
  @Link
  private count: number
  build() {
    Column() {
      Text(`Child Count: ${this.count}}`)
      D()
    }
  }
}
@Component
struct D {
  @Consume
  private count: number
  build() {
    Button('我在深层子组件')
      .onClick(() => this.count++)
  }
}


@Observed 与 @ObjectLink


上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed @ObjectLink装饰器


对他们使用主要步骤如下


  • 父组件中,使用 @Observed 装饰的 class 对象初始化 @State 变量
  • 子组件中,使用 @ObjectLink 接收父组件传递过来的参数


示例如下,首先使用 @Observed 定义复杂数据结构的对象

// objectLinkNestedObjects.ets
let NextID: number = 1;
@Observed
class ClassA {
  public id: number;
  public c: number;
  constructor(c: number) {
    this.id = NextID++;
    this.c = c;
  }
}
@Observed
class ClassB {
  public a: ClassA;
  constructor(a: ClassA) {
    this.a = a;
  }
}

然后在父组件中,使用刚才定义的复杂对象初始化 @State

@State b: ClassB = new ClassB(new ClassA(0));

然后在子组件中,使用 @ObjectLink 接收参数

@Component
struct ViewA {
  label: string = 'ViewA1';
  @ObjectLink a: ClassA;
  build() {
    Row() {
      Button(`ViewA [${this.label}] this.a.c=${this.a.c} +1`)
        .onClick(() => {
          this.a.c += 1;
        })
    }
  }
}

父组件完整代码如下,包括状态初始化,参数传递

@Entry
@Component
struct ViewB {
  @State b: ClassB = new ClassB(new ClassA(0));
  build() {
    Column() {
      ViewA({ label: 'ViewA #1', a: this.b.a })
      ViewA({ label: 'ViewA #2', a: this.b.a })
      Button(`ViewB: this.b.a.c+= 1`)
        .onClick(() => {
          this.b.a.c += 1;
        })
      Button(`ViewB: this.b.a = new ClassA(0)`)
        .onClick(() => {
          this.b.a = new ClassA(0);
        })
      Button(`ViewB: this.b = new ClassB(ClassA(0))`)
        .onClick(() => {
          this.b = new ClassB(new ClassA(0));
        })
    }
  }
}


04总结


学习相关内容只用了一天,但是写这篇文章就用了三天时间 ~ ~ 因为官方文档的内容有点零散,关于自定义组件的内容分布在了几个不同的地方,因此为了确保每一个表达的准确性,反复翻阅文档和写代码验证花费了不少时间,真不是一个轻松的过程


不过写这篇文章本身也是一个总结的过程,让我对自定义组件的相关内容有了更深刻的理解。整体感受下来就是 arkUI 对于状态的区分更为细化,因此在实践中要结合具体情况选择合适的状态,就不得不对这些状态的基本情况有比较详细的了解


除了能够熟练使用之外,官方文档对于内部逻辑的运行机制都分别做了介绍,这可以作为一个进阶内容在后续的过程中学习,不过如果你理解 React 和 Vue 的底层原理的话,大概也能猜到他是如何实现的


虽然我学得挺快的,不过可以预想,对于零基础的同学来说,arkUI 的学习成本非常高,要掌握的概念和细节很多,写这篇文章我感觉自己都被干冒烟了,本来预计还有很大一部分内容要写,放到下一篇文章里来说吧

相关文章
|
2天前
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
本篇将带你实现一个自定义天气预报组件。用户可以通过选择不同城市来获取相应的天气信息,页面会显示当前城市的天气图标、温度及天气描述。这一功能适合用于动态展示天气信息的小型应用。
73 38
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
|
29天前
|
前端开发 搜索推荐 开发者
「Mac畅玩鸿蒙与硬件20」鸿蒙UI组件篇10 - Canvas 组件自定义绘图
Canvas 组件在鸿蒙应用中用于绘制自定义图形,提供丰富的绘制功能和灵活的定制能力。通过 Canvas,可以创建矩形、圆形、路径、文本等基础图形,为鸿蒙应用增添个性化的视觉效果。本篇将介绍 Canvas 组件的基础操作,涵盖绘制矩形、圆形、路径和文本的实例。
63 12
「Mac畅玩鸿蒙与硬件20」鸿蒙UI组件篇10 - Canvas 组件自定义绘图
|
29天前
|
搜索推荐 前端开发 开发者
「Mac畅玩鸿蒙与硬件19」鸿蒙UI组件篇9 - 自定义动画实现
自定义动画让开发者可以设计更加个性化和复杂的动画效果,适合表现独特的界面元素。鸿蒙提供了丰富的工具,支持通过自定义路径和时间控制来创建复杂的动画运动。本篇将带你学习如何通过自定义动画实现更多样化的效果。
72 11
「Mac畅玩鸿蒙与硬件19」鸿蒙UI组件篇9 - 自定义动画实现
|
29天前
|
UED 开发者
「Mac畅玩鸿蒙与硬件18」鸿蒙UI组件篇8 - 高级动画效果与缓动控制
高级动画可以显著提升用户体验,为应用界面带来更流畅的视觉效果。本篇将深入介绍鸿蒙框架的高级动画,包括弹性动画、透明度渐变和旋转缩放组合动画等示例。
63 12
「Mac畅玩鸿蒙与硬件18」鸿蒙UI组件篇8 - 高级动画效果与缓动控制
|
25天前
|
UED
「Mac畅玩鸿蒙与硬件31」UI互动应用篇8 - 自定义评分星级组件
本篇将带你实现一个自定义评分星级组件,用户可以通过点击星星进行评分,并实时显示评分结果。为了让界面更具吸引力,我们还将添加一只小猫图片作为评分的背景装饰。
63 6
「Mac畅玩鸿蒙与硬件31」UI互动应用篇8 - 自定义评分星级组件
|
29天前
|
UED
「Mac畅玩鸿蒙与硬件17」鸿蒙UI组件篇7 - Animation 组件基础
在应用开发中,动画效果可以增强用户体验。鸿蒙框架提供了 translate、scale 和 rotate 等动画功能,允许对组件进行平移、缩放和旋转等操作。本篇将介绍 Animation 组件的基础知识和示例代码。
77 10
「Mac畅玩鸿蒙与硬件17」鸿蒙UI组件篇7 - Animation 组件基础
|
27天前
|
前端开发 开发者
「Mac畅玩鸿蒙与硬件21」鸿蒙UI组件篇11 - Canvas 组件的静态进阶应用
在鸿蒙应用开发中,Canvas 组件不仅用于基础绘图,还提供了处理复杂路径和渐变效果的多种手段,帮助开发者实现精美的静态图形。本篇将介绍如何在 Canvas 中绘制复杂路径、创建渐变填充效果。
45 7
「Mac畅玩鸿蒙与硬件21」鸿蒙UI组件篇11 - Canvas 组件的静态进阶应用
|
29天前
|
大数据 UED
「Mac畅玩鸿蒙与硬件16」鸿蒙UI组件篇6 - List 和 Grid 组件展示数据列表
List 和 Grid 是鸿蒙开发中的核心组件,用于展示动态数据。List 适合展示垂直或水平排列的数据列表,而 Grid 则适用于展示商品或图片的网格布局。本篇将展示如何封装组件,并通过按钮实现布局切换,提升界面的灵活性和用户体验。
63 9
「Mac畅玩鸿蒙与硬件16」鸿蒙UI组件篇6 - List 和 Grid 组件展示数据列表
|
27天前
|
前端开发 开发者
「Mac畅玩鸿蒙与硬件22」鸿蒙UI组件篇12 - Canvas 组件的动态进阶应用
在鸿蒙应用中,Canvas 组件可以实现丰富的动态效果,适合用于动画和实时更新的场景。本篇将介绍如何在 Canvas 中实现动画循环、动态进度条、旋转和缩放动画,以及性能优化策略。
45 6
「Mac畅玩鸿蒙与硬件22」鸿蒙UI组件篇12 - Canvas 组件的动态进阶应用
|
27天前
|
前端开发 开发者
「Mac畅玩鸿蒙与硬件23」鸿蒙UI组件篇13 - 自定义组件的创建与使用
自定义组件可以帮助开发者实现复用性强、逻辑清晰的界面模块。通过自定义组件,鸿蒙应用能够提高代码的可维护性,并简化复杂布局的构建。本篇将介绍如何创建自定义组件,如何向组件传递数据,以及如何在不同页面间复用这些组件。
36 5
「Mac畅玩鸿蒙与硬件23」鸿蒙UI组件篇13 - 自定义组件的创建与使用