系列文章目录
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(下)
系列文章目录2
HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)
HarmonyOS Next系列之实现一个左右露出中间大两边小带缩放动画的轮播图(十二)
HarmonyOS Next系列之水波纹动画特效实现(十三)
文章目录
前言
HarmonyOS NEXT(鸿蒙应用)开发快速入门教程ArkTS语法之装饰器篇(下),基于HarmonyOS NEXT Beta1版本(api 12)讲解。
一、装饰器语法
6.@Builder
自定义组件除了通过@Component申明方式构建,也可以通过函数方式构建。@Builder就是用来装饰函数定义一个轻量级组件(UI元素),因此@Builder装饰器也简称为自定义构建函数,返回一个组件或UI元素。类似vue的render函数,通过函数形式来构建UI元素。
语法:
(1)在组件内创建(按值传递):
@Component
struct Demo {
@Builder 函数名(参数1:类型,参数2:类型,.....) {
}
}
调用:
this.函数名(参数1:类型,参数2:类型,.....)
(2)在组件外创建(按值传递)
@Builder function 函数名(参数1:类型,参数2:类型,.....) {
}
@Component
struct Demo {
}
调用:
函数名(参数1:类型,参数2:类型,.....)
(3)对象字面量入参形式(引用传递)
class ClassName{
a:xxxx
b:xxxx
c:xxxx
}
@Component
struct Demo {
@Builder 函数名($$:ClassName) {
}
}
调用
函数名({
a:value,
b:value2,
c:value3,
......
.......
})
说明:
1、在组件内创建称为私有自定义构建函数,只有当前组件能使用,在组件外创建称为全局自定义构建函数,所有组件都能使用。
2、入参有2种写法,一种像上面示例传递多个形参方式,称为按值传递,另一种把多个参数写成对象字面量形式(只有一个入参)传入,这种叫按引用传递。按值传递只会在首次渲染时候有效,后续修改入参值将无法响应UI变化,而引用传递修改对象字面量属性值会响应UI变化,所以推荐入参全部写成对象字面量形式。
示例1:
按引用传递
class BuilderParams {
image: Resource | string = ''
label: string = ''
}
@Entry
@Component
struct Parent {
@State image: Resource = $r('app.media.home')//图标
@State label: string = '首页'; //标签
//自定义构建函数
@Builder
childBuilder(params: BuilderParams) {
Column({ space: 6 }) {
Image(params.image).width(100)
Text(params.label).fontSize(16)
}
}
build() {
Column({ space: 30 }) {
this.childBuilder({ image: this.image, label: this.label })
Button('改变图标和标签').onClick(() => {
this.image = $r('app.media.mine')
this.label = '我的'
})
}.width('100%')
}
}
运行效果:
示例2:
示例1也可以改成 如下直接访问外部状态变量,效果一样
@Entry
@Component
struct Parent {
@State image: Resource = $r('app.media.home')//图标
@State label: string = '首页'; //标签
//自定义构建函数
@Builder
childBuilder() {
Column({ space: 6 }) {
Image(this.image).width(100)
Text(this.label).fontSize(16)
}
}
build() {
Column({ space: 30 }) {
this.childBuilder()
Button('改变图标和标签').onClick(() => {
this.image = $r('app.media.mine')
this.label = '我的'
})
}.width('100%')
}
}
示例3:
示例1也可以改成全局构建自定义函数,效果一样
class BuilderParams {
image: Resource | string = ''
label: string = ''
}
//全局自定义构建函数
@Builder
function childBuilder(params: BuilderParams) {
Column({ space: 6 }) {
Image(params.image).width(100)
Text(params.label).fontSize(16)
}
}
@Entry
@Component
struct Parent {
@State image: Resource = $r('app.media.home')//图标
@State label: string = '首页'; //标签
build() {
Column({ space: 30 }) {
childBuilder({ image: this.image, label: this.label })
Button('改变图标和标签').onClick(() => {
this.image = $r('app.media.mine')
this.label = '我的'
})
}.width('100%')
}
}
@Builder和@Component都可以自定义组件,我们要怎么选择呢?
@Builder主要封装轻量级UI元素,组件参数传递只靠入参或者直接访问外部状态变量,无生命周期函数、无法使用其他装饰器功能,有开发的局限性,而@Component适用于构建复杂逻辑的组件,有自己的生命周期可以使用各种类型装饰器,可实现更复杂的场景。
实际开发根据自己的需求场景去选择,如果2种方式都满足,选择@Builder方式性能会更好。
7.@BuilderParam
@BuilderParam相当于vue的插槽作用(slot),先在子组件调用@BuilderParam修饰的函数占位,具体渲染内容从父组件传入,通过上面介绍我们知道@Builder是用来自定义一个组件,父组件通过@Builder生成的组件当参数传入子组件,子组件拿到@Builder组件在占位地方填充渲染。所以可以简单理解和记忆@BuilderParam是子组件的入参(函数类型),入参类型是@Builder函数。
语法:
子组件:
@Builder A() {};
@BuilderParam B:类型=this.A
build(){
...
...
//插槽占位
this.B()
}
ps:@BuilderParam装饰的函数初始值必须设置且只能是@Builder装饰的函数
父组件:
//自定义插槽内容
@Builder C(){
}
build(){
...
...
child({B:this.C})
}
如果只有一个插槽,父组件可以省略自定义构建函数,直接在子组件内嵌套写布局,此方式叫尾随闭包初始化组件,相当于vue中的默认插槽场景
父组件
build(){
...
...
child(){
//自定义插槽内容
......
......
}
}
示例1:
只有一个@BuilderParam
@Entry
@Component
struct Parent {
@Builder customBuild(){
//插槽内容
Button('插槽按钮')
}
build() {
Column({ space: 30 }) {
Child({customBuilderParam:this.customBuild})
}.width('100%')
}
}
@Component
struct Child{
@Builder customBuilder() {};
@BuilderParam customBuilderParam:()=>void=this.customBuilder
build() {
Column(){
Text('child组件')
//插槽占位
this.customBuilderParam()
}
}
}
示例2:
只有一个@BuilderParam可以写成尾随闭包方式:
@Entry
@Component
struct Parent {
build() {
Column({ space: 30 }) {
Child(){
//插槽内容
Button('插槽按钮')
}
}.width('100%')
}
}
@Component
struct Child{
@Builder customBuilder() {};
@BuilderParam customBuilderParam:()=>void=this.customBuilder
build() {
Column(){
Text('child组件')
//插槽占位
this.customBuilderParam()
}
}
}
运行效果:
8.@Styles
@Styles用于装饰函数,函数内封装公共通用属性或通用事件,提供给需要的组件复用。类似scss里面的@mixin的作用
语法:
和@Builder类似@Styles可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字
(1)全局:
@Styles function functionName() { ... }
调用:
组件(){
}.functionName()
(2)组件内:
// 在组件内
@Component
struct Demo{
@Styles functionName() {
.height(100)
}
}
调用:
组件(){
}.functionName()
使用限制规则:
1、@Styles仅支持通用属性和通用事件
2、@Styles修饰的方法不支持入参
3、只能在当前文件内使用,不支持export导出给其他文件使用
4、组件内的@Styles优先级高于全局@Style
示例1:
全局定义
//全局公共样式
@Styles
function globalStyle() {
.width('100%')
.height(100)
.padding(10)
.backgroundColor(Color.Green)
.border({ width: 1, color: Color.Black, style: BorderStyle.Solid })
.borderRadius(10)
}
@Entry
@Component
struct Parent {
build() {
Column({ space: 30 }) {
Text('text1').fontColor(Color.White).globalStyle()
Text('text2').fontColor(Color.White).globalStyle()
Text('text3').fontColor(Color.White).globalStyle()
}.width('100%')
.padding(20)
}
}
运行效果:
示例2:
组件内定义
@Entry
@Component
struct Demo {
@State bgColor: ResourceColor = Color.Yellow //背景色
@State iHeight: number = 100 //高度
//组件内样式
@Styles
globalStyle() {
.width('100%')
.height(this.iHeight)
.border({ width: 1, color: Color.Black, style: BorderStyle.Solid })
.backgroundColor(this.bgColor)
.borderRadius(10)
.onClick(() => {
//高度+10
this.iHeight += 10
})
}
//按钮样式
@Styles
custButtonStyle(){
.width(200)
.height(80)
.onClick(() => {
//切换背景色
this.bgColor = this.bgColor === Color.Pink ? Color.Yellow : Color.Pink
})
}
build() {
Column({ space: 30 }) {
Column() {
Text('点击高度+10')
}
.justifyContent(FlexAlign.Center)
.globalStyle()
Button('改变颜色').custButtonStyle()
}.width('100%')
.padding(20)
}
}
运行效果:
从上述示例看,尽管@Styles装饰的函数不能写入参数,但是我们可以把@Style定义在组件内,并通过this去访问组件内的状态变量,达到动态传参的效果。
9.@Extend
@Extend跟@Styles功能类似,都是用来抽离组件属性和事件,@Extend针对的是某个具体系统组件单独设置,单独使用,而@Styles所有组件通用。
语法:
@Extend(系统组件名) function functionName(入参可选) { ... }
使用规则:
1、和@Styles不同,@Extend仅支持在全局定义,不支持在组件内部定义。
2、和@Styles不同,@Extend支持封装指定组件的私有属性、私有事件和自身定义的全局方法。
3、和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
4、只支持扩展原生组件,不支持自定义组件
示例:
/**
* 自定义按钮样式
* @param height:按钮高
* @param width :按钮宽
* @param backgourndColor :按钮背景色
* @param type //按钮样式
*/
@Extend(Button)
function customButton(height: number, width: number, backgourndColor: ResourceColor, type: ButtonType) {
.height(height)
.width(width)
.type(type)
.backgroundColor(backgourndColor)
.fontColor(Color.White)
.fontSize(14)
}
@Entry
@Component
struct Demo {
build() {
Column({ space: 30 }) {
Button('提交').customButton(50, 300, Color.Blue, ButtonType.Capsule)
Button('删除').customButton(60, 60, Color.Red, ButtonType.Circle)
}.width('100%')
.padding(20)
}
}
运行效果:
从以下开始的装饰器都是跟数据本地存储相关
10.@LocalStorageProp和@LocalStorageLink
@LocalStorageProp和@LocalStorageLink是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。
简单说每个注册的路由页面(@Entry装饰的组件)单独享有和管理一个LocalStorage实例,包括该页面引入的子孙组件共用同一个LocalStorage来存取数据。每个页面单独使用互不影响,默认不能相互访问,不能覆盖存储,关闭页面LocalStorage实例被系统回收数据被清除,类似web sessionStorage。
@LocalStorageProp(key)是和LocalStorage实例中key对应的属性建立单向数据同步。LocalStorage实例中key数据改变,@LocalStorageProp(key)装饰的变量也将跟着改变并能响应UI变化。
@LocalStorageLink(key)是和LocalStorage实例中key对应的属性建立双向数据同步,两者区别跟@Prop和@Link一样,一个数据单向流动一个双向绑定,其他作用一致。
语法:
创建LocalStorage实例:
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate(key,value)//创建属性
storage.setOrCreate(key2,value2)//创建属性
storage.setOrCreate(key3,value3)//创建属性
...
...
@Entry(storage)//页面绑定LocalStorage
@Component
struct Parent {
}
组件调用:
@Entry(storage)
@Component
struct Parent {
@LocalStorageProp(key) 变量:类型=默认值
@LocalStorageLink(key) 变量:类型=默认值
}
修改值:
分两种方式,如果是@LocalStorageLink(key)双向绑定只要修改@LocalStorageLink绑定变量即可。另一种通过调用LocalStorage实例的方法修改
storage.set(key,value)
也可以通过storage获取值:
storage.get(key)
判断key是否存在
storage.has(key)
删除LocalStorage中所有的属性
storage.clear()
更多的api可以查看LocalStorage9+文档
示例
//城市地区对象
class AddressProp{
city:string
area:string
constructor(city:string,area:string) {
this.city=city
this.area=area
}
}
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('name','李磊')
storage.setOrCreate('age',0)
storage.setOrCreate('address',new AddressProp('厦门市','思明区'))
@Entry(storage)
@Component
struct Parent {
@LocalStorageProp('name') name:string='' //姓名
@LocalStorageLink('age') age:number=0 //年龄
@LocalStorageLink('address') address:AddressProp=new AddressProp('','') //地址
build() {
Column({ space: 20 }) {
Text(`姓名:${this.name}`)
Text(`年龄:${this.age}`)
//子组件
Child()
Button('修改姓名').onClick(()=>{
storage.set('name','小明')
})
Button('修改年龄').onClick(()=>{
this.age=20
})
Button('修改城市').onClick(()=>{
this.address.city='福州市'
})
Button('修改地区').onClick(()=>{
this.address.area='鼓楼区'
})
Button('修改城市+地区').onClick(()=>{
storage.set('address',new AddressProp('泉州市','丰泽区'))
})
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
//子组件
@Component
struct Child {
@LocalStorageLink('address') address:AddressProp=new AddressProp('','')
build() {
Column({space:20}){
Text(`地址:${this.address.city}${this.address.area}`)
}
}
}
运行效果:
11.@StorageProp和@StorageLink
@StorageProp和@StorageLink是应用全局的UI状态存储,与之对应的是全局静态类AppStorage。和进程绑定,同一进程下的所有页面共用,生命周期和LocalStorage类似,当关闭应用将被回收,如果想要数据持久化本地保存需要结合PersistentStorage使用。
语法:
@Entry
@Component
struct Parent {
@StorageLink(key) 变量:类型=默认值
@StorageProp(key) 变量:类型=默认值
}
无需创建实例,直接使用
AppStorage和LocalStorage实例内置方法一样
AppStorage.setOrCreate(key,value) //创建属性
AppStorage.set(key,value) //设置值
AppStorage.get(key) //获取值
AppStorage.has(key)//判断是否存在属性
AppStorage.clear()//清除所有属性
示例:
Index.ets (第一个页面)
import { router } from '@kit.ArkUI'
//商品对象类型
export class GoodsProp{
name:string
price:number
image:Resource|string
constructor(name:string,price:number,image:Resource|string) {
this.name=name
this.price=price
this.image=image
}
}
//创建属性并设置初始值
AppStorage.setOrCreate('category','水果')
AppStorage.setOrCreate('place','海南')
AppStorage.setOrCreate('goods',new GoodsProp('榴莲',200,'https://img0.baidu.com/it/u=211945568,1824960805&fm=253&fmt=auto&app=120&f=JPEG?w=399&h=399'))
@Entry
@Component
struct Demo {
@StorageProp('category') category:string=''
@StorageLink('place') place:string=''
@StorageLink('goods') goods:GoodsProp=new GoodsProp('',0,'')
build() {
Column({ space: 20 }) {
Text(`类别:${this.category}`)
Text(`产地:${this.place}`)
Text(`名称:${this.goods.name}`)
Text(`价格:¥${this.goods.price}`)
Image(this.goods.image).width(100)
Button('修改商品信息').onClick(()=>{
//修改商品信息
AppStorage.set('category','饮料')
this.place='福建'
this.goods=new GoodsProp('雪津啤酒',10,'https://img1.baidu.com/it/u=3892212987,1352825001&fm=253&fmt=auto&app=138&f=JPEG?w=283&h=800')
})
Button('跳转下一页').onClick(()=>{
router.pushUrl({url:'pages/Second'})
})
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
Second.ets(第二个页面)
import {GoodsProp} from './Index'
import { router } from '@kit.ArkUI'
@Entry
@Component
struct Second{
@StorageLink('category') category:string=''
@StorageProp('place') place:string=''
@StorageProp('goods') goods:GoodsProp=new GoodsProp('',0,'')
build() {
Column({ space: 20 }) {
Text(`类别:${this.category}`)
Text(`产地:${this.place}`)
Text(`名称:${this.goods.name}`)
Text(`价格:¥${this.goods.price}`)
Image(this.goods.image).width(100)
Button('修改商品信息').onClick(()=>{
//修改商品信息
this.category="食物"
AppStorage.set('place','北京')
AppStorage.set('goods',new GoodsProp('北京烤鸭',50,'https://img2.baidu.com/it/u=543904622,2448295859&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=667'))
})
Button('返回上一页').onClick(()=>{
router.back()
})
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
运行效果:
想要持久化数据只需页面开头用PersistentStorage.persistProp(key,value)初始化属性即可
例如上个例子改为:
Index.ets:
import { router } from '@kit.ArkUI'
//商品对象类型
export class GoodsProp{
name:string
price:number
image:Resource|string
constructor(name:string,price:number,image:Resource|string) {
this.name=name
this.price=price
this.image=image
}
}
PersistentStorage.persistProp('category','水果')
PersistentStorage.persistProp('place','海南')
PersistentStorage.persistProp('goods',new GoodsProp('榴莲',200,'https://img0.baidu.com/it/u=211945568,1824960805&fm=253&fmt=auto&app=120&f=JPEG?w=399&h=399'))
@Entry
@Component
struct Demo {
@StorageProp('category') category:string=''
@StorageLink('place') place:string=''
@StorageLink('goods') goods:GoodsProp=new GoodsProp('',0,'')
build() {
Column({ space: 20 }) {
Text(`类别:${this.category}`)
Text(`产地:${this.place}`)
Text(`名称:${this.goods.name}`)
Text(`价格:¥${this.goods.price}`)
Image(this.goods.image).width(100)
Button('修改商品信息').onClick(()=>{
//修改商品信息
AppStorage.set('category','饮料')
this.place='福建'
this.goods=new GoodsProp('雪津啤酒',10,'https://img1.baidu.com/it/u=3892212987,1352825001&fm=253&fmt=auto&app=138&f=JPEG?w=283&h=800')
})
Button('跳转下一页').onClick(()=>{
router.pushUrl({url:'pages/Second'})
})
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
二、 总结
至此11个常用的装饰器已经讲完了,完全掌握你将能从容应对大部分的需求场景开发。v2版本官方也在加速推进开发中,我们完全能期待下一个版本正式发布,因为v2版本又会新增多个实用装饰器,改进v1版本不足将带来更加便捷的开发方式。例如有类似vue中的compute计算属性装饰器、类似@click装饰事件的装饰器,以及对对象深度监听支持等。