1. 自定义组件基础
相关资源:
- 📎day10 图片素材.zip
1. 自定义组件基础
概念:在ArkUI中由框架直接提供的称为系统组件 -> Column,Button等,由开发者定义的称为自定义组件
作用:自定义组件可以对 UI和业务逻辑进行封装,从而复用组件

自定义组件语法:

- struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
- @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
- @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。
- @Preview:如果想要单独预览组件,可以使用@Preview 进行装饰
注意:自定义组件必须导出后,才能在其他组件中导入使用
export struct SonCom {}
1.1. 成员函数/变量
自定义组件除了必须要实现build()函数外,还可以定义其他的成员函数,以及成员变量
注意:
- 成员函数、变量均为私有
- 可以在父组件调用子组件时向成员变量传递数据
// HelloComponent.ets
@Component
export struct HelloComponent {// 成员变量info: string = '感觉自己闷闷哒'// 成员变量也可以是函数sayHello = ()=>{}// 状态变量@State message: string = 'Hello, World!';// 成员函数sayHi() {console.log('你好呀')}build() {// HelloComponent自定义组件组合系统组件Row和TextColumn() {Text(this.message)Text(this.info)Button('修改数据').onClick(() => {this.info = '(*  ̄3)(ε ̄ *)'this.message = 'Hello,ArkTS'this.sayHi()this.sayHello()})}}
}
// 页面的.ets
import { HelloComponent } from './components/HelloComponent'@Entry
@Component
struct CustomComponentDemo {build() {Column() {// 使用组件内部定义的初始值HelloComponent()// 使用传入的值,覆盖子组件的默认值HelloComponent({ info: '你好', message: 'ArkTS' })// 函数也可以传入HelloComponent({ sayHello:()=>{ console.log('传入的逻辑') } })}}
}
@Component
struct MyCom {// 定义函数用来接收父传入的函数sayHell:(p:string)=>void = ()=>{}build() {Column() {Button('点我向父组件传值').onClick(()=>{// 调用父组件的函数this.sayHell('我是子组件传给父组件的数据')})}
}@Entry
@Component
struct Index {build() {Column() {// 函数也可以传入MyCom({ // 接收子组件传入的数据sayHell:(msg:string)=>{ console.log('接收子组件传入的数据:'+msg) }})}}
}
1.2. 通用样式事件
自定义组件可以通过点语法的形式设置通用样式,通用事件
子组件().width(100).height(100).backgroundColor(Color.Orange).onClick(() => {console.log('外部添加的点击事件')})
试一试:
- 添加自定义组件,随意设置内容
- 使用自定义组件,通过点语法设置通用样式
@Component
struct MyComponent2 {build() {Button(`Hello World`)}
}@Entry
@Component
struct MyComponent {build() {Row() {MyComponent2().width(200).height(300).backgroundColor(Color.Red).onClick(() => {console.log('外部添加的点击事件')})}}
}
说明
ArkUI给自定义组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。
1.3. 案例-卡片组件
日常开发中,经常会在圆角的容器中展示不同的内容,我们一般称之为卡片,



@Component
struct PanelComp {title: string = ''more: string = ''clickHandler: () => void = () => {console.log('默认的逻辑')}build() {Column() {Row() {Text(this.title).layoutWeight(1).fontWeight(600)Row() {Text(this.more).fontSize(14).fontColor('#666666').onClick(() => {this.clickHandler()})Image($r('app.media.ic_public_arrow_right')).width(16).fillColor('#666666')}}.padding(10)Row() {Text('默认内容')}.height(100)}.borderRadius(12).backgroundColor('#fff').width('100%')}
}@Entry
@Component
struct Index {build() {Column({ space: 15 }) {PanelComp({title: '评价(2000+)', more: '好评率98%', clickHandler() {console.log('传入的逻辑')}})Row({ space: 15 }) {PanelComp({ title: '推荐', more: '查看全部' }).layoutWeight(1)PanelComp({ title: '体验', more: '4 条测评' }).layoutWeight(1)}}.height('100%').padding(15).backgroundColor('#f5f5f5')}
}
2. 构建函数-@BuilderParam 传递 UI
@BuilderParam 该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
链接
简而言之:就是自定义组件允许外部传递 UI
// SonCom 的实现略@Entry
@Component
struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来(尾随闭包)Button('传入的结构').onClick(() => {AlertDialog.show({ message: '点了 Button' })})}}}
}
2.1. 单个@BuilderParam参数
首先来看看单个的情况
使用尾随闭包的方式传入:
- 组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
- 内容直接在 {} 传入即可
注意:
- 此场景下自定义组件不支持使用通用属性。
@Component
struct SonCom {// 1.设置默认 的 Builder,避免外部不传入@BuilderdefaultBuilder() {Text('默认的内容')}// 2.定义 BuilderParam 接受外部传入的 ui,并设置默认值@BuilderParam ContentBuilder: () => void = this.defaultBuilderbuild() {Column() {// 3. 使用 @BuilderParam 装饰的成员变量this.ContentBuilder()}.width(300).height(200).border({ width: .5 })}
}// 使用自定义组件时,就可以使用如下方式传递 UI
// 不传递时会使用默认值
SonCom(){// 传入的 UI
}
试一试:
- 添加自定义组件:
-
- 定义默认的 Builder
- 添加BuilderParam,添加类型,并设置默认的 Builder
- 组件内部使用 BuilderParam
- 外部使用自定义组件,分别测试传递,不传递 UI 的情况
/** @BuilderParam 作用:可以在子组件中提供一个变量(尾随闭包),以便接收父组件传入的【UI结构】* 基本使用:* 1. 定义尾随闭包* 2. 在组件中使用这个尾随闭包* 3. 在父组件中调用这个组件传入UI结构** 注意点:单个写法特点:如果一个组件中,只有一个尾随闭包,那么这个尾随闭包可以不写变量名,直接使用即可* */@Component
export struct MyBuilder {// 1. 定义一个变量,用来接收父组件传递过来的UI结构// 默认值@Builder defaultBuilder(){Text('默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column(){// 2. 使用这个变量this.defaultUI()}}
}import { MyBuilder } from '../views/MyBuilder'@Entry
@Component
struct Index {build() {Column() {MyBuilder() {// 3. 调用子组件,并向子组件中传入自己定义的UI结构Button('按钮').backgroundColor(Color.Red).onClick(()=>{AlertDialog.show({message:'OK'})})}}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
2.2. 多个@BuilderParam 参数
子组件有多个BuilderParam,必须通过参数的方式来传入
核心步骤:
- 自定义组件-定义:
-
- 添加多个 @BuilderParam ,并定义默认值
- 自定义组件-使用
-
- 通过参数的形式传入多个 Builder,比如
SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })
@Component
struct SonCom {// 由外部传入 UI@BuilderParam titleBuilder: () => void = this.titleDefaultBuilder@BuilderParam contentBuilder: () => void = this.contentDefaultBuilder// 设置默认 的 Builder,避免外部不传入@BuildertitleDefaultBuilder() {Text('默认标题')}@BuildercontentDefaultBuilder() {Text('默认内容')}build() {Column() {Row() {this.titleBuilder()}.layoutWeight(1)Divider()Row() {this.contentBuilder()}.layoutWeight(1)}.width(300).height(200).border({ width: .5 })}
}@Entry
@Component
struct Index {@BuilderfTitleBuilder() {Text('传入的标题').fontSize(20).fontWeight(600).fontColor(Color.White).backgroundColor(Color.Blue).padding(10)}@BuilderfContentBuilder() {Text('传入的标题').fontSize(20).fontWeight(600).fontColor(Color.White).backgroundColor(Color.Blue).padding(10)}build() {Column({ space: 15 }) {// 指定名字传入UI结构SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })}}
}
2.3. 总结
// 定义子组件
@Component
export struct MyBuilder{ @Builder defaultBuilder(){Text('标题默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column(){this.defaultUI()}}
}// 父组件调用子组件MyBuilder(){Button('按钮')}
// 定义子组件
@Component
export struct MyBuilder{ @Builder defaultBuilder(){Text('标题默认的结构')}@Builder contentBuilder(){Text('内容默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilder@BuilderParam contentUI:() => void = this.contentBuilderbuild() {Column(){this.defaultUI()this.contentUI()}}
}// 父组件调用子组件@Builder defaultBuilder(){Button('标题0')}@Builder contentBuilder(){Text('内容')}MyBuilder({defaultUI:this.defaultBuilder,contentUI:this.contentBuilder})
2.4. 案例-卡片组件优化
使用刚刚学习的知识,让外部可以传递Builder到卡片组件内部

需求:
- 调整 卡片自定义组件,支持传入 UI PanelComp(){ // 此处传入 }
思路:
- 直接大括号(尾随闭包)传入只需要设置一个BuilderParam即可:
参考代码:
import { PanelComp } from '../views/MyPanel'@Entry
@Component
struct Index {build() {Column() {PanelComp({ leftTitle:'评价(2000+)',rightTitle:'好评率98%' ,clickHandler:(id:number)=>{ AlertDialog.show({message:'组件1的回调内容'+id}) }}){Column(){Text('组件1的内容')Button('按钮')}}PanelComp({ leftTitle:'体验',rightTitle:'4条评测',clickHandler:(id:number)=>{ AlertDialog.show({message:'组件2的回调内容'+id}) }}){Row(){Text('组件1的内容')Button('按钮')}}}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
/** 在组件中编写单个@BuilderParams写法步骤:* 1. 定义尾随闭包* 2. 准备一个默认的自定义构建函数* 3. 使用它* */@Preview
@Component
export struct PanelComp {leftTitle: string = '默认标题'rightTitle: string = '默认更多'clickHandler: (id:number) => void = () => {}@Builder defaultBuilder(){Text('默认的内容')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column() {Row() {Text(this.leftTitle).layoutWeight(1).fontWeight(600)Row() {Text(this.rightTitle).fontSize(14).fontColor('#666666')Image($r('app.media.ic_public_arrow_right')).width(16).fillColor('#666666')}.onClick(() => {// AlertDialog.show({ message: '子组件点击了' })this.clickHandler(100)})}.padding(10)Row() {// 接收父组件传递过来的UI结构// Text('默认内容')this.defaultUI()}.height(100)}.borderRadius(12).backgroundColor('#fff').width('100%')}
}
3. 页面路由
页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。
我们先明确自定义组件和页面的关系:
- 自定义组件:@Component 装饰的UI单元,
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成。
-
- @Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry

通过 Router 模块就可以实现这个功能. import { router } from '@kit.ArkUI'
步骤:
- 创建页面 -> 页面与组件不同的地方是有且只有一个入口组件( @Entry修饰),并且在
src/main/resources/base/profile/main_pages.json有配置好了页面路径
-
- 口诀:一入口,一配置


- router控制页面跳转

- 带参数跳转并获取

3.1. 页面栈
页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则
页面栈的最大容量为32个页面
3.1.1. pushUrl的情况
先来看看 pushUrl的情况
- 默认打开首页 → 首页入栈
- pushUrl 去详情页 → 详情页入栈
- back 返回上一页 → 详情页出栈
- 此时页面栈中应该只有一个页面
整一个过程中,都可以 router.getLength 进行查看

3.1.2. replaceUrl 的情况
再来看看replaceUrl的情况
- 默认打开首页 → 首页入栈
- replaceUrl 去详情页 → 详情页替换首页,首页销毁
- back 无法返回 → 没有上一页
跳转到登录页面时可以使用 replaceUrl,因为无需在页面栈中保存其他页面的页面栈信息了。

3.1.3. 页面栈相关 api
为了让咱们更好的获取页面栈的信息,router 模块也提供了对应的 api 以供使用
// 获取页面栈长度
router.getLength()// 获取页面状态
let page = router.getState();
console.log('current index = ' + page.index);
console.log('current name = ' + page.name);
console.log('current path = ' + page.path);// 清空页面栈
router.clear()
3.2. 路由模式
路由提供了两种不同的跳转模式:
- standard(标准实例模式)
- Single(单实例模式)
不同模式的决定了页面是否会创建多个实例:
- Standard:无论之前是否添加过,一直添加到页面栈【默认】
-
- 场景:适用于每次跳转都呈现全新内容或状态的场景,避免数据展示紊乱(数据变化)
- Single:如果之前加过页面,会使用之前添加的页面【需要添加参数手动修改】
-
- 场景:适用于那些需要保留页面状态或避免重复创建相同页面的场景(数据不变化)


路由模式语法:

3.3. 总结
路由跳转的方式有pushUrl和replaceUrl
pushUrl的跳转要考虑两种模式:标准模式,单例模式
- pushUrl({},模式为标准模式(默认的模式)) -> 页面栈中的表现形式

- pushUrl({},模式为单例模式) -> 页面栈中的表现形式

- replaceUrl() -> 跳转以后的前一个页面自动销毁了

4. 页面和自定义组件的生命周期
组件和页面在创建、显示、销毁的这一整个过程中,会自动执行 一系列的【生命周期钩子】,其实就是一系列的【函数】,让开发者有机会在特定的阶段运行自己的代码
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,如果不能触发aboutToAppear函数的时候,网络请求数据代码写在onPageShow中
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行,通常在这里发送网络请求获取数据
- onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量

注意:
- @Entry修饰的页面入口组件有:aboutToAppear、onDidBuild、aboutToDisappear、onPageShow、onPageHide、onBackPress
- @component修饰的组件有:aboutToAppear、onDidBuild、aboutToDisappear
// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageShow() {console.info('onPageShow');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageHide() {console.info('onPageHide');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onBackPress() {console.info('onBackPress');return true; // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理}// 组件生命周期aboutToAppear() {console.info('aboutToAppear');}// 组件生命周期onDidBuild() {console.info('onDidBuild');}// 组件生命周期aboutToDisappear() {console.info('aboutToDisappear');}
5. 状态管理补充

5.1. 装饰器总览
ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。
根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
咱们来看一张完整的装饰器说明图,咱们后续的学习就围绕着这张图来展开

- 管理组件状态:小框中
- 管理应用状态:大框中
5.2. @State 组件内状态-补充
@State 装饰器是管理组件内部状态的
@State装饰的变量,或称为状态变量
但是,并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。
观察变化注意点:
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为Object或数组时
-
- 可以观察到自身的赋值的变化
- 可以观察到对象属性赋值的变化,即Object.keys(observedObject)返回的所有属性
-
-
- 注意:嵌套属性的赋值观察不到
-
-
- 可以观察到数组本身的赋值和添加、删除、更新数组项的变化
-
-
- 注意:数组项中属性的赋值观察不到
-



interface iPerson {name: stringdog: iDog
}interface iDog {name: string
}@Entry
@Component
struct Index {@State person: iPerson = {name: '张三',dog: {name: '旺财'}}build() {Column() {Text(JSON.stringify(this.person))Button('修改数据').onClick(() => {// ✔️1. 如果修改的是整个对象 -> 能观察到变化通知UI更新// this.person = {// name: '李四',// dog: {// name: '旺财1'// }// }// ✔️ 2. 如果修改的是对象的一层属性 -> 能观察到变化,通知UI更新// this.person.name = '李四'// this.person.dog = { name:'萨摩耶' }// ❌ 3. 如果修改的是对象的二层属性 -> 不能观察到变化,UI不会更新this.person.dog.name = '萨摩耶'})}.height('100%').width('100%')}
}
interface iPerson {name:string
}@Entry
@Component
struct Index {@State list:iPerson[] = [{name:'张三'}]build() {Column() {Text(JSON.stringify(this.list)).onClick(()=>{// 修改数组项this.list[0] = {name:'李四'}//✔️this.list[0].name = '李四'//❌})}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
5.3. 状态共享@Prop -父子单向传递
@Prop 装饰的变量可以和父组件建立单向的同步关系
@Prop 装饰的变量是可变的,但是变化不会同步回其父组件


注意:
- 修改父组件数据,会同步更新子组件
- 修改子组件@Prop 修饰的数据,子组件 UI 更新,更新后的数据不会同步给父组件
- 通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新

随堂演示代码
/* @Prop作用:可以将父文件中的状态变量传递给子组件,让他们形成一个单向数据传递关系
单向 父组件--->子组件 子组件 ---❌--->父组件语法步骤:
1. 在子组件使用 @Prop定义一个变量
2. 在父组件中调用子组件,并传递参数(父组件中的状态变量)总结:
1. @Prop的应用场景:父组件要将一个变量传递给子组件,并且在父组件中改变这个变量的值,能让子组件也跟着改变
但是当子组件改了这个变量的值,父组件不会改变注意点:
1. 父组件传递给子组件的变量,只能是状态状态
2. 只有当父组件中的状态变量值发生改变,才能让子组件更新,但是如果子组件本身修改了这个变量的值也会刷新
3. 如果在子组件中改变的值要通知父组件更新,使用回调函数(父亲给我取的名字,和我自己改的名字同步了)** */
import { ChildCom } from './ChildCom'@Entry
@Component
struct Index {@State sonName:string = '张三'build() {Column({space:40}) {ChildCom({ name:this.sonName,updateName:(childName:string)=>{this.sonName = childName} })Divider().backgroundColor(Color.Red).height(2)Button('修改儿子的名字').onClick(()=>{this.sonName = '张四'})}.height('100%').width('100%')}
}
@Component
export struct ChildCom {// 定义了一个名字@Prop name: stringupdateName:(myname:string)=>void = ()=>{}build() {Row() {Text(this.name).fontSize(50)Button('改自己的名字').onClick(()=>{// this.name = '张思思'this.updateName('张思思')})}.height(50).width('100%')}
}
@Component
struct SonCom {@Prop info: stringchangeInfo = (newInfo: string) => {}build() {Button('info:' + this.info).onClick(() => {this.changeInfo('改啦')})}
}@Entry
@Component
struct FatherCom {@State info: string = '么么哒'build() {Column() {Text(this.info)SonCom({info: this.info,changeInfo: (newInfo: string) => {this.info = newInfo}})}.padding(20).backgroundColor(Color.Orange)}
}
interface User {name: stringage: number
}@Entry
@Component
struct Index {@StateuserInfo: User = {name: 'jack',age: 18}build() {Column({ space: 20 }) {Text('父组件').fontSize(30)Text('用户名:' + this.userInfo.name).white().onClick(() => {this.userInfo.name = 'rose'})Text('年龄:' + this.userInfo.age).white().onClick(() => {this.userInfo.age++})Child({user: this.userInfo,userChange: (newUser: User) => {this.userInfo.name = newUser.namethis.userInfo.age = newUser.age}})}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).backgroundColor(Color.Pink)}
}@Component
struct Child {@Propuser: UseruserChange: (newUser: User) => void = (newUser: User) => {}build() {Text('子组件:' + JSON.stringify(this.user)).padding(10).backgroundColor('#0094ff').fontColor(Color.White).onClick(() => {this.userChange({name: '路飞',age: 26})})}
}@Extend(Text)
function white() {.fontSize(20).fontColor(Color.White)
}
6. 案例-知乎评论

6.1. 静态结构+数据准备
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]@State rootComment: ReplyItem = {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem()// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(Array.from({ length: 10 }), () => {CommentItem()})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {build() {Row() {Image($r('app.media.avatar')).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text('作者').fontWeight(600)Text('内容').lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`11-30 . IP属地 北京`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor("#c3c4c5") // "#c3c4c5" 或 redText('10').fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.2. 评论数据渲染
需求:
- 顶部评论组件 渲染评论数据
- 评论列表组件 渲染评论列表

核心步骤:
- 评论组件:
-
- 定义@Prop 接收评论数据
- 父组件:
-
- 评论数据传递给子组件
- 评论组件
-
- 评论组件,接收数据并使用
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem) => {CommentItem({ item: item })})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItembuild() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor("#c3c4c5") // "#c3c4c5" 或 redText(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.3. 评论点赞-顶部评论
完成顶部评论
需求:
- 点击顶部的❤,切换点赞状态

核心步骤:
- 子组件:
-
- 定义changeLike函数,在点赞时调用
- 数据使用@Prop修饰,父组件状态更新会触发子组件更新
- 父组件:
-
- 传递changeLike 给子组件,内部实现点赞顶部评论逻辑
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.4. 评论点赞-列表
完成 列表的点赞效果

核心步骤:
- 在父组件:
-
- 列表区域实现changeLike函数,实现点赞逻辑
- 页面的更新,通过数组的 splice 方法来实现
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,changeLike: () => {item.likeFlag = !item.likeFlagif (item.likeFlag == true) {// 累加item.likeNum++} else {// 递减item.likeNum--}this.commentList.splice(index, 1, item)}})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.5. 发布评论
最后来完成发布评论
需求:
- 发布评论
- 评论个数同步更新

核心步骤:
- 发布评论
-
- 收集输入框内容
- 点击发布传递给父组件
- 父组件加入数据
- 评论个数同步更新
-
- 将 length 传入子组件
- 子组件内部通过 Prop 接收(响应更新)
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount({ count: this.commentList.length })// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,changeLike: () => {item.likeFlag = !item.likeFlagif (item.likeFlag == true) {// 累加item.likeNum++} else {// 递减item.likeNum--}this.commentList.splice(index, 1, item)}})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput({addReply: (inputValue: string) => {this.commentList.unshift({id: Date.now(),avatar: $r('app.media.avatar'),author: '小狗钱钱',content: inputValue,time: `${new Date().getMonth() + 1}-${new Date().getDate()}`,area: '浙江',likeNum: 0,likeFlag: false})}})}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {@Propcount: numberbuild() {Text() {Span('回复')Span(`${this.count}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {@State inputValue: string = ''addReply = (inputStr: string) => {}build() {Row() {TextInput({ placeholder: '回复', text: $$this.inputValue }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10}).onClick(() => {this.addReply(this.inputValue)})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
相关文章:
1. 自定义组件基础
相关资源: 📎day10 图片素材.zip 1. 自定义组件基础 概念:在ArkUI中由框架直接提供的称为系统组件 -> Column,Button等,由开发者定义的称为自定义组件 作用:自定义组件可以对 UI和业务逻辑进行封装&…...
MySQL MHA 部署全攻略:从零搭建高可用数据库架构
文章目录 1.MHA介绍2.MHA组件介绍3.集群规划4.服务器初始化5.MySQL集群部署5.1 安装MySQL集群5.2 配置一主两从5.3 测试MySQL主从5.4 赋予MHA用户连接权限 6.安装MHA环境6.1 安装MHA Node6.2 安装MHA Manager 7.配置MHA环境8.MySQL MHA高可用集群测试8.1 通过VIP连接MySQL8.2模…...
Spring Boot3+Vue2极速整合:10分钟搭建DeepSeek AI对话系统
前言 在生成式AI技术蓬勃发展的今天,大语言模型已成为企业智能化转型和个人效率提升的核心驱动力。作为国产大模型的优秀代表,DeepSeek凭借其卓越的中文语义理解能力和开发者友好的API生态,正在成为构建本土化AI应用的首选平台。 本文将以S…...
浅谈 Redis 主从复制原理(二)
大家好,我是此林。 【浅谈 Redis 主从集群原理(一) 】 上一篇文章中,说到了 Redis 主从复制的全量同步和增量同步,repl_baklog 复制缓冲区,以及 slave 挂掉之后数据同步的措施。 下面介绍的上一篇遗留问…...
elf_loader:一个使用Rust编写的ELF加载器
本文介绍一个使用Rust实现的ELF加载器。 下面是elf_loader的仓库链接: github: https://github.com/weizhiao/elf_loaderhttps://github.com/weizhiao/elf_loader crates.io: https://crates.io/crates/elf_loaderhttps://crates.io/cra…...
连接Sql Server时报错无法通过使用安全套接字层加密与 SQL Server 建立安全连接
文章目录 一. 前言二. 解决方案 方案1方案2 三. 总结 一. 前言 在《数据库原理》这门课的实验上,需要使用SQL Server,然后使用jdbc连接sql server突然报错为:SQLServerException: “Encrypt”属性设置为“true”且 “trustServerCertific…...
Qt常用控件之日历QCalendarWidget
日历QCalendarWidget QCalendarWidget 是一个日历控件。 QCalendarWidget属性 属性说明selectDate当前选中日期。minimumDate最小日期。maximumDate最大日期。firstDayOfWeek设置每周的第一天是周几(影响日历的第一列是周几)。gridVisible是否显示日历…...
Java——抽象类
在Java中,抽象类(Abstract Class) 是一种特殊的类,用于定义部分实现的类结构,同时允许子类提供具体的实现。抽象类通常用于定义通用的行为或属性,而将具体的实现细节留给子类。 1. 抽象类的定义 语法&…...
Linux CentOS 上 Ollama 的安装与部署:从入门到实践
Linux CentOS 上 Ollama 的安装与部署:从入门到实践 随着人工智能技术的快速发展,大语言模型(LLMs)的应用场景越来越广泛。Ollama 作为一个轻量级的 AI 模型管理工具,为开发者提供了便捷的模型部署和管理解决方案。本文将详细介绍如何在 Linux CentOS 系统上安装和部署 O…...
超级详细Spring AI运用Ollama大模型
大模型工具Ollama 官网:https://ollama.com/ Ollama是一个用于部署和运行各种开源大模型的工具; 它能够帮助用户快速在本地运行各种大模型,极大地简化了大模型在本地运行的过程。用户通过执行几条命令就能在本地运行开源大模型,如Lama 2等; 综上&#x…...
Django项目设计一个简单CRUD
在Django中实现一个学生类(Student)的CRUD(创建、读取、更新、删除)操作需要几个步骤,包括定义模型、创建视图、设置URL、以及配置模板。以下是一个基本的实现步骤: 1. 创建Django项目和应用 首先&#x…...
0083.基于springboot+uni-app的社区车位租赁系统小程序+论文
一、系统说明 基于springbootuni-app的社区车位租赁系统小程序,系统功能齐全, 代码简洁易懂,适合小白学编程。 现如今,信息种类变得越来越多,信息的容量也变得越来越大,这就是信息时代的标志。近些年,计算机科学发展…...
计算机视觉行业洞察--影像行业系列第一期
计算机视觉行业产业链的上下游构成相对清晰,从基础技术研发到具体应用场景的多个环节相对成熟。 以下是我结合VisionChina经历和行业龙头企业对计算机视觉行业产业链上下游的拆解总结。 上下游总结 上游产业链分为软硬件两类,视觉的硬件主要指芯片、…...
网页请求腾讯云环境的云函数
背景:uniapp,做一个管理后台 需求:在PC端网页请求云环境的云函数 npm npm install cloudbase/js-sdk -S 在APP中,封装匿名登陆,因为未登录时无法请求云函数 app.vue <script>import Vue from vueimport cl…...
【深度学习量化交易15】基于miniQMT的量化交易回测系统已基本构建完成!AI炒股的框架初步实现
我是Mr.看海,我在尝试用信号处理的知识积累和思考方式做量化交易,应用深度学习和AI实现股票自动交易,目的是实现财务自由~ 目前我正在开发基于miniQMT的量化交易系统——看海量化交易系统。 AI怎么炒股?就是通过量化交易。 近期D…...
使用大语言模型(Deepseek)构建一个基于 SQL 数据的问答系统
GitHub代码仓库 架构 从高层次来看,这些系统的步骤如下: 将问题转换为SQL查询:模型将用户输入转换为SQL查询。 执行SQL查询:执行查询。 回答问题:模型根据查询结果响应用户输入。 样本数据 下载样本数据…...
QT闲记-工具栏
工具栏通常用来放置常用的操作按钮,如QPushButton,QAction等。可以放置在顶部,底部,左侧,右侧,并且支持拖曳,浮动。 1、创建工具栏 通常通过QMainWindow 提供的addToolBar()来创建,它跟菜单栏一样,如果需要工具栏,一般情况下,我们设置这个类的基类为QMainWindow。 …...
JAVA最新版本详细安装教程(附安装包)
目录 文章自述 一、JAVA下载 二、JAVA安装 1.首先在D盘创建【java/jdk-23】文件夹 2.把下载的压缩包移动到【jdk-23】文件夹内,右键点击【解压到当前文件夹】 3.如图解压会有【jdk-23.0.1】文件 4.右键桌面此电脑,点击【属性】 5.下滑滚动条&…...
网络安全之探险
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 因为工作相关性,看着第三方公司出具的网络安全和shentou测试报告就想更深入研究一下,于是乎开始探索网络安全方面的知识,度娘、…...
基础dp——动态规划
目录 一、什么是动态规划? 二、动态规划的使用步骤 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 三、试题讲解 1.最小花费爬楼梯 2.下降路径最小和 3.解码方法 一、什么是动态规划? 动态规划(Dynamic Programming&…...
异常处理在 Promptic 中怎么实现?
在 Promptic 中,异常处理主要通过与 tenacity 库结合来实现,用于处理诸如 API 限流、临时故障等问题。以下是实现异常处理的具体方法和步骤: 1. 使用 tenacity 实现重试机制 tenacity 是一个强大的 Python 库,用于实现重试逻辑&…...
(四)趣学设计模式 之 原型模式!
目录 一、 啥是原型模式?二、 为什么要用原型模式?三、 原型模式怎么实现?四、 原型模式的应用场景五、 原型模式的优点和缺点六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式…...
会话对象 Cookie 四、Cookie的路径
1.Cookie的path属性 Cookie还有一个path属性,可以通过Cookie#setPath(String)方法来设置。你可以使用HttpWatch查看响应中的Set-Cookie中是否存在路径。下面是通过Chrome查看Cookie信息。 也就是说,就算你不设置Cookie的path,Cookie也是有路…...
A. Jagged Swaps
time limit per test 1 second memory limit per test 256 megabytes You are given a permutation†† aa of size nn. You can do the following operation Select an index ii from 22 to n−1n−1 such that ai−1<aiai−1<ai and ai>ai1ai>ai1. Swap aia…...
hugging face---transformers包
一、前言 不同于计算机视觉的百花齐放,不同网络适用不同情况,NLP则由Transformer一统天下。transformer是2017年提出的一种基于自注意力机制的神经网络架构,transformers库是hugging face社区创造的一个py库,通过该库可以实现统一…...
将 Vue 项目打包后部署到 Spring Boot 项目中的全面指南
将 Vue 项目打包后部署到 Spring Boot 项目中的全面指南 在现代 Web 开发中,前后端分离架构已经成为主流。然而,在某些场景下,我们可能需要将前端项目(如 Vue)与后端项目(如 Spring Boot)集成部…...
ipad连接电脑断断续续,不断弹窗的解决办法
因为ipad air 屏幕摔坏,换了一个内外屏,想用爱思检验一下屏幕真伪, 连接电脑时,断断续续,连上几秒钟然后就断开,然后又连上 然后又断开,不断地弹出信任的弹窗。 刚开始以为是数据线问题&#x…...
GPIO外设
一、GPIO简介 GPIO,general-purpos IO port,通用输入输出引脚,所有的GPIO引脚都有基本的输入输出功能。 最基本的输出功能:STM32控制引脚输出高、低电平,实现开关控制;最基本的输入功能:检测外部输入电平&…...
C++——priority_queue模拟实现
目录 前言 一、优先级队列介绍 二、优先级队列实现 向上调整 向下调整 三、仿函数 总结 前言 上一篇文章我们讲了stack和queue,这两个容器是容器适配器,本质上是一种复用,那本篇文章要讲的优先级队列也是一个容器适配器,我们…...
计算机网络基础:DOS命令、批处理脚本常见命令
目录 1. DOS 基础命令 1. echo 、 > 编写文件 2. type 读取文件 3. copy con 整段编写 4. attrib 命令 5. 快速生成空文件 6. 修改关联性 7. 关机shutdown 8. 复制文件copy、移动文件move 9. 重命名ren 2. 批处理 2.1 简单显示 2.2 死循环 2.3 定时关机小程序 …...
