系列文章目录
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
【鸿蒙】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系列之华为账号一键登录功能实现(十四)
文章目录
前言
HarmonyOS NEXT(鸿蒙应用)开发快速入门教程ArkTS语法之装饰器篇,基于HarmonyOS NEXT Beta1版本(api 12)讲解。
本文将从前端开发者角度来理解和学习每个语法点,通过举例HarmonyOS NEXT和web端两种领域类似语法的使用,帮助前端开发人员快速入门HarmonyOS NEXT。在每个装饰器讲解上把同一个功能分别用ArkTs和vue 2种代码进行演示,使其更深刻理解每个装饰器的作用,在类比中学习记忆达到无缝衔接。
一、ArkTS基本介绍
ArkTs是鸿蒙开发主要语言,在以TS为语法基础上进行部分扩展和约束,以提升程序执行稳定性和性能。ArkTs可以看成严格模式的TS,并结合声明式UI进行页面布局。ArkTs吸收各家语言优点形成一种全新语法,在ArkTs身上我们到处都能看到web、vue、flutter、安卓等前端领域语法的身影
1、 ArkTS组成
说明:
1、以@开头为装饰器,装饰器都有固定英文名称,不同装饰器有着不同作用。例如@Entry表示当前组件为页面级别组件(页面路由入口),@Component声明该文件为组件 @State声明一个可引起UI响应式的变量等等
2、struct 后面跟着组件名称,固定写法,struct 类似es6 类里面关键字class, 后面的组件名称可以自定义,内部语法也类似class类语法,包括方法和属性添加
3、UI描述固定放置在build函数里面,换句话说build函数里面主要写布局代码
4、系统组件:一些系统自带的组件例如文字(Text)、按钮(Button)、输入框(TextInput)等,类似web里面dom标签,子组件通过嵌套标签写法引入,组件的属性和事件通过链式调用。
2、组件参数和属性
2.1、区分参数和属性的含义
组件标签函数入参称为组件参数,也即括号内的内容,而点后面的链式调用函数称为属性,事件和属性一样也通过链式函数调用
示例2.1:
build() {
Column(){
Button('按钮')
.type(ButtonType.Capsule)
.onClick(()=>{
console.log('click')
})
}
.width('500px')
.height('200px')
.backgroundColor(Color.Black)
}
如上述示例所示,'按钮’为组件Button入参,type为Button属性,onClick为Button点击事件,width、height、backgroundColor为组件Column属性。
所有组件都有通用属性,通用属性大部分类似web里的css属性,例如设置组件的尺寸宽高、位置、背景、透明度等。
2.2、父子组件嵌套
父组件花括号{}内写子组件,如示例2.1Column为父组件,Button为子组件,如果没有子组件可以省略{},如示例2.1的Button
示例2.1等价于如下的html写法:
<div style="height:200px;width:500px;background:black">
<button >按钮</button>
</div>
二、装饰器语法
常用的装饰器语法跟vue很像,本模块将通过ArkTs示例结合web(主要vue)示例演示对比,使其更好的理解和掌握ArkTs装饰器的使用。
1.@State
@State用来装饰变量,通过@State装饰的变量改变后才能触发UI刷新,而普遍变量改变不触发UI刷新,相当于Vue3的ref
语法:@State 变量名:类型=值
//例如
@State sex:string="男"
示例:
ArkTs写法:
@Entry
@Component
struct Demo {
@State name:string='小红'//姓名
age:number=10//年龄
build() {
Column({space:10}){
Text(`姓名:${this.name}`).fontColor(Color.Black)
Text(`年龄${this.age}`).fontColor(Color.Black)
Button('点击改变姓名').onClick(()=>{
this.name='小明'
})
Button('点击改变年龄').onClick(()=>{
this.age=20
})
}
}
}
等价于
Vue3写法:
<template>
<div style="display:flex;flex-direction: column;">
<span>姓名{{name}}</span>
<span>年龄{{age}}</span>
<button @click="onChangeName">点击改变姓名</button>
<button @click="onChangeAge">点击改变年龄</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
const name=ref('小红')
let age=10
const onChangeName=()=>{
name.value='小明'
}
const onChangeAge=()=>{
age=20
}
</script>
运行效果:
上述示例name(姓名)变量用@State修饰而age(年龄)变量为普通变量,当点击改变姓名按钮,姓名变成小红,当点击改变年龄按钮,年龄不变
2.@Prop
@Prop用来定义子组件的入参,和父组件建立单向的同步关系,相当于vue中的prop,区别在于vue中的prop不允许改变值,而鸿蒙中可以随意改变值,但是改变后的值不会同步回其父组件,也就是数据是单向传递。
语法:@Prop 变量名:类型=默认值
@Prop size:number=20
示例:
ArkTs写法:
//父组件
@Entry
@Component
struct Parent {
@State city:string='上海'
build() {
Column({space:20}) {
//引入子组件
Child({city:this.city})
Button('定位').onClick(()=>{
this.city='深圳'
})
}
}
}
//子组件
@Component
struct Child{
@Prop city:string='北京' //默认值北京
build() {
Column({space:10}) {
Text(`当前所处城市:${this.city}`).fontSize(20)
}
}
}
等价于
Vue3写法:
child.vue(子组件):
<template>
<div>
<span>当前所处的城市:{{city}}</span>
</div>
</template>
<script setup>
const props=defineProps({
city:{
type:String,
default:'北京'
}
})
</script>
parent.vue(父组件):
<template>
<div style="display:flex;flex-direction: column;">
<Child :city="city"></Child>
<button @click="onLocation">定位</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import Child from './child.vue'
const city=ref('上海')
//定位点击事件
const onLocation=()=>{
city.value='深圳'
}
</script>
<style scoped>
</style>
运行效果:
ps:ArkTS支持在同一个文件内自定义多个组件,也可以分出去单独写子组件通过import导入
3.@Link
@Link用来定义子组件入参,和父组件建立双向绑定关系,相当于vue中的v-model,区别在于@Link是直接在子组件内修改数据源,而v-model是语法糖,本质通过事件通知父组件来改变值。
语法:@Link 变量名:类型
@Link loop:boolean
示例:
ArkTs写法:
//父组件
@Entry
@Component
struct Parent {
@State value: string = '' //输入内容
build() {
Column({ space: 20 }) {
Text(`输入框值为:${this.value}`)
Child({ value: this.value })
}.padding(20)
}
}
//子组件
@Component
struct Child {
@Link value: string //输入内容
build() {
//输入框
TextInput({ text: this.value })
.onChange((value: string) => { //输入事件监听
this.value = value
})
}
}
等价于
Vue3写法:
child.vue(子组件):
<template>
<input :value="modelValue" @input="onChange"/>
</template>
<script setup>
const props=defineProps({
modelValue:{
type:String,
default:''
}
})
const emits=defineEmits(['update:modelValue'])
//输入事件监听
const onChange=(e)=>{
emits('update:modelValue',e.target.value)
}
</script>
parent.vue(父组件):
<template>
<div style="display:flex;flex-direction: column;">
<span>输入框值为:{{value}}</span>
<Child v-model="value"></Child>
</div>
</template>
<script setup>
import {ref} from 'vue'
import Child from './child.vue'
//输入内容
const value=ref('')
</script>
<style scoped>
</style>
运行效果:
ps:@Link修饰的变量不能设置默认值
4.@Watch
@Watch用于对状态变量的监听,当变量值变化会触发回调。相当于vue中 watch。
语法:
其他装饰器 @Watch(回调函数名) 变量名:类型=值
或
@Watch(回调函数名) 其他装饰器 变量名:类型=值
推荐@Watch写在其他装饰器后面
@State @Watch("onIndexChange") index:number=0
//监听值改变回调
onIndexChange(){
}
区别和注意点:
1、@Watch无法获取旧值,相当于无vue-watch的oldValue入参
2、@Watch无法深度监听,相当vue-watch的deep属性为false
3、@Watch无法设置初始化触发,相当vue-watch的immediate属性为false
4、@Watch可监听所有装饰器装饰的状态变量。不允许监听常规变量
5、@Watch对于数组监听能力跟vue2对数组响应式监听一样能监听到push、pop、splice、shift、unshift等数组操作变化
示例:
ArkTs写法:
//父组件
@Entry
@Component
struct Demo {
private price: number = 10 //单价
@State @Watch('onCountChange') count:number=1//数量
@State total:number=10 //总价
//数量变化监听
onCountChange(){
this.total=this.price*this.count
}
build() {
Column({ space: 20 }) {
Text(`单价:¥${this.price}`)
Text(`数量:x${this.count}`)
Text(`总价:¥${this.total}`)
Button('数量+1').onClick(()=>{
this.count++
})
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
//子组件
@Component
struct Child {
@Link value: string //输入内容
build() {
//输入框
TextInput({ text: this.value })
.onChange((value: string) => { //输入事件监听
this.value = value
})
}
}
等价于
Vue3写法:
<template>
<div style="display:flex;flex-direction: column;">
<span>单价:¥{{price}}</span>
<span>数量:x{{count}}</span>
<span>总价:¥{{total}}</span>
<button @click="onCountChange">数量+1</button>
</div>
</template>
<script setup>
import {ref,watch} from 'vue'
//单价
const price=ref(10)
//数量
const count=ref(1)
//总价
const total=ref(10)
//数量+1
const onCountChange=()=>{
count.value++
}
watch(count,(newValue,oldValue)=>{
total.value=price.value*newValue
})
</script>
<style scoped>
</style>
运行效果:
从上述例子可以看出watch回调函数中无任何入参,获取新值是通过重新访问属性值来获取,而旧值无法获取,这是第一点不足。第二点不足无法深层监听对象,第三点不足只能监听单个值变化,无法像vue3可以监听多个值。好在下一个装饰器语法版本(v2版本)将对这些不足点进行改进并支持,目前v2版本处于试用开发阶段还不成熟这里不过多介绍。
5.@Provide和@Consume
@Provide和@Consume成对使用,作用是把参数往子孙层传递,实现跨层级(多层级)传递。父组件使用@Provide修饰变量参数,子孙组件使用@Consume接收变量参数,跟vue3的Provide+Consume使用机制一样。
两种写法:
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
示例:
ArkTs写法:
//父组件
@Entry
@Component
struct Parent {
@Provide('weight') weight: number = 50
build() {
Column({ space: 20 }) {
Text(`父组件体重值:${this.weight}`)
Button(`父组件体重+1`).onClick(() => {
this.weight++
})
Child()
}.padding(20).alignItems(HorizontalAlign.Start)
}
}
//子组件
@Component
struct Child {
build() {
Grandson()
}
}
//孙组件
@Component
struct Grandson {
@Consume('weight') weight: number
build() {
Column({ space: 20 }) {
Text(`孙组件体重值:${this.weight}`)
Button(`孙组件体重+1`).onClick(() => {
this.weight++
})
}.margin({ top: 50 })
}
}
等价于
Vue3写法:
parent.vue(父组件):
<template>
<div>
<span>父组件体重值:{{ weight }}</span>
<button @click="onAdd">父组件体重+1</button>
<Child/>
</div>
</template>
<script setup>
import { ref,provide } from "vue";
import Child from './child.vue'
const weight=ref(50)
provide('weight',weight)
const onAdd=()=>{
weight.value++
}
</script>
child.vue(子组件):
<template>
<Grandson/>
</template>
<script setup>
import Grandson from "./grandson .vue";
</script>
grandson.vue(孙组件)
<template>
<div>
<span>孙组件体重值:{{ weight }}</span>
<button @click="onAdd">孙组件体重+1</button>
</div>
</template>
<script setup>
import { ref,inject } from "vue";
const weight=inject('weight',50)
const onAdd=()=>{
weight.value++
}
</script>
运行效果:
ps:@Consume修饰的变量不能设置默认值
6.@Observed和@ObjectLink
对于对象类型的数据劫持鸿蒙和vue不一样,不管是@State、@Prop、@Link或者@Provide+@Consume对于对象类型的数据只能监听到最外层变化,当对象嵌套多层对象内部对象的属性值改变将无法响应UI变化,@Observed和@ObjectLink就是为了解决这个问题而设计的。
使用方法:
1、@Observed用来修饰类(也即TS对象类型),被@Observed装饰的类,可以被观察到属性的变化,每一层的类都需要用@Observed修饰才能生效。
2、@ObjectLink装饰器在子组件中使用,用于装饰@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定,也可以看成子组件的入参变量。
3、@Observed和@ObjectLink要配合自定义子组件使用才能生效,而且每嵌套一层就要抽离出一个子组件引入,简单理解就是每一层数据当入参传入子组件。
语法示例:
@Observed
class xxx{
constructor(){
}
}
子组件:
@ObjectLink 变量名:类型
代码示例:
场景设计—— 假设有个学生对象数据,对象里包含姓名、性别、和成绩属性,成绩属性又是个对象,包含语文、数学、英文分数属性。通过改变学生性别和分数观察UI变化。
示例1:(不使用@Observed和@ObjectLink)
//学生对象
class Student {
name: string //姓名
sex: string //性别
score: ScoreData //分数对象
constructor(name: string, sex: string, score: ScoreData) {
this.name = name
this.sex = sex
this.score = score
}
}
//分数对象
class ScoreData {
math: number //数学
chinese: number //语文
english: number //英语
constructor(math: number, chinese: number, english: number) {
this.math = math
this.chinese = chinese
this.english = english
}
}
@Entry
@Component
struct Demo {
//学生对象实例
@State student:Student=new Student("王明","男",new ScoreData(80,90,75))
build() {
Column({space:10}) {
Text(`姓名:${this.student.name}`)
Text(`性别:${this.student.sex}`)
Text(`数学成绩:${this.student.score.math}分`)
Text(`语文成绩:${this.student.score.chinese}分`)
Text(`英语成绩:${this.student.score.english}分`)
Button('改变性别').onClick(()=>{
this.student.sex='女'
})
Button('改变数学成绩').onClick(()=>{
this.student.score.math=10
})
}.width('100%').padding(20).alignItems(HorizontalAlign.Start)
}
}
运行效果:
从运行效果可以看出性别变了,而数学分数未变,因为数学分数(math属性)属于对象中第二层数据,@State无法观察到多层变化,而性别(sex属性)属于第一层可以观察到变化。
示例2:(使用@Observed和@ObjectLink)
//学生对象
@Observed
class Student {
name: string //姓名
sex: string //性别
score: ScoreData //分数对象
constructor(name: string, sex: string, score: ScoreData) {
this.name = name
this.sex = sex
this.score = score
}
}
//分数对象
@Observed
class ScoreData {
math: number //数学
chinese: number //语文
english: number //英语
constructor(math: number, chinese: number, english: number) {
this.math = math
this.chinese = chinese
this.english = english
}
}
@Entry
@Component
struct Demo {
//学生对象实例
@State student: Student = new Student("王明", "男", new ScoreData(80, 90, 75))
build() {
Column({ space: 10 }) {
Text(`姓名:${this.student.name}`)
Text(`性别:${this.student.sex}`)
ScoreView({
data: this.student.score
})
Button('改变性别').onClick(() => {
this.student.sex = '女'
})
Button('改变数学成绩').onClick(() => {
this.student.score.math = 10
})
Button('改变语文成绩').onClick(() => {
this.student.score.chinese--
})
}.width('100%').padding(20).alignItems(HorizontalAlign.Start)
}
}
//子组件
@Component
struct ScoreView {
@ObjectLink data: ScoreData //分数对象
build() {
Column() {
Text(`数学成绩:${this.data.math}分`)
Text(`语文成绩:${this.data.chinese}分`)
Text(`英语成绩:${this.data.english}分`)
}
}
}
运行效果:
从运行效果可以看出,因为使用了@Observed和@ObjectLink,所以修改第二层数据(数学和英文成绩)都会响应UI变化。
小结:
通过上面示例演示来看,鸿蒙对嵌套对象场景的开发显得力不从心,如果数据对象是n层就需要自定义n-1个子组件来传递每一层的数据,特别麻烦。好在官方已经注意到这些不足,在下个版本(v2版)提供的新的装饰器@ObservedV2+@Trace解决对象嵌套问题,v2版本目前处于开发试用阶段还没正式发布,这里不过多介绍,有兴趣可以自行查阅官网文档。
ps:对于多层嵌套场景不单单指对象中嵌套对象,还包括对象中嵌套数组或者数组中嵌套对象,因为在js世界里数组也是对象类型。
三、总结
上述6种装饰器语法是开发中比较高频率使用的装饰器,可以看出除了第六个剩下的都可以在vue中找到对应的语法,使用上几乎一样,所以从事前端开发特别是vue技术栈的开发人员可以快速无缝衔接。
四、未完待更
除了上述6种常用装饰器,ArkTs还有一些其他比较重要装饰器,将在下篇博文继续介绍。。。。。。