HarmonyOS Next V2 状态管理实战
HarmonyOS Next V2 状态管理实战
介绍
以下案例适合刚开始手鸿蒙开发的小伙伴,有大量的最新逻辑锻炼、鸿蒙核心语法、使用最新鸿蒙的@Local、@Computed 等装饰器来完成。
另外,考虑在学习知识的知识时候,优先关注核心功能,所以提供的布局都会适当简化,但是能保证把核心功能展示出来。
每一个案例会点出终点和核心知识,让学习者可以练习完毕,可以得到什么。
学习的路线
- 先看效果
- 复现效果
- 如果有对代码产生的疑问,可以在评论区内直接提出,有疑问,必回复
- 如果能帮助到你,就很好了。😄
点击高亮
- 练习基本的线性布局
- 练习基本的数组使用
- 练习列表渲染语法 ForEach
- 练习布局中的状态切换 三元表达式
- 掌握通用的点击高亮
@Entry
@ComponentV2
struct 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-> 状态 的一些交互
@ObservedV2
class Task {
@Trace task: string = ""
@Trace isFinished: boolean = false
}
@Entry
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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
@ComponentV2
struct 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()
})
}
}
}
}
小结
如果部分内容中的图片不存在,自己随机替换即可。