知乎案例
2024-01-17 本文已影响0人
家乡的蝈蝈
1.1、定义一个评论列表
export class ReplyItem {
// next版本在定义属性时需要初始值
id: number = 0
avatar: string | Resource = "" // 联合类型,可以是类型中的一个
author: string = ""
content: string = ""
time: string = ""
area: string = ""
likeNum: number = 0
likeFlag?: boolean = false
}
- 定义一个评论列表数据- 在Entry组件中
@State commentList: ReplyItem[] = []
- 在主页中渲染
ForEach(this.commentList, (obj: ReplyItem) => {
CommentItem({ item: obj })
})
- CommentItem组件接收传入数据
@Component
struct CommentItem {
// 父组件给子组件传参数,在子组件中定义一个属性即可,非响应式数据
// item:ReplyItem = {id:0,avatar:"",....}
// 默认public属性
public item:Partial<ReplyItem> = {} //Partial是个泛型工具,把类型中的所有属性都变成可选;等价于在类中的属性 id ?: number = 0
build() {
Row() {
Image(this.item.avatar)
.width(32)
.aspectRatio(1)
.borderRadius(16)
Column({space:10}) {
Text(this.item.author)
.fontWeight(FontWeight.Bold)
Text(this.item.content)
.fontSize(16)
.fontColor("#565656")
.lineHeight(20)
Row() {
Text(`${this.item.time} .ip属地${this.item.area}`).fontSize(12).fontColor("#c3c4c5")
Row() {
Image($r("app.media.favorite_block"))
.width(12)
.aspectRatio(1)
.fillColor('#c3c4c5')
.margin({
right:5
})
Text(this.item.likeNum?.toString()) // ?表示可选,如果取不到,后面的不执行
.fontSize(12)
.fontColor('#c3c4c5')
}
}.justifyContent(FlexAlign.SpaceBetween)
.width("100%")
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({left:10})
}.padding(15)
.alignItems(VerticalAlign.Top)
}
}
- 自定义NavBar组件
@Component
struct NavBar {
build() {
Row() {
Row() {
Image($r("app.media.ic_public_arrow_left"))
.fillColor("#848484")
.width(16)
.height(16)
}
.justifyContent(FlexAlign.Center)
.width(24)
.aspectRatio(1)
.backgroundColor("#f5f5f5")
.borderRadius(12)
.margin({
left:15
})
Text("评论回复")
.layoutWeight(1)
.textAlign(TextAlign.Center)
.fontSize(18)
.padding({
right:39
})
}
.height(40)
.border({
color:"#f4f4f4",
width:{
bottom:0.5
}
})
}
}
- 由于数据过多,我们可以使用Scroll 组件来包裹整个的Column,注意Column的高度100%要去掉
build() {
Scroll() { //只能放置一个子组件
Column() {
NavBar()
CommentItem({
item: {
id: 1,
avatar: $r('app.media.icon'),
author: '周杰伦',
content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',
time: '11-30',
area: '海南',
likeNum: 100
}
})
// 分割线
Divider()
.strokeWidth(6)
.color("#f4f4f4")
Row() {
Text("回复7")
.width('100%')
.fontWeight(FontWeight.Bold)
}.height(40).padding({left:20})
ForEach(this.commentList,(item:ReplyItem) => {
// CommentItem({item:item}) // 在实例化子组件时,给子组件的属性赋值
CommentItem({item}) // 在es6中,当属性和值相同时可以简写
})
}
}
}
1.2、底部回复按钮
- 底部按钮固定定位的模式可以采用Stack栈布局,特点是后面的元素级别高于前面元素,会贴在上一个元素上,并且使用alignContent属性可以直接让 后加的元素去到想去的位置
// 回复评论组件
@Component
struct ReplyInput {
build() {
Row() {
TextInput({placeholder:'回复~'})
.layoutWeight(1) // 占据除'发布'按钮的所有空间
.backgroundColor('#4f5f6')
Text('发布')
.fontColor('#6ecff6')
.margin({
left:10
})
}
.border({
color:'#f4f5f6',
width: {
top:1
}
})
.height(50)
.backgroundColor(Color.White)
.width('100%')
.padding({
left:10,
right:10
})
}
}
1.2.1、使用Stack包裹整个的主页组件,并设置alignContent为 Alignment.Bottom
image.png使用Stack包裹整个的主页组件,并设置alignContent为 Alignment.Bottom,Scroll被盖住的地方 需要加个padding
1.3、实现点赞
使用函数传递的方式
- 在父组件定义一个函数changeLike, 传递给CommentItem
changeLike(obj:ReplyItem) {
if (obj.likeFlag) {
obj.likeFlag = false
obj.likeNum--
} else {
obj.likeFlag = true
obj.likeNum++
}
// State数据只能监听到第一层的变化
// 怎么让数据具备驱动型
const index = this.commentList.findIndex(item => item.id === obj.id)
// this.commentList[index] = {...obj} // 禁用延展运算符,next不支持
this.commentList.splice(index,1,obj) // splice表示从哪个位置删除,删除几个,使用obj来替换
} // 由于不一代延展运算符只能用于数组,对于对象不再支持,所以采用对数组采用替换更新的方式来进行响应式更新
- 子组件接收
ForEach(this.commentList,(item:ReplyItem) => {
// CommentItem({item:item}) // 在实例化子组件时,给子组件的属性赋值
// CommentItem({item}) // 在es6中,当属性和值相同时可以简写
// CommentItem({item,changLike:this.changeLike.bind(this)}) // 写法错误,
CommentItem({item, changeLike:(obj:ReplyItem) => {
this.changeLike(obj)
}})
})
- 子组件调用
struct CommentItem {
changeLike:(params:ReplyItem) => void = () => {}
build() {
Row() {
Text(this.item.likeNum?.toString()) // ?表示可选,如果取不到,后面的不执行
.fontSize(12)
.fontColor('#c3c4c5')
}
.onClick(() => {
// 点赞
// this.item属性都是可选的
this.changeLike(this.item as ReplyItem); // as 为类型断言
})
}.justifyContent(FlexAlign.SpaceBetween)
.width("100%")
}
}
1.4、实现回复
- 定义State状态,双向绑定input
// 回复评论组件
@Component
struct ReplyInput {
@State commentStr:string = ""
// 在回复组件中声明了一个变量add,它的类型是函数,给了一个初始值是函数
add: (item:ReplyItem) => void = () => {}
TextInput({placeholder:'回复~', text:this.commentStr})
.layoutWeight(1) // 占据除'发布'按钮的所有空间
.backgroundColor('#4f5f6')
.onChange((value) => {
this.commentStr = value
})
- 父组件定义函数, 传递子组件
addComment(item:ReplyItem) {
this.commentList.unshift(item) // 在数组的头部添加元素
}
- 父组件传递给回复子组件
ReplyInput({add: (item:ReplyItem) => {
this.addComment(item)
}})
- 回复子组件逻辑
Text('发布')
.fontColor('#6ecff6')
.margin({
left:10
})
.onClick(() => {
// AlertDialog.show({message:this.commentStr}) // alert弹窗
// 告诉父组件,我拿到值,你给我个方法我来调一下
// 要传入一个新的评论对象
if (this.commentStr !== "") {
let obj:ReplyItem = {
id: Date.now(),
content: this.commentStr,
avatar: $r("app.media.icon"),
likeNum:0,
likeFlag:false,
author:"高大的绿地",
time:`${ new Date().getMonth() + 1}-${ new Date().getDate()}`,
area:'上海'
}
this.add(obj)
this.commentStr = ""
}
})
1.5、foreach中key的疑问
大家发现没有,我们没有给key,我们的id是唯一的,给个id试试
(item: ReplyItem) => item.id.toString()
● 因为ForEach中更新UI时,key也必须连带更新,否则它会认为你没有发生变化,不去触发UI更新, 怎么办
● 直接把id-点赞-点赞数一起作为key就可以了
(item: ReplyItem) => JSON.stringify({ id: item.id, flag: item.likeFlag, num: item.likeNum })
(item: ReplyItem) => JSON.stringify(item)