HarmonyOS Next V2 状态管理实战
作者:万少
- 2024-12-15 江苏
本文字数:6709 字
阅读完需:约 22 分钟

HarmonyOS Next V2 状态管理实战
介绍
以下案例适合刚开始手鸿蒙开发的小伙伴,有大量的最新逻辑锻炼、鸿蒙核心语法、使用最新鸿蒙的 @Local、@Computed 等装饰器来完成。
另外,考虑在学习知识的知识时候,优先关注核心功能,所以提供的布局都会适当简化,但是能保证把核心功能展示出来。
每一个案例会点出终点和核心知识,让学习者可以练习完毕,可以得到什么。
学习的路线
先看效果
复现效果
如果有对代码产生的疑问,可以在评论区内直接提出,有疑问,必回复
如果能帮助到你,就很好了。😄
点击高亮
练习基本的线性布局
练习基本的数组使用
练习列表渲染语法 ForEach
练习布局中的状态切换 三元表达式
掌握通用的点击高亮
@Entry@ComponentV2struct Index { @Local list: string[] = ["小明", "小红", "小黑", "小黄"] // 声明一个数字 表示你当前选中的按钮的下标 @Local select: number = 1
build() { Column() {
ForEach(this.list, (item: string, index: number) => { Button(item + " " + (this.select == index)) .backgroundColor(this.select == index ? "#ffcd43" : "#007dfe") .onClick(() => { this.select = index })
}) } .width("100%") .height("100%") .padding({ top: 100 })
}}
复制代码
待办列表
新手上手新的编程语言的必做案例 crud - 增删该查
练习 V2 装饰器、@Local、@Computed、事件等
打通 状态 -> UI 、 UI-> 状态 的一些交互
@ObservedV2class Task { @Trace task: string = "" @Trace isFinished: boolean = false}
@Entry@ComponentV2struct Index { // 任务列表 @Local list: Task[] = [ // { task: "数组学习", isFinished: true }, // { task: "函数学习", isFinished: false } ] // 输入框输入的内容 @Local inpValue: string = ""
// 未完成数量
@Computed get statistics() { let undoneNum = this.list.filter(v =>!v.isFinished).length let doneNum = this.list.length - undoneNum return [undoneNum, doneNum] }
// 清理已经完成的任务 onClear = () => { // 筛选 留下未完成 this.list = this.list.filter((v =>!v.isFinished)) } // 删除 onDelete = (index: number) => {
this.list.splice(index, 1)
}
build() { Column() { Row() { Button("清理已完成") .onClick(this.onClear) }
Row() { Text(`未完成的数量 ${this.statistics[0]}`) Text(`完成的数量 ${this.statistics[1]}`) } .width("80%") .justifyContent(FlexAlign.SpaceAround)
Row() { TextInput() .width(200) .onChange(value => {
this.inpValue = value }) Button("确认") .onClick(() => { // 熟练 探囊取物!! // 先判断当前任务有没有出现过 const isExit = this.list.some(element => element.task == this.inpValue) if (!isExit) { const p = new Task() p.task = this.inpValue this.list.push(p) } }) }
ForEach(this.list, (item: Task, index: number) => { Row() { Text(item.task) .fontColor(item.isFinished ? "#666" : "#000") .decoration({ type: item.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None }) .fontStyle(item.isFinished ? FontStyle.Italic : FontStyle.Normal) .onClick(() => { this.onDelete(index) })
Button(item.isFinished ? "继续" : "完成") .backgroundColor(item.isFinished ? "#ffa601" : "#007dfe") .onClick(() => {
this.list[index].isFinished = !this.list[index].isFinished
}) } })
} .width("100%") .height("100%") .padding({ top: 100 })
}}
复制代码
B 站显示更多
练习 Flex 布局的换行
练习 Scroll 布局的水平滚动
练习绝对定位-水平居中
练习条件渲染
@Entry@ComponentV2struct Index { @Local list: string[] = ["首页", "动画", "番剧", "国创", "音乐", "舞蹈", "游戏", "知识", "科技", "运动", "汽车", "生活", "美食", "动物圈", "鬼畜", "时尚", "娱乐", "影视", "纪录片", "电影", "电视剧", "直播", "课堂"] // 是否显示更多 @Local isShowMore: boolean = false
build() { Column() { Row({ space: 5 }) { Scroll() { // true 换行 Flex({ wrap: this.isShowMore ? FlexWrap.Wrap : FlexWrap.NoWrap }) { ForEach(this.list, (item: string) => { Text(item) .margin(10) }) } }
.scrollable(ScrollDirection.Horizontal) .layoutWeight(1) // .backgroundColor(Color.Yellow) .padding({ bottom: this.isShowMore ? 30 : 0 })
if (this.isShowMore) { Image($r("app.media.app_icon")) .width(20) .position({ left: "50%", bottom: 0 }) .translate({ x: -10 }) .onClick(() => { this.isShowMore = !this.isShowMore }) } else { Image($r("app.media.app_icon")) .width(20) .onClick(() => { this.isShowMore = !this.isShowMore }) }
} .width("100%") .backgroundColor(Color.Red)
} .width("100%") .height("100%")
}}
复制代码
仿考研日程
练习如何根据需求来拆分数据
简单的渲染
// 二级目录interface SubContent { subTitle: string subContent: string}
// 一级目录interface OneContent { title: string content: SubContent[]}
@Entry@ComponentV2struct Index { @Local list: OneContent[] = [ { title: "统考", content: [ { subTitle: "国家线", subContent: "2024。。。。" }, { subTitle: "考研复试流程图", subContent: "" } ] }, { title: "统考22", content: [ { subTitle: "国家线22", subContent: "2024。。。。22" }, { subTitle: "考研复试流程图22", subContent: "" } ] } ] // 选中标题的下标 @Local current: number = 0
build() {
Column() { // 1 标题 Row({ space: 10 }) { ForEach(this.list, (item: OneContent, index: number) => { Text(item.title) .fontColor(this.current == index ? "#0094ff" : "#000") .onClick(() => { this.current = index }) }) }
// 2 内容 Column() { ForEach(this.list[this.current].content, (item: SubContent) => { Column() { Text(item.subTitle) Text(item.subContent) } }) }
} .width("100%") .height("100%") .backgroundColor("#eee")
}}
复制代码
仿 vantUI -倒计时
练习定时器
练习一点关于时间处理的逻辑功能
@Entry@ComponentV2struct Index { @Local str: string = "" // 时间 毫秒 time: number = 5 * 60 * 60 * 1000 tid: number = -1
build() {
Column() { Button("开始倒计时") .onClick(() => { // setInterval 时间间隔最少 是10ms this.tid = setInterval(() => { this.time -= 10 // 计算小时 整数 parseInt Math.floor() // parseInt 只能传递字符串类型 const hour = Math.floor(this.time / 1000 / 60 / 60) const minute = Math.floor(this.time / 1000 / 60 % 60) const seconde = Math.floor(this.time / 1000 % 60) const milliSeconde = this.time % 1000 this.str = `${hour}:${minute}:${seconde}.${milliSeconde}`
}, 10) })
Button("暂停") .onClick(() => { clearInterval(this.tid) }) Text(this.str) .fontSize(30) } }}
复制代码
仿掘金抽奖
练习 flex 布局-换行
练习随机数
练习数组+随机数实现随机获取元素
@Entry@ComponentV2struct Index { @Local list: string[] = [ "4090", "4399", "大彩电", "iphone16", "meta70", "Mac", "小牛电动车", "迪拜7日游", "北京房子一套" ] @Local current: number = 0
// 根据下标设置奖品高亮 setHighline(index: number) { this.current = index }
build() { Column() { Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.list, (item: string, index: number) => { Text(item) .width("33.33%") .padding({ top: 20, bottom: 20 }) .border({ width: 1 }) .backgroundColor(this.current == index ? "#e37815" : "#fff") }) }
Button("开始抽啦") .onClick(() => {
// 开始抽奖 let tid = setInterval(() => { // 等于 数组长度范围内的随机数 const index = Math.floor(Math.random() * this.list.length)
this.setHighline(index)
}, 10)
// 开启5s种延时器 - 停止定时器 setTimeout(() => { clearInterval(tid) }, 5000) }) }
}}
复制代码
仿掘金抽奖 - 不重复抽奖
加强逻辑处理,如何实现不重复抽奖
练习一些数组的方法
练习使用 @Computed
@Entry@ComponentV2struct Index { @Local list: string[] = [ "4090", "4399", "大彩电", "iphone16", "meta70", "Mac", "小牛电动车", "迪拜7日游", "北京房子一套" ] @Local selectedList: number[] = [] @Local current: number = 0
// 奖池 @Computed get newList() { const newList: number[] = [] for (let index = 0; index < this.list.length; index++) { let item = this.selectedList.find(v => v === index)
if (!item) { newList.push(index) } } return newList }
// 根据下标设置奖品高亮 setHighline(index: number) { this.current = index }
build() { Column() { Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.list, (item: string, index: number) => { Text(item) .width("33.33%") .padding({ top: 20, bottom: 20 }) .border({ width: 1 }) .backgroundColor( this.selectedList.includes(index) ? "#e37815" : (this.current == index ? "#e37815" : "#fff")) }) }
Button("开始抽啦") .onClick(() => {
// 开始抽奖 let tid = setInterval(() => { // 等于 数组长度范围内的随机数
const index = Math.floor(Math.random() * this.newList.length)
this.setHighline(this.newList[index])
}, 10)
// 开启5s种延时器 - 停止定时器 setTimeout(() => { clearInterval(tid) this.selectedList.push(this.current) }, 1000) }) }
}}
复制代码
仿 vantUI-分页组件-简单版本
练习基本的鸿蒙线性布局
练习条件渲染
练习逻辑能力
@Entry@ComponentV2struct Index { @Local list: string[] = ['1', '2', '3', '4', '5'] @Local current: number = 4
build() { Column() { Row({ space: 2 }) { Button("上一页") .enabled(this.current != 0) .backgroundColor("#fff") .fontColor("#0094ff") .stateStyles({ disabled: { .backgroundColor("#eee") } }) .onClick(() => { this.current--
}) ForEach(this.list, (item: string, index: number) => { Button(item) .backgroundColor(this.current == index ? "#0094ff" : "#fff") .fontColor(this.current == index ? "#fff" : "#0094ff") .onClick(() => { this.current = index }) }) Button("下一页") .enabled(this.current != this.list.length - 1) .backgroundColor("#fff") .stateStyles({ disabled: { .backgroundColor("#eee") } })
.fontColor("#0094ff") .onClick(() => { this.current++ }) } } .width("100%") .height("100%") .padding({ top: 100 }) .backgroundColor("#eee") }}
复制代码
仿 vantUI-分页组件-复杂版本
练习基本的鸿蒙线性布局
练习条件渲染
练习复杂的逻辑能力
@Entry@ComponentV2struct Index { @Local list: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] @Local test: number = 1 @Local showList: number[] = [1, 2, 3]
change() { if (this.test > 1 && this.test < this.list.length) { this.showList = [this.test - 1, this.test, this.test + 1] } }
previous() { this.test-- this.change() }
next() { this.test++ this.change() }
build() {
Column() { Row() { Button("上") .enabled(this.test != 1) .backgroundColor("#ccc") .onClick(() => { this.previous() }) if (this.test > 2) { Button("...") .backgroundColor("#ccc") .onClick(() => { this.previous() }) }
ForEach(this.showList, (item: number) => { Button(item.toString()) .backgroundColor(this.test == item ? Color.Blue : "#ccc") .onClick(() => { this.test = item this.change() }) })
if (this.test < this.list[this.list.length-1] - 1) { Button("...") .backgroundColor("#ccc") .onClick(() => { this.next() }) }
Button("下") .enabled(this.test != this.list[this.list.length-1]) .backgroundColor("#ccc") .onClick(() => { this.next() }) } }
}}
复制代码
小结
如果部分内容中的图片不存在,自己随机替换即可。
划线
评论
复制
发布于: 刚刚阅读数: 6
版权声明: 本文为 InfoQ 作者【万少】的原创文章。
原文链接:【http://xie.infoq.cn/article/34e82a8ace7ae6e871a65b3fe】。文章转载请联系作者。
万少
关注
还未添加个人签名 2021-12-02 加入
还未添加个人简介









评论