鸿蒙开发-组件初体验

时间:2024-10-29 19:42:50

​????个人主页:前端青山
????系列专栏:鸿蒙开发篇
????人终将被年少不可得之物困其一生

依旧青山,本期给大家带来鸿蒙开发篇专栏内容:鸿蒙开发-组件初体验

目录

1.组件简介

2 组件是啥!!!

3 UI组件库/框架:简介!!!

4 UI组件库/框架:体验cli

1 ohpm 工具包

2 OpenHarmony三方库中心仓

5.小总结

2.组件初体验

1 语法

2 封装移动端导航栏

3.组件关系

1 层级思想

2.2 封装思想!!!

3 封装移动端底部栏/标签栏

4.组件状态共享

1 场景

2 父子单向

3 父子双向

4 后代

5.小总结


1.组件简介

1 组件化开发思想

以前:每个页面对应一个.html文件,如果出现页面字体图标或者文字需要更改,工作量略大

现在:鸿蒙倡导组件化开发思想,将页面划分成一个个小的模块,也就是小的页面/组件,然后通过导入拼凑的方式组成一个完整的网页,从而便于后期维护

image.png

2 组件是啥!!!

它是鸿蒙开发中非常重要的一个思想,让我们通过独立的、或可复用的组件来构建网站,从而提高代码复用性,便于后期维护

简单来说,其实就是HTML升级后的思想,增加到导出导入

3 UI组件库/框架:简介!!!

是什么:一堆提前封装好的,项目开发常见的公共组件(指公共布局界面)

能干嘛:1-统一开发规范、页面布局 2-减少代码冗余,便于后期维护

有哪些:OpenHarmony三方库中心仓

4 UI组件库/框架:体验cli

禁止操作后面单独讲 ????????????????

1、下载 需要先准备 ohpm 工具包 准备好了 通过敲命令的方式下载

2、导入

3、使用

1 ohpm 工具包

OpenHarmony三方库中心仓

2 OpenHarmony三方库中心仓

去搜索下载

OpenHarmony三方库中心仓

import { AddressList } from "@isrc/easyui"
​
AddressList({
  addressList: [
    { "id":1,"name":"张三","tel":"13000000000","address":"浙江省杭州市西湖区文一路 138 号东方通信大厦7楼501室"}
    ,{ "id":2,"name":"李四","tel":"13100000000","address":"浙江省杭州市拱墅区莫干山路 50号"}
    ,{ "id":3,"name":"王五","tel":"13200000000","address":"浙江省杭州市滨江区江南大道13号"}
  ],
  outRangeIdArray:[5,6,7]
})

相信后期会有更全的三方库

5.小总结

组件编程开发思想:传统【一个.html文件】写代码,组件化开发【n个.ets文件】通过导入组成一个完整的网页,从而便于后期维护。

组件:就是把html代码升级的思想,并且支持导出导入

UI组件库概念:一堆提前封装好的,项目开发常见的公共组件

UI组件库使用:1-通过命令下载 略难一点 主要配置麻烦,2-导入,3-使用(可以传递不同的数据 实现不同的效果)

2.组件初体验

1 语法

.vue template style script

.ets 类似于class语法 (切记公共组件、逻辑组件不要写@Entry 也就是子组件不要写)

新建页面 LearnComponent

@Entry
@Component
struct LearnComponent {
  build() {
    // 网页 标签形式调用
    // 鸿蒙 函数调用形式
    Column() {
      HelloComponent()
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#f66')
  }
}
​
@Component
struct HelloComponent {
  build(){
    Column() {
      Text('我是一个组件')
    }
      .width('100%')
      .height(60)
      .backgroundColor('#0f0')
  }
}

细节1:使用上述语法分两步1定义、2使用

细节2:组件名大驼峰

细节3:是否加@Entry

首页    @Entry      
  传统开发  直接把布局代码全部写完   但是不方便后期维护  例如公共头部、底部等等
鸿蒙思想
头部小页面/模块/组件
轮播图小页面/模块/组件
热卖小页面/模块/组件
轮播图小页面/模块/组件
  ...
  分类      @Entry
  左侧导航组件
右侧导航数据组件
购物      @Entry
  我的      @Entry

2 封装移动端导航栏

导航栏之前布局喜欢用header、nav等考虑到避免和HTML标签冲突,以后统一叫NavBar导航栏

底部导航之前布局喜欢用footer等考虑到避免和HTML标签冲突,以后统一叫Tabbar 标签/尾部栏

参考效果:OpenHarmony三方库中心仓 主要需要分析人家在封装组件时思考的全面程度

@Entry
@Component
struct LearnComponent {
  build() {
    Column() {
      NavBar()
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#f5f5f5')
  }
}
​
@Component
struct NavBar {
  build() {
    Row(){
      Text('<').fontSize(30).fontColor('#fff')
      Text('首页').fontSize(30).fontColor('#fff').margin({ left: 120 })
      Text('')
    }
      .width('100%')
      .height(60)
      .padding({ left: 20, right: 20 })
      .backgroundColor('#f66')
  }
}

image.png

3.组件关系

1 层级思想

被调用的组件必定是子组件

image.png

2.2 封装思想!!!

实战中,组件封装至少分为 公共组件 和 逻辑/页面组件 这二个类别

公共组件价值:增加代码【复用性】,便于后期维护

一次定义 多次使用,减少代码冗余、便于后期维护 或者 提高代码的复用性等,例如NavBar导航栏、TabBar标签栏、弹框Dialog

逻辑/页面组件价值:增加代码【可读性】,便于后期维护

image.png

3 封装移动端底部栏/标签栏

navBar

Row   w100% p20  bg#000
Text size 20  color #fff
  Text size 30  color #fff  left120   
Text
Row   w100% bg#000
Text 首页
Text 分类
Text 购物车
Text 我的
​
w25%   h60  size25  color#fff red 居中
​
  点击红色
import NavBar from '../components/NavBar'
import TabBar from '../components/TabBar'
@Entry
@Component
struct LearnComponent {
  build() {
    Column() {
      NavBar()
​
      Column() {
        Text('内容')
      }
        .width('100%')
        .layoutWeight(1) // 排除了顶部以及底部之后剩余的空间
​
      TabBar()
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#f5f5f5')
  }
}

TabBar.ets

@Preview // 可以在预览器预览组件的展示效果,如果存在组件间传值,则无法看到动态效果,看到的静态效果
@Component
struct TabBar {
  build() {
    Row() {
      Text('首页')
      Text('分类')
      Text('购物车')
      Text('我的')
    }
      .width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .height(50)
      .backgroundColor('#ffff')
      .border({
        width: { top: 1 },
        color: '#000'
​
      })
  }
}
​
export default TabBar
// 定义组件
@Component
struct NavBar {
  build() {
    Row(){
      Text('<').width(50).fontSize(24).fontColor('#fff')
      Text('首页').fontSize(30).fontColor('#fff')
      Text('按钮').width(50).fontSize(24).fontColor('#fff')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround) // Row子元素主轴排列对齐方式
    .height(44)
    .backgroundColor('#f66')
  }
}
​
// 暴露组件
export default NavBar

image.png

4.组件状态共享

1 场景

圣旨:只要被调用就是子组件

圣旨:公共组件/逻辑组件 必定都是子组件

不同父调用NavBar子组件所显示的内容不一样,父得传递数据给子 也就是组件通信

image.png

image.png

2 父子单向

传递:组件名( { 名字: 数据, ...., } )

接收:@Prop 名字:string

使用: this.名字

  • 支持类型 string、number、boolean、enum 类型

  • 子组件不可以初始化默认值

  • 子组件可修改 Prop 数据值,但不同步到父组件,父组件更新后覆盖子组件 Prop 数据

父传子,父变子变,子变父不变,数据类型还受限

import NavBar from '../components/NavBar'
import TabBar from '../components/TabBar'
@Entry
@Component
struct LearnComponent {
​
  @State title: string = '首页1'
​
  build() {
    Column() {
      // 调用子组件时,通过属性形式传递
      NavBar({ title: this.title })
​
      Column() {
        Text('内容' + this.title)
        Button('点击修改标题为分类').onClick(() => { this.title = '分类' })
        Button('点击修改标题为购物车').onClick(() => { this.title = '购物车' })
      }
        .width('100%')
        .layoutWeight(1) // 排除了顶部以及底部之后剩余的空间
​
      TabBar()
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#f5f5f5')
  }
}
​
// 定义组件
@Component
struct NavBar {
  // 子组件中通过 @Prop 装饰器接收父传子 单向的数据,不需要赋值
  @Prop title: string // 此组件中则可以通过 this.title 访问
​
  build() {
    Row(){
      Text('<').width(50).fontSize(24).fontColor('#fff')
      // Text('首页').fontSize(30).fontColor('#fff')
      Text(this.title).fontSize(30).fontColor('#fff')
      Text('按钮').width(50).fontSize(24).fontColor('#fff').onClick(() => {
        this.title = '哈哈'
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround) // Row子元素主轴排列对齐方式
    .height(44)
    .backgroundColor('#f66')
  }
}
​
// 暴露组件
export default NavBar

3 父子双向

父传子,父变子变,子变父也变,数据类型基本不受限

  • 子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

  • 步骤1:父组件传值的时候需要 this. 改成 $

  • 步骤2:子组件 @Link 修饰数据

1. 当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化,如果是对象或者数组则只能观察到第一层修改的变化。
2. 类型必须被指定,且和双向绑定状态变量的类型相同
3. 不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。不支持Length、ResourceStr、ResourceColor类型,Length、ResourceStr、ResourceColor为简单类型和复杂类型的联合类型
4. 不允许初始化,装饰后为必传参数
5. 传值的时候需要加上$
6. 只能从父组件中@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰变量初始化子组件
  • 案例1:

// class Student {
//   constructor(public name: string, public money: number) {
//   }
// }
class Student {
  name: string
  money: number
  constructor(name: string, money: number) {
    this.name = name
    this.money = money
  }
}
​
@Entry
@Component
struct LearnLink {
  @State data1: number = 1
  // ts中可以使用 class 类作为数据类型
  @State data2: Student = new Student('张三', 100)
​
  build() {
    Column() {
      Text('父组件')
      Text('父组件显示data1:' + this.data1)
      Button('data1++').onClick(() => this.data1++)
      Text('父组件显示data2的money:' + this.data2.money)
      Button('data2.money++').onClick(() => this.data2.money++)
​
      Divider()
      // 双向
      // 父传子时不要使用 this.data1,使用 $data1
      // Child({ data1: this.data1 })
      Child({ data1: $data1, data2: $data2 })
    }
  }
}
​
@Component
struct Child {
​
  // 双向,子接收使用 @Link
  @Link data1: number
  @Link data2: Student
​
​
  build() {
    Column() {
      Text('子组件')
      Text('子组件显示data1:' + this.data1)
      Button('data1++').onClick(() => this.data1++)
      Text('子组件显示data2的money:' + this.data2.money)
      Button('data2.money++').onClick(() => this.data2.money++)
    }
  }
}
// class Student {
//   constructor(public name: string, public money: number) {
//   }
// }
class Student {
  name: string
  money: number
  constructor(name: string, money: number) {
    this.name = name
    this.money = money
  }
}
​
@Entry
@Component
struct LearnLink {
  @State data1: number = 1
  // ts中可以使用 class 类作为数据类型
  @State data2: Student = new Student('张三', 100)
​
  // 检测到对象的第一层数据的改变
  @State data3: { a: number, b: number } = {
    a: 1,
    b: 2
  }
​
  build() {
    Column() {
      Text('父组件')
      Text('父组件显示data1:' + this.data1)
      Button('data1++').onClick(() => this.data1++)
      Text('父组件显示data2的money:' + this.data2.money)
      Button('data2.money++').onClick(() => this.data2.money++)
​
      Text('data3.a的数据:' + this.data3.a)
​
      Divider()
      // 双向
      // 父传子时不要使用 this.data1,使用 $data1
      // Child({ data1: this.data1 })
      Child({ data1: $data1, data2: $data2, data3: $data3})
    }
  }
}
​
@Component
struct Child {
​
  // 双向,子接收使用 @Link
  @Link data1: number
  @Link data2: Student
​
  @Link data3: { a: number, b: number }
​
​
  build() {
    Column() {
      Text('子组件')
      Text('子组件显示data1:' + this.data1)
      Button('data1++').onClick(() => this.data1++)
      Text('子组件显示data2的money:' + this.data2.money)
      Button('data2.money++').onClick(() => this.data2.money++)
​
      Text('data3.a的数据:' + this.data3.a)
      Button('data3.a++').onClick(() => this.data3.a++)
    }
  }
}
  • 案例2:移动app开发骨架

import NavBar from '../components/NavBar'
import TabBar from '../components/TabBar'
@Entry
@Component
struct LearnComponent {
​
  @State title: string = '首页1'
​
  build() {
    Column() {
      // 调用子组件时,通过属性形式传递
      NavBar({ title: this.title })
​
      Column() {
        Text('内容' + this.title)
        Button('点击修改标题为分类').onClick(() => { this.title = '分类' })
        Button('点击修改标题为购物车').onClick(() => { this.title = '购物车' })
      }
        .width('100%')
        .layoutWeight(1) // 排除了顶部以及底部之后剩余的空间
​
      TabBar({ title: $title })
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#f5f5f5')
  }
}
​

涉及到传值需要注释掉 自定义组件的 @Preview 装饰器

// @Preview // 可以在预览器预览组件的展示效果,如果存在组件间传值,则无法看到动态效果,看到的静态效果
@Component
struct TabBar {
  @Link title: string
​
  @State currentIndex: number = 0
  build() {
    Row() {
      Text('首页').fontColor(this.currentIndex === 0 ? '#f66': '#666').onClick(() => {
        console.log('首页')
        this.currentIndex = 0
        this.title = '首页'
      })
      Text('分类').fontColor(this.currentIndex === 1 ? '#f66': '#666').onClick(() => {
        console.log('分类')
        this.currentIndex = 1
        this.title = '分类'
      })
      Text('购物车').fontColor(this.currentIndex === 2 ? '#f66': '#666').onClick(() => {
        console.log('购物车')
        this.currentIndex = 2
        this.title = '购物车'
      })
      Text('我的').fontColor(this.currentIndex === 3 ? '#f66': '#666').onClick(() => {
        console.log('我的')
        this.currentIndex = 3
        this.title = '我的'
      })
      // Text('首页').onClick(() => { this.title = '首页'})
      // Text('分类').onClick(() => { this.title = '分类'})
      // Text('购物车').onClick(() => { this.title = '购物车'})
      // Text('我的').onClick(() => { this.title = '我的'})
    }
      .width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .height(50)
      .backgroundColor('#ffff')
      .border({
        width: { top: 1 },
        color: '#000'
​
      })
  }
}
​
export default TabBar

4 后代

vue provide inject 祖先组件向后代组件传值,推荐配合计算属性使用,保持双向同步

react Context 传值

@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。

其中@Provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先节点提供的变量。

  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。

  • 不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量。

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

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

  • 必须指定类型。@Provide变量的@Consume变量的类型必须相同

  • 嵌套对象内部不具备响应式

@Provide name: string = 'hello'

@Consume name: string

或者

@Provide('key') name: string = 'hello'

@Consume('key') 新名字: string

双向,嵌套对象内部不具备响应式

import NavBar from '../components/NavBar'
import TabBar from '../components/TabBar'
@Entry
@Component
struct LearnProvide {
  @State title: string = '页面'
​
  @Provide gift: string = '传家宝'
​
  build() {
    Column() {
      NavBar( { title: this.title })
​
      Column() {
        Text('祖先组件:' + this.gift)
        Button('修改传家宝为夜明珠')
          .onClick(() => this.gift = '夜明珠')
        ChildTwo()
      }
      .width('100%')
      .layoutWeight(1)
​
      TabBar({ title: $title })
    }
  }
}
​
@Component
struct ChildTwo {
  build() {
    Row() {
      Text('第二级')
      ChildThree()
    }
  }
}
​
@Component
struct ChildThree {
  // @Consume('gift') 表示拿到祖先组件传递的 gift
  // @Consume('gift') gift: string
  // 拿到祖先组件的变量gift,改名为 myGIft
  @Consume('gift') myGift: string
  build() {
    Row() {
      Text('第三级')
      Text(this.myGift)
      Button('修改传家宝为瓷器')
        .onClick(() => {
          this.myGift = '瓷器'
        })
    }
  }
}

5.小总结

修饰符 支持类型 双向 场景
不写直接写名字 number/string/boolean/对象类型 / 我推荐尽量别用 不够语义化(需要默认值 小心响应式)
@Prop number/string/boolean 不行 不需要默认值推荐就用它了
@Link number/string/boolean/对象类型 可以
@Provide number/string/boolean/对象类型 可以
@ObjectLink 对象类型 可以