????个人主页:前端青山
????系列专栏:鸿蒙开发篇
????人终将被年少不可得之物困其一生
依旧青山,本期给大家带来鸿蒙开发篇专栏内容:鸿蒙开发-组件初体验
目录
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文件,如果出现页面字体图标或者文字需要更改,工作量略大
现在:鸿蒙倡导组件化开发思想,将页面划分成一个个小的模块,也就是小的页面/组件,然后通过导入拼凑的方式组成一个完整的网页,从而便于后期维护
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')
}
}
3.组件关系
1 层级思想
被调用的组件必定是子组件
2.2 封装思想!!!
实战中,组件封装至少分为 公共组件 和 逻辑/页面组件 这二个类别
公共组件价值:增加代码【复用性】,便于后期维护
一次定义 多次使用,减少代码冗余、便于后期维护 或者 提高代码的复用性等,例如NavBar导航栏、TabBar标签栏、弹框Dialog
逻辑/页面组件价值:增加代码【可读性】,便于后期维护
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
4.组件状态共享
1 场景
圣旨:只要被调用就是子组件
圣旨:公共组件/逻辑组件 必定都是子组件
不同父调用NavBar子组件所显示的内容不一样,父得传递数据给子 也就是组件通信
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 | 对象类型 | 可以 |