大家好我是牛牛,一名软件开发从业者,无意中接触到了鸿蒙移动端开发,对鸿蒙操作系统产生了极大的兴趣,作者将从无到有开发出一款鸿蒙原生APP。每天写一篇关于鸿蒙开发的技术文章。欢迎大家踊跃订阅➕关注,文章中有什么 不妥之处可以在评论区中指出。
注意:最好是有开发经验的伙伴来阅读系列文章。零基础的同学可以先去了解一下TypeScript从最基本的开发语言进行学起
前言
上一章节《一百天挑战学会HarmanyOS——HarmanyOS的基本组件与布局》介绍了ArkUI的基本组件与布局,初步的带大家认识了ArkUI中的基本组件,在真实的开发项目当中这些组件使用频率很高,其中还包括了一些高级组件。以我个人而言,只需了解最基本的组件使用,剩下的千篇一律,不会的直接翻阅官方文档和即可。ArkUI组件文档
本章介绍
本章节将为大家介绍ArkUI
中组件双向绑定
的使用,组件事件监听
的使用,组件状态
的使用,以及完成一个简单的系统登录的小案例。
双向绑定
什么是双向绑定?
提到双向绑定先介绍下一MVVM
模型,MVVM模型在许多前端框架设计中广泛使用,即 数据-模型
MVVM
- M : Model数据模型
- V : View视图
- VM: ViewModel属于打得通数据与视图的桥梁
双向绑定的含义就是,数据驱动视图的变化,数据改变,视图也随之变化,视图也可以驱动数据的变化,视图中的数据变化,即视图所绑定的数据也随之变化。
在不同框架中实现的方式各有不同,在 Vue
中使用v-model
进行数据的双向绑定,微信小程序使用wx:
进行数据的双向绑定。那在HarmanyOS中使用什么呢?
HarmanyOS双向绑定语法
语法: $$变量
例如:
$$this.username
注意:
- 当前
$$
支持基础类型变量,以及@State、@Link和@Prop装饰的变量。- 组件中有的双向组件是属性,有的双向绑定是参数
- 不支持嵌套数据的双向绑定
鸿蒙NEXT版本所有支持双向绑定的组件
下面使用几个常用组件的小案例介绍一下具体使用方法
TextInput
组件的双向绑定
示例:
代码:
@Entry
@Component
struct MvvmPage {
@State
textValue:string = "";
build() {
Column({space:20}) {
Text('TextInput的双向绑定')
// 这个Text中的值会因为Input输入的值而改变
Text(this.textValue)
// TextInput中使用双向绑定语法
TextInput({
text: $$this.textValue
})
}
.padding({
left:20,
right:20
})
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
CheckBox
组件的双向绑定
示例:
代码:
@Entry
@Component
struct CheckBoxMvvmPage {
@State
isStatus: boolean = false;
build() {
Column({ space: 20 }) {
Text('CheckBox的双向绑定')
Row() {
// CheckBox中使用双向绑定
Checkbox().select($$this.isStatus)
Text(this.isStatus ? '已选中' : '未选中')
}
// 使用按钮改变CheckBox的状态
Button('修改状态').width('100%').onClick(() => {
this.isStatus = !this.isStatus
})
}
.padding({
left: 20,
right: 20
})
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
Search
组件的双向绑定
示例:
代码:
@Entry
@Component
struct SearchMvvmPage {
@State
textValue:string = "";
build() {
Column({space:20}) {
Text('Search组件的双向绑定')
Search({
value: $$this.textValue
})
Text(this.textValue)
}
.padding({
left:20,
right:20
})
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
暂且演示这几个组件的双向绑定,剩下的组件可以在实际开发中得到实际的运用
事件监听
监听原生组件的事件和设置属性的方式是一样的都是链式调用,值得注意的是,我们注册事件必须使用箭头函数的写法,Next版本禁止使用匿名函数的形式来给组件注册事件
- 匿名函数 function () {}
尝试给一个TextInput和一个按钮注册一个值改变事件和点击事件
示例:
代码:
@Entry
@Component
struct ClickPage {
build() {
Row() {
Column({ space: 15 }) {
Row() {
TextInput({ placeholder: '请输入用户名' })
.backgroundColor('#f4f5f6')
.width('100%')
// onChange 监听输入框变化的事件
.onChange((value) => {
// 一变化就弹窗
AlertDialog.show({
message: value
})
})
}.padding({
left: 20,
right: 20
})
Row() {
Button("登录")
.width('100%')
// 按钮的点击事件
.onClick(() => {
// 一点击就弹窗
AlertDialog.show({
message: '点击了按钮'
})
})
}.padding({
left: 20,
right: 20
})
}
.width('100%')
}
.height('100%')
}
}
请注意: 在注册事件中的逻辑必须使用箭头函数 () => {}
- 因为function中this指向为undefind
- 箭头函数中的this指向当前struct实例,可以方便的调用方法和获取属性
组件状态
在上面双向绑定的案例代码中使用了@State
修饰符修饰了基本不同的变量。@State
装饰的变量称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
如何使用 @State
定义一个状态变量?
示例代码:
@Entry
@Component
struct Event {
@State loginName: string = ""
@State password: string = ""
}
需要注意的是,@State
修饰的类型
Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。类型必须被指定。不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。
下面使用这章节的所有知识点制作一个小案例。
模拟登录案例
示例:
代码:
import { promptAction, router } from '@kit.ArkUI';
@Entry
@Component
struct LoginPage {
@State
loginInfo: LoginUserInfoModel = new LoginUserInfoModel({
username: '',
password: ''
})
build() {
Column({ space: 20 }) {
Text('HarmanyOS Login').fontSize(30).fontWeight(FontWeight.Lighter)
TextInput({
placeholder: $r('app.string.accountInput'),
text: this.loginInfo.username
}).maxLength(16).onChange((value) => {
this.loginInfo.username = value
})
TextInput({
placeholder: $r('app.string.passwordInput'),
text: this.loginInfo.password
}).type(InputType.Password).maxLength(16)
Button($r('app.string.login')).width('100%').enabled(this.getBtnStatus()).onClick(() => {
this.loginSubmit();
router.pushUrl({
url: "pages/login/HomePage"
})
})
}
.justifyContent(FlexAlign.Center)
.padding({
left: 20,
right: 20
})
.height('100%')
.width('100%')
}
/** 设置按钮状态 */
getBtnStatus(): boolean {
if (this.loginInfo.username || this.loginInfo.password) {
return true
} else {
return false
}
}
loginSubmit() {
promptAction.showToast({ message: '登录成功' })
}
}
interface LoginUserInfo {
username: string,
password: string,
}
export class LoginUserInfoModel implements LoginUserInfo {
username: string = ''
password: string = ''
constructor(model: LoginUserInfo) {
this.username = model.username
this.password = model.password
}
}
这一期就到这里啦!为了方便大家我这一期代码放到git仓库上,建议大家动起手实操起来。 项目代码git