目录
深入鸿蒙开发:掌握ArkTS中的关键装饰器
@Styles装饰器
介绍
@styles
装饰器主要用于将样式绑定到组件上,通过使用这个装饰器,你可以定义一组样式,并将这些样式应用到特定的组件上,这种方式使得样式管理更加模块化和可重用,只负责在当前页面的复用。
组件内公共样式有两种方案:
- 组件内的定义。
- 组件外的定义。
组件内部定义的语法:
// 定义
@Styles boxStyles(){
.width(100)
.height(100)
.backgroundColor(Color.Red)
.borderRadius(20)
}
// 使用
Column(){}.boxStyles()
注意:
不是所有的样式都可以提取到@Styles
装饰器中,一般默认是容器的布局样式布局属性是可以提取到@Styles
装饰其中,但是字体或者文本样式不支持提取。
使用案例:
@Entry
@Component
struct Page09_AddTask{
// 定义
@Styles boxStyles(){
.width(100)
.height(100)
.backgroundColor(Color.Red)
.borderRadius(20)
}
build(){
Column(){
// 标题模块
Text("新建任务")
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor($r("app.color.text_color"))
.margin({bottom:20})
// 使用
Column(){}.boxStyles()
}
.width("100%")
.height("100%")
.padding(10)
.backgroundColor("#f1f3f5")
}
}
效果:
组件外部定义的语法:
// 定义
@Styles function boxStyles(){
.width(100)
.height(100)
.backgroundColor(Color.Red)
.borderRadius(20)
}
// 使用
Column(){}.boxStyles()
组件外部定义需要多加一个function
语法。
总结:
- 布局容器的公共样式可以用
Styles
来进行定义。 - 公共样式提取放在组件内部,也可以放在组件外部,但是必须同一个文件中。
- 虽然你可以理解为
@Styles
定义公共样式实际上就是抽取函数出来,但是它不支持参数。
@Extend装饰器
介绍
@Extend
装饰器主要用于对鸿蒙原生组建的样式或者事件进行扩展,你可以使用这个装饰器对原生的组件进行重定义,然后在页面中复用,实际上类似于@Styles
装饰器的效果。必须指定你对哪个原生组件进行扩展。
定义代码格式如下:
// 定义Text组件的扩展样式
@Extend(Text) function TextStyles(){
.fontSize(16)
.fontColor($r("app.color.add_title_color"))
}
@Extend(TextInput) function InputStyles(){
.borderRadius(0)
.fontWeight(FontWeight.Bold)
.backgroundColor("#f1f3f5")
.placeholderColor($r("app.color.text_color"))
}
@Extend(Line) function LineStyles(value:string = "100%"){
.width(value)
.height(1)
.backgroundColor($r("app.color.add_title_color"))
}
使用格式如下:
Text("标题").TextStyles()
TextInput({placeholder:"请输入任务名字"}).InputStyles()
Line().LineStyles()
@Entry
@Component
...
...
案例代码:
@Styles
function boxStyles() {
.width(100)
.height(100)
.backgroundColor(Color.Red)
.borderRadius(20)
}
// 定义Text组件的扩展样式
@Extend(Text)
function TextStyles() {
.fontSize(16)
.fontColor($r("app.color.add_title_color"))
}
@Extend(TextInput)
function InputStyles() {
.borderRadius(0)
.fontWeight(FontWeight.Bold)
.backgroundColor("#f1f3f5")
.placeholderColor($r("app.color.text_color"))
}
@Extend(Line)
function LineStyles(value: string = "100%") {
.width(value)
.height(1)
.backgroundColor($r("app.color.add_title_color"))
}
@Entry
@Component
struct Page09_AddTask {
build() {
Column() {
// 标题模块
Text("新建任务")
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor($r("app.color.text_color"))
.margin({ bottom: 20 })
// 主体内容
Column() {
// 存放标题的模块
Column() {
Text("标题")
.TextStyles()
TextInput({ placeholder: "请输入任务名字" })
.InputStyles()
Line().LineStyles()
}
}
}
.width("100%")
.height("100%")
.padding(10)
.backgroundColor("#f1f3f5")
}
}
案例效果:
总结:
@Extend
来进行样式扩展,针对指定的组件进行样式的封装、重构、让页面样式作用更加精准。@Extend
来扩展样式,解决@Styles无法传递参数的问题。
@State装饰器
介绍
在ArkTs
中,@State
装饰器用于定义状态变量。状态变量是组件内部的状态,他们的变化会触发组件的从新渲染,反应最新的状态值,这种机制可以使得UI可以根据应用状态自动更新,简化界面与数据同步的复杂度。
定义代码格式如下:
@Entry
@Component
struct Page10_State {
@State message:string = "hello world"
username:string = "WuYouke"
build() {
Column(){
}
.width("100%")
.height("100%")
}
}
用@State装饰器定义的变量,代表组件的内部状态,只要这个变量发生变化,页面UI会进行重新渲染。
特点
@State
来修饰变量,必须要求变量要进行初始化,不能为空值。@State
来修饰变量,可以支持Object
、class
、string
、number
、boolean
等等。- 如果出现嵌套类型的数据结构,嵌套对象的属性发生变化,页面无法检测更新。
- 标记为
@State
的变量默认都是私有变量,只能在组件中访问,不能在外面访问。
关于复杂类型数据定义
import { UserModel } from "../viewmodel/UserModel"
@Entry
@Component
struct Page10_State {
@State message:string = "hello world"
@State user:UserModel = new UserModel(1,"WuYouKe")
@State users:UserModel[] = [new UserModel(2,"WuQi"),new UserModel(2,"WuMan")]
@State Users2:Array<UserModel> = [new UserModel(2,"WuLun"),new UserModel(2,"ZhangYongfang")]
build() {
Column(){
}
.width("100%")
.height("100%")
}
}
其中UserModel代码为:
export class UserModel{
id:number
name:string
constructor(id: number, name: string) {
this.id = id
this.name = name
}
}
总结
页面上要用到数据,并不是所有数据都需要大家采用@State
来修饰,有些变量无需采用@State
来修饰的,只需要在页面加载一次,直接定义就可以了。
@Prop装饰器
前言
鸿蒙开发中,涉及到父子组件通信,我们需要用到@Prop
装饰器,子组件可以使用这个数据进行渲染,但是修改子组件的传递至,父组件无法同步。【前端框架中,一般都是单向数据流】
方案一:格式如下
子组件中定义了一个变量,这个变量默认初始化,也可以接受父组件传递过来的值。
struct Children{
private title:string = "title"
build{
Text(`${this.title}`)
}
}
负组件中调用子组件传递参数
struct Parent{
build(){
Children({title:"Harmony OS")
}
}
总结:
- 只要父组件中传递子组件传递的数据过去,字段和子组件内容同名,默认会覆盖组件的数据。
- 子组件基本变量,能够接收数据,但是无法实现修改页面更新。
- 适用于页面中标题、尺寸、这些内容不变,也无需在页面引起数据的动态渲染。
方案二:格式如下
采用单向数据流来接受数据变化,子组件代码如下,@Prop
修饰不需要初始化。
struct Children{
@Prop title:string = "title"
build{
Text(`${this.title}`)
}
}
负组件中调用子组件传递参数
struct Parent{
build(){
Children({title:"Harmony OS")
}
}
总结:
- @Prop只能在@Component组件中使用,不能@Entry组件中使用。
- @Prop来定义变量,可以默认不初始化,本来就是要接收外部数据。
- @Prop来定义数据,可以进行修改,但是只能影响本组件,无法同步给父组件。
@prop
为单项数据流:
@Link装饰器
前言:
双向数据通信,父组件传递值给子组件,子组件可以修改传递过去这个值,同步将内容修改完成传递到父组件。
使用格式如下:
父组件代码:
import { UserModel } from "../viewmodel/UserModel"
import { Children } from "../pages/Children"
@Entry
@Component
struct Page10_State {
@State user:UserModel = new UserModel(1,"WuYouke")
build() {
Column(){
Text(`父组件 - 编号:${this.user.id}、姓名:${this.user.name}`)
Button("父组件按钮").onClick((event: ClickEvent) => {
this.user.name = "WuYouke------"
})
Children({user:this.user})
}
.width("100%")
.height("100%")
}
}
子组件代码:
import { UserModel } from "../viewmodel/UserModel"
@Component
export struct Children {
@Link user:UserModel;
build() {
Column(){
Text(`子组件 - 编号:${this.user.id}、姓名:${this.user.name}`)
Button("子组件按钮").onClick((event: ClickEvent) => {
this.user.name = "吴又可"
})
}
.width("100%")
.height("100%")
}
}
总结:
@Link
只能在@Component
组件中使用,不能在@Entry
组件中使用。@Link
来定义变量,可以默认不初始化,本来代表接收外部数据。@Link
来定义数据,可以进行修改,不管父组件还是子组件数据都是同步变化。
@ObjectLink与@Observed装饰器:
前言:
在鸿蒙开发中,会使用@State
来装饰我们变量,一旦变量发生变化页面更新,遇到了复杂的数据结构,数据更新会有差别,比如嵌套对象,数组对象。
- 嵌套对象:如果修改时内层对象的属性,默认页面无法检测更新。 - 数组对象:修改数组页面都能检测到变化,修改数组里面的对象属性,页面无法检测更新。
嵌套对象修改无法监听更新案例:
比如如下效果:只能修改第一层的属性值,不能修改更深层的值,如果修改更深层则需要重新new
一个对象进行替换。
代码如下:
class Pet {
id: string = ""
name: string = ""
type: string = ""
constructor(id: string, name: string, type: string) {
this.id = id
this.name = name
this.type = type
}
}
class Student {
id: string = ""
name: string = ""
pet: Pet
constructor(id: string, name: string, pet: Pet) {
this.id = id
this.name = name
this.pet = pet
}
}
@Entry
@Component
struct Page14_ObjectLink {
@State stu: Student = new Student("1", "王何凤", new Pet("001", "快快", "猫"))
aboutToAppear(): void {
console.log(JSON.stringify(this.stu))
}
build() {
Column() {
Text(`学生信息为:${JSON.stringify(this.stu)}`).fontSize(25)
Button("修改学生名字").onClick((event: ClickEvent) => {
this.stu.name = "吴用"
})
Button("修改学生宠物名字").onClick((event: ClickEvent) => {
this.stu.pet.name = "乐乐"
})
Button("通过new新宠物替换修改学生宠物名字").onClick((event: ClickEvent) => {
const temp = new Pet("001", "乐乐", "猫")
this.stu.pet = temp
})
}
.height('100%')
.width('100%')
}
}
对象数组无法监听到更新案例:
对象数组进行数据循环遍历的时候,如果直接操作数组中的元素,数组变化能引起页面更新,比如新增何删除能引起页面更新,但是你操作数组里的对象属性无法检测更新。
代码如下:
class Pet {
id: string = ""
name: string = ""
type: string = ""
constructor(id: string, name: string, type: string) {
this.id = id
this.name = name
this.type = type
}
}
class Student {
id: string = ""
name: string = ""
pet: Pet
constructor(id: string, name: string, pet: Pet) {
this.id = id
this.name = name
this.pet = pet
}
}
@Entry
@Component
struct Page14_ObjectLink {
@State stu: Student = new Student("1", "王何凤", new Pet("001", "快快", "猫"))
@State school: Array<Student> = [
new Student("001", "吴用", new Pet("001", "快快", "猫")),
new Student("001", "王何凤", new Pet("002", "乐乐", "猫")),
new Student("001", "吴又可", new Pet("003", "饼干", "鼠")),
]
aboutToAppear(): void {
console.log(JSON.stringify(this.stu))
}
build() {
Column() {
Button("+添加学生信息").onClick(() => {
const tempDate = new Student("004", "王何凤", new Pet("004", "小灰灰", "兔子"))
this.school.push(tempDate)
}).height(20).fontSize(14).margin({ bottom: 10 })
ForEach(this.school, (item: Student, index: number) => {
Text(`姓名 :${item.name}, 宠物名称 :${item.pet.name}, 宠物类型 :${item.pet.type}`)
.fontColor(Color.Brown)
Row() {
Button("修改").onClick(() => {
item.name = "默认名称"
}).width(70).height(20).fontSize(14)
Button("删除")
.onClick(() => {
this.school.splice(index, 1)
})
.width(70)
.height(20)
.fontSize(14)
.backgroundColor(Color.Red)
}
}, (item: Student) => item.id)
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
}
}
数组对象深度更新解决方案:
studentModel.ets
:
export class Pet {
id: string = ""
name: string = ""
type: string = ""
constructor(id: string, name: string, type: string) {
this.id = id
this.name = name
this.type = type
}
}
@Observed
export class Student {
id: string = ""
name: string = ""
pet: Pet
constructor(id: string, name: string, pet: Pet) {
this.id = id
this.name = name
this.pet = pet
}
}
在父组件中:
import { Pet, Student } from "../viewmodel/StudentModel"
import { ObjectLinkList } from "../view/ObjectLinkList"
@Entry
@Component
struct Page14_ObjectLink {
@State school: Array<Student> = [
new Student("001", "吴用", new Pet("001", "快快", "猫")),
new Student("001", "王何凤", new Pet("002", "乐乐", "猫")),
new Student("001", "吴又可", new Pet("003", "饼干", "鼠")),
]
build() {
Column() {
Button("+添加学生信息").onClick(() => {
const tempDate = new Student("004", "王何凤", new Pet("004", "小灰灰", "兔子"))
this.school.push(tempDate)
}).height(20).fontSize(14).margin({ bottom: 10, top: 15 })
ForEach(this.school, (item: Student, index: number) => {
ObjectLinkList({item:item})
}, (item: Student) => item.id)
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
}
}
子组件中:使用@ObjectLink
监听
import { Pet,Student } from "../viewmodel/StudentModel"
@Component
export struct ObjectLinkList {
@ObjectLink item:Student
build() {
Column() {
Text(`姓名 :${this.item.name}, 宠物名称 :${this.item.pet.name}, 宠物类型 :${this.item.pet.type}`)
.fontColor(Color.Brown)
Row() {
Button("修改").onClick(() => {
this.item.name = "默认名称"
}).width(70).height(20).fontSize(14)
}
}
}
}
子组件接收的数据就是你要监听的哪个对象,配合ObjectLink
配合Observed
就能实现对指定的这个对象进行深度监听。
数组对象深度更新解决方案效果:
嵌套对象深度更新解决方案:
import { Pet, Student } from "../viewmodel/StudentModel"
@Entry
@Component
struct Page14_ObjectLink {
@State stu: Student = new Student("1", "王何凤", new Pet("001", "快快", "猫"))
build() {
Column() {
Column() {
Text(`学生信息为:${JSON.stringify(this.stu)}`).fontSize(25)
Button("父组件修改学生宠物名字").onClick((event: ClickEvent) => {
this.stu.pet.name = "小白"
})
ObservedChildren({pet:this.stu.pet})
}
}
.height('100%')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
}
}
@Component
export struct ObservedChildren{
@ObjectLink pet:Pet
build() {
Column(){
Text(`宠物信息${this.pet.name}}`)
.fontSize(20)
.fontColor(Color.Orange)
Button("子组件修改学生宠物名字").onClick((event: ClickEvent) => {
this.pet.name = "乐乐"
})
}.width("100%").height("100%")
}
}
嵌套对象深度更新解决效果:
总结:
如果页面上面只是渲染嵌套对象,那我们无需进行干预,直接渲染出来即可,但是如果要针对嵌套的对象进行修改,一定创建一个子组件,将这个对象传递给子组件专门进行页面渲染以及修改,也需要给model
数据约束增加@Observerd
以及@ObjectLink
来接收修改对象。
@Provider与@Consume装饰器:
在鸿蒙开发过程中,会遇到跨组件进行通信,爷孙节点进行通信。
使用注意:
@Provider
在父组件中定义。@Comsume
可以在子组件或者孙节点使用。
使用格式案例:
@Entry
@Component
struct Page15_Provider {
@Provide msg:string = "harmonyos"
build() {
Column() {
Text("我是Page15_Provider组件").fontSize(20).fontColor(Color.Red)
Text(this.msg).fontColor(Color.Blue)
Button("父组件修改msg").onClick((event: ClickEvent) => {
this.msg = "鸿蒙开发"
})
CompA()
}.width("100%")
.height("100%")
}
}
@Component
export struct CompA{
@Consume msg:string
build() {
Column() {
Text("我是CompA组件").fontSize(20).fontColor(Color.Red)
Text(this.msg).fontColor(Color.Blue)
CompB()
}.width("100%")
.height("100%")
}
}
@Component
export struct CompB{
// 给获取的数据去一个别名
@Consume("msg") mymsg:string
build() {
Column() {
Text("我是CompB组件").fontSize(20).fontColor(Color.Red)
Text(this.mymsg).fontColor(Color.Blue)
Button("子组件修改msg").onClick((event: ClickEvent) => {
this.mymsg = "harmonyos"
})
}.width("100%")
.height("100%")
}
}
使用格式效果:
总结
@Consume
装饰器在定义过程中无需初始化,值必须来自于@Provide
装饰器,@Consume
获取数据有两种写法,@Consume
便令名称,@Consume
(“变量名称”) 别名。@Consume
和@Link
一样,都具有双向数据更新的特点。
鸿蒙开发中,如何进行组件通信
@Prop
的方式可以实现父子组件单项数据更新。@Link
装饰器可以实现父子组件的双向数据更新。@Provide
和@Consume
可以实现夸组件之间的通信。LocalStorage
和Appstorage
来实现页面数据的共享。
@Build装饰器
前言:
类似一个组件封装,使用@Builder
定义的模块,直接使用this.
调用即可。
简单使用格式案例
@Builder tabBuilder(){
Column({space:3}) {
Image($r("app.media.home"))
.size({ width: 25, height: 25 })
Text("首页")
.fontColor("#3DA9F1")
.fontWeight(400)
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
@Builder myBuilder(){
Text("首页")
.fontSize(30)
.fontColor(Color.Red)
}
build() {
Column() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
// Text('首页的内容').fontSize(30)
this.myBuilder()
}
// 提取公共的布局代码
.tabBar(this.tabBuilder())
TabContent() {
Text('推荐的内容').fontSize(30)
}
.tabBar('产品')
TabContent() {
Text('购物车').fontSize(30)
}
.tabBar('购物车')
TabContent() {
Text('我的内容').fontSize(30)
}
.tabBar("我的")
}
}
.height('100%')
.width('100%')
}
}
简单使用效果
可传参使用案例
实现应用底部菜单
import { BreakPointSystem } from '../utils/BreakPointSystem';
import { BasicConstants } from "../Constans/BasicConstans"
@Entry
@Component
struct Index {
@State message: string = 'Entry Index';
@StorageProp("currentBreakpoint") currentBreakpoint: string = "sm"
@State currentIndex:number = 0
private breakPointSystem = new BreakPointSystem()
private tabsController: TabsController = new TabsController()
aboutToAppear(): void {
// 注册屏幕监听
this.breakPointSystem.register()
}
// 这个函数里面存放的是布局代码
@Builder
tabBuilder(title: string, targetIndex: number, selectImage: Resource, normalImg: Resource) {
Column({ space: 3 }) {
Image(this.currentIndex==targetIndex?selectImage:normalImg)
.size({ width: 25, height: 25 })
Text(title)
.fontColor(this.currentIndex==targetIndex?"#3DA9F1":"black")
.fontWeight(400)
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.onClick(()=>{
this.currentIndex = targetIndex
// 控制器的下标也更新,控制器显示页面下标
this.tabsController.changeIndex(targetIndex)
})
}
@Builder
myBuilder() {
Text("首页")
.fontSize(30)
.fontColor(Color.Red)
}
build() {
Column() {
Tabs({ barPosition: BarPosition.End,controller:this.tabsController }) {
TabContent() {
// Text('首页的内容').fontSize(30)
this.myBuilder()
}
// 提取公共的布局代码
.tabBar(this.tabBuilder("首页",0,$r("app.media.homeActive"),$r("app.media.home")))
TabContent() {
Text('推荐的内容').fontSize(30)
}
.tabBar(this.tabBuilder("产品",1,$r("app.media.productActive"),$r("app.media.product")))
TabContent() {
Text('购物车').fontSize(30)
}
.tabBar(this.tabBuilder("购物车",2,$r("app.media.gwcActiv"),$r("app.media.gwc")))
TabContent() {
Text('我的内容').fontSize(30)
}
.tabBar(this.tabBuilder("我的",3,$r("app.media.myActive"),$r("app.media.my")))
}
.onChange((index:number)=>{
// tab滑动的时候
this.currentIndex = index
})
}
.height(BasicConstants.FULL_WIDTH)
.width(BasicConstants.FULL_HEIGHT)
.backgroundColor("#f1f3f5")
}
}
传参案例实现效果:
项目数据封装
拿到项目后,根据项目页面设计进行数据的抽象,需要定义好数据的约束,如果后端还没提供接口,我还需要构造app
的数据,需要采用面向对象类来定义我们数据的约束。
import { formatDate,getRandomDateCurrentMonth } from "../utils/DateFormat"
enum TaskStatusType{
DONE = "done",
UNDONE = "undone",
FUTURE = "future",
DOING = "doing",
}
export class TaskModel{
// 每个任务的编号
id:string = ""
// 每个任务的名称
taskName:string = ""
// 每个任务的日期
taskTime:string = ""
// 任务详情
taskMsg:string = ""
// 每个任务的开始时间
taskBeginDate:string = ""
// 每个任务的结束时间
taskEndData:string = ""
// 每个任务的类型,1:生活类型,2:工作类型
TaskType:number = 1
// 任务的标签名称集,学习,生活,工作,娱乐
taskTitle:number = 1
// 人物的状态 done完成,undone未完成,doing正在进行 future 未来任务
taskStatus:string = ""
constructor(id: string, taskName: string, taskTime: string, taskBeginDate: string, taskEndData: string,
TaskType: number, taskTitle: number,taskMsg:string,) {
this.id = id
this.taskName = taskName
this.taskTime = taskTime
this.taskBeginDate = taskBeginDate
this.taskEndData = taskEndData
this.TaskType = TaskType
this.taskTitle = taskTitle
this.taskMsg = taskMsg
// 状态不用传递,自己计算状态
this.updateTaskStatus()
}
// 用于根据当前任务时间,计算我们的任务状态
updateTaskStatus(){
const currentData = formatDate(new Date().getTime(),"yy-mm-dd")
// 计划完成时间,比当前时间更大,future状态
if(this.taskTime > currentData){
this.taskStatus = TaskStatusType.FUTURE
// 计划完成时间,等于当前时间,doing状态
}else if(this.taskTime === currentData){
this.taskStatus= TaskStatusType.DOING
// 计划完成时间小于当前时间,状态undone
}else if(this.taskTime <= currentData){
this.taskStatus = TaskStatusType.UNDONE
}
}
}
需要将数据约束的类给暴露出去,在页面中就可以引入进来约束。
import CreateTaskModel ,{ TaskModel } from "../viewmodel/TaskModel"
@State tasks:Array<TaskModel> = []
在进行数据构造的时候,考虑了日期需要频繁的使用,定义了日期格式化的工具来进行处理。
数据结构定义好了过后,为了让页面功能能够基于目前的数据进行实战,在Model中构造了测试数据。
// 目前临时用,等以后有接口,发送网络请求去后端获取数据
class CreateTaskModel{
private tasks:TaskModel[] = []
initData(){
for(let index=0;index<20;index++){
const fullDate = getRandomDateCurrentMonth()
const task = new TaskModel(
`${index+1}`,
`计划要完成的任务${index+1}`,
fullDate,
"9:00",
"12:00",
1,
1,
`这是人物的详情${index}`)
this.tasks.push(task)
}
return this.tasks
}
}
export default new CreateTaskModel()
页面中要使用我们钩爪的数据,可以在生命周期中调用
@State tasks:Array<TaskModel> = []
aboutToAppear(): void {
const tasks = CreateTaskModel.initData()
this.tasks = tasks
}
使用:
@ObjectLink
和Observed
装饰器一般要一起使用,才能实现对象监听。@Observed
放在类上面(model
数据约束上面)。ObjectLink
放在组件中定义数据,无法在entry
组件中使用。
工具的封装
封装了常用的日期格式化工具
export class Opt{
yy:string = ''
mm:string = ''
dd:string = ''
HH:string = ''
MM:string = ''
SS:string = ''
}
export function formatDate(timestamp:number,format = 'yyy-mm-dd'){
let res = ""
try {
// 转化为日期对象
const date = new Date(timestamp);
// 定义了日期字段
const opt:Opt = {
yy:date.getFullYear().toString(),
mm:(date.getMonth()+1).toString(),
dd:date.getDate().toString(),
HH:date.getHours().toString(),
MM:date.getMinutes().toString(),
SS:date.getSeconds().toString(),
}
// 存放正则表达式要匹配的字段, y+代表一个或者多个y
const regKeys:string[] = ['y+','m+','d+','H+','M+','S+'];
for(let i = 0;i<regKeys.length;i++){
const regKey = regKeys[i];
// 钩爪正则表达式对象 new RegExp
const reg = new RegExp(regKey);
let ret = reg.exec(format)
if(ret){
switch (regKey){
case "y+":
format = format.replace(reg,ret.length !==1?opt.yy : opt.yy.padStart(ret.length,"0"))
case "m+":
format = format.replace(reg,ret.length !==1?opt.mm :opt.mm.padStart(2,"0") )
case "d+":
format = format.replace(reg,ret.length !==1?opt.dd : opt.dd.padStart(2,"0"))
case "H+":
format = format.replace(reg,ret.length !==1?opt.HH : opt.HH.padStart(2,"0"))
case "M+":
format = format.replace(reg,ret.length !==1?opt.MM : opt.MM.padStart(2,"0"))
case "S+":
format = format.replace(reg,ret.length !==1?opt.SS : opt.SS.padStart(2,"0"))
}
}
}
res = format
}catch (err){
console.log("Error FormatData")
}
return res
}
/*
* 为了获取到随机的一个日期
* */
export function getRandomDateCurrentMonth(){
let today = new Date()
const year = today.getFullYear()
let mouth = today.getMonth()+1
let minDay = 1
let maxDay = new Date(year,mouth+1,0).getDate()
let randomDay = Math.floor(Math.random()*(maxDay-minDay+1))+minDay
return `${year}-${String(mouth).padStart(2,"0")}-${String(randomDay).padStart(2,"0")}`
}
页面中渲染数据
父组件:
import { TaskStatisties } from "../view/TaskStatisties"
import { TaskItem } from "../view/TaskItem"
import CreateTaskModel ,{ TaskModel } from "../viewmodel/TaskModel"
@Entry
@Component
struct Page02_TaskList{
@State tasks:Array<TaskModel> = []
aboutToAppear(): void {
const tasks = CreateTaskModel.initData()
this.tasks = tasks
console.log("tasks",this.tasks)
}
build() {
Column(){
TaskStatisties({tasks:this.tasks})
}
}
}
子组件:
import { TaskModel } from "../viewmodel/TaskModel"
@Component
export struct TaskStatisties{
@Prop tasks:Array<TaskModel>
computedStatusCount(status:string){
const count = this.tasks.reduce((sum:number,item:TaskModel)=>{
if(item.taskStatus === status){
sum+=1
}
return sum
},0)
return count
}
build() {
// 我的计划
Column(){
Row(){
// 我的计划左侧
Column({space:10}){
// 存放已完成的任务
Column(){
Image($r("app.media.startIcon"))
.width(70)
.margin({top:10,left:10,bottom:10})
Text("已完成")
.fontWeight(FontWeight.Bold)
.fontSize(18)
.fontColor($r("app.color.text_color"))
.margin({left:15})
Text(`${this.computedStatusCount("done")}任务`)
.fontWeight(FontWeight.Bold)
.fontSize(18)
.fontColor($r("app.color.text_color"))
.margin({left:15,top:5})
}
.height(164)
.width(168)
.backgroundColor("#83C7E3")
.borderRadius(15)
.alignItems(HorizontalAlign.Start)
}
.width("50%")
.height("100%")
}
.height(300)
.width("100%")
}
}
}
效果:
ForEach循环渲染
前言
在鸿蒙开发中如果要涉及循环渲染数据可以使用两种方案。
ForEach
的方式来实现,直接封装了循环的语法,接受数组来进行动态遍历。LazyForEach
来实现,循环懒加载,可以实现大数据量的情况下,实现谢娜功能优化。
格式
forEach这个接口需要传递三个参数
ForEach(this.tasks,(item:TaskModel,index:number)=>{},(item:TaskModel)=>item.id)
第一个参数:必须提供一个数组。
第二个参数:循环体,接受遍历产生的对象,index下表。
第三个参数:可选项,用于指定一个字段为循环的key。
案例效果:
案例代码:
import { formatDate } from '../utils/DateFormat'
import { TaskModel } from '../viewmodel/TaskModel'
@Component
export struct TaskItem {
@Prop tasks: Array<TaskModel>
computedTodayTask() {
// 获取当前日期
const today = formatDate(new Date().getTime(), "yyyy-mm-dd")
// tasks中赛选满足条件的数据
return this.tasks.filter((item: TaskModel) => item.taskTime === today)
}
build() {
Column({ space: 10 }) {
ForEach(this.tasks, (item: TaskModel, index: number) => {
Column({ space: 10 }) {
// 存放图片和文字
Row({ space: 20 }) {
Image($r("app.media.startIcon"))
.width(30)
.height(30)
Text(item.taskName)
.fontColor($r("app.color.text_color"))
.fontWeight(FontWeight.Bold)
.fontSize(18)
}
.width("100%")
Text(`${item.taskBeginDate}-${item.taskEndData}`)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Gray)
Row() {
Text(`${item.TaskType==1?"生活":"工作"}`)
.fontColor(Color.White)
}
.width(80)
.height(30)
.backgroundColor(Color.Brown)
.borderRadius("50%")
.justifyContent(FlexAlign.Center)
}
.width("100%")
.height(120)
.backgroundColor(Color.White)
.borderRadius(10)
.alignItems(HorizontalAlign.Start)
.padding({ left: 15, top: 10 })
}, (item: TaskModel) => item.id)
}
}
}
if / else条件渲染
前言:
当if
、else if
后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:
- 评估
if
和else if
的状态判断条件,如果分支没有变化,无需执行以下步骤。如果分支有变化,则执行2
、3
步骤。- 删除此前构建的所有子组件。
- 执行新分支的构造函数,将获取到的组件添加到if父容器中。如果缺少适用的
else
分支,则不构建任何内容。
格式:
if(item.taskStatus === "done"){
Row() {
Text(`${item.TaskType==1?"生活":"工作"}`)
.fontColor(Color.White)
}
.width(80)
.height(30)
.backgroundColor(Color.Brown)
.borderRadius("50%")
.justifyContent(FlexAlign.Center)
}else if(item.taskStatus === "undone"){
Text("未完成")
}
效果:
条件可以包括Typescript
表达式。对于构造函数中的表达式,此类表达式不得更改应用程序状态。
最大特点可以直接在build
这个函数中使用,用于判断页面组件是否需要进行渲染。
等待完善添加~