系列文章目录
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
文章目录
前言
HarmonyOS NEXT(鸿蒙应用)开发快速入门教程ArkTS语法之装饰器篇,基于HarmonyOS NEXT Beta1版本(api 12)讲解。
一、ArkTS基本介绍
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>
二、装饰器语法
1.@State
语法:@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 变量名:类型=默认值
@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 变量名:类型
@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(回调函数名) 变量名:类型=值
或
@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 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
使用方法:
1、@Observed用来修饰类(也即TS对象类型),被@Observed装饰的类,可以被观察到属性的变化,每一层的类都需要用@Observed修饰才能生效。
2、@ObjectLink装饰器在子组件中使用,用于装饰@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定,也可以看成子组件的入参变量。
3、@Observed和@ObjectLink要配合自定义子组件使用才能生效,而且每嵌套一层就要抽离出一个子组件引入,简单理解就是每一层数据当入参传入子组件。
语法示例:
@Observed
class xxx{
constructor(){
}
}
子组件:
@ObjectLink 变量名:类型
代码示例:
示例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变化。
小结:
ps:对于多层嵌套场景不单单指对象中嵌套对象,还包括对象中嵌套数组或者数组中嵌套对象,因为在js世界里数组也是对象类型。
三、总结
四、未完待更
除了上述6种常用装饰器,ArkTs还有一些其他比较重要装饰器,将在下篇博文继续介绍。。。。。。