【鸿蒙】HarmonyOS NEXT星河入门到实战8-自定义组件-组件通信
目录
1、模块化语法
1.1 模块化基本认知
1.2 默认导出和导入
1.2.1 在ets下新建tools目录
1.2.2 在tools下新建moduls.ets文件
1.2.3 index.ets
1.3 按需导出和导入
1.4 全部导入
2、自定义组件 -基础
2.1 自定义组件 - 基本使用
2.2 自定义组件 -通用样式
2.2.1 ets下新建components文件夹
2.2.2 components新建HelloCom.ets
2.2.3 Index.ets
2.3 自定义组件 -成员函数变量
3、@BuilderParam
3.1 @BuilderParam 传递UI
3.2 多个 @BuilderParam 参数
4、 状态管理
4.1 状态管理概述
4.2 @ State 自己的状态
4.3 @Prop -父子单向
4.4 掘金评论案例
4.4.1 头部组件
4.4.1.1 在ets/components下新建InfoTop.ets
4.4.1.2 Index.ets
4.4.2 评论案例 - List列表组件的使用
4.4.2.1 List列表基本使用
4.4.2.2 中间评论列表
4.4.2.2.1 在components下新增InfoItem.ets
4.4.2.2.2 Index .ets
4.4.3 底部评价
4.4.3.1 IconFont的使用
4.4.3.1.1 ets\fonts下存放下载的文件
4.4.3.1.2 注册字体&使用字体
4.4.3.2 在components下新增InfoBottom.ets
4.4.3.3 Index .ets
4.4.4 列表渲染
4.4.4.1 准备数据
4.4.4.2 列表渲染完成
4.4.4.3 点赞
4.4.4.4 添加评论
4.4.4.5 评论排序
4.5 @Link双向同步
4.6 @Provide \@Consume后代组件
4.7 @ Observed &@ObjectLink嵌套对象数组属性变化
前言:自定义组件-组件通信(模块化语法)、自定义组件、传递UI、状态管理
1、模块化语法
1.1 模块化基本认知
1.2 默认导出和导入
1.2.1 在ets下新建tools目录
1.2.2 在tools下新建moduls.ets文件
interface Person {
name: string
age: number
}
let num: number = 10
let person: Person ={
name: '春天的菠菜',
age: 18
}
// 默认导出(导出一个值)
export default num
1.2.3 index.ets
导入进来 import 导入进来新取的名称 from 路径(相对路径)
import window from '@ohos.window'; // 相对路径 import myNum from '../tools/moduls' @Extend(Text) function textExtend(){ .fontSize(20) .fontWeight(700) .backgroundColor(Color.Green) .padding(10) .margin(5) } @Entry @Component struct Index { @State message: string = '@春天的菠菜'; onPageShow(): void { window.getLastWindow(AppStorage.get("context"), (err, data) => { if (err.code) { console.error('Failed to get last window. Cause:' + JSON.stringify(err)); return; } data.setFullScreen(true) }); } build() { Column(){ Text('来自tools/modlus 模块的值:'+ myNum) .textExtend() } .width('100%').height(100) } }
1.3 按需导出和导入
tools下新建/module3.ets
// 按需导出
// 多个特性, 逐个 export 按需导出
// export let name1: string = '刘备'
// export let price: number = 9.98
// export let sayHi = () => {
// console.log('打招呼')
// }
let name1: string = '刘备'
let name2: string = '张飞'
let name3: string = '关羽'
let price: number = 9.98
let price2: number = 10.1
let sayHi = () => {
console.log('打招呼')
}
let run = () => {
console.log('跑步')
}
// 一次性将多个特性, 进行导出
export {
name1, name2, name3,
price, price2,
sayHi, run
}
import window from '@ohos.window';
// 相对路径
import myNum from '../tools/moduls'
import { name1, price, sayHi }from '../tools/module3'
sayHi()
@Extend(Text)
function textExtend(){
.fontSize(20)
.fontWeight(700)
.backgroundColor(Color.Green)
.padding(10)
.margin(5)
}
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
Text('来自tools/modlus 模块的值:'+ myNum)
.textExtend().margin(5)
Text('来自tools/module3 模块的值:'+ name1 +'**' + price )
.textExtend()
}
.width('100%').height(100)
}
}
1.4 全部导入
2、自定义组件 -基础
2.1 自定义组件 - 基本使用
1、无@Entry 2、 struct 后面自定义名称
import window from '@ohos.window';
@Extend(Text)
function textExtend(){
.fontSize(20)
.fontWeight(700)
.backgroundColor(Color.Green)
.padding(10)
.margin(5)
}
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column() {
MyHeader()
MyMain()
MyFooter()
}
}
}
@Component
struct MyCom {
@State count: number = 1
build() {
Row() {
Text(this.count.toString())
.fontColor(Color.White)
.margin(10)
Button('按钮')
.onClick(() => {
this.count++
})
}
}
}
@Component
struct MyHeader {
build() {
Row() {
Text('我是头部')
.fontColor(Color.White)
}
.width('100%')
.height(50)
.backgroundColor(Color.Brown)
}
}
@Component
struct MyMain {
build() {
Column() {
// 将相同的业务逻辑, 封装成一个通用的组件
MyCom()
MyCom()
MyCom()
}
.layoutWeight(1)
.width('100%')
.backgroundColor(Color.Gray)
}
}
@Component
struct MyFooter {
build() {
Row() {
Text('我是底部')
}
.width('100%')
.height(50)
.backgroundColor(Color.Green)
}
}
2.2 自定义组件 -通用样式
2.2.1 ets下新建components文件夹
2.2.2 components新建HelloCom.ets
@Preview
@Component
export struct HelloCom {
build() {
Row() {
Text('自定义组件')
Button('按钮')
}
.width(200)
.height(50)
.backgroundColor(Color.Orange)
}
}
2.2.3 Index.ets
import window from '@ohos.window';
import { HelloCom } from '../components/HelloCom'
@Extend(Text)
function textExtend(){
.fontSize(20)
.fontWeight(700)
.backgroundColor(Color.Green)
.padding(10)
.margin(5)
}
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column() {
HelloCom()
.width(250)
.height(60)
.backgroundColor(Color.Gray)
.onClick(() => {
AlertDialog.show({
message: '测试点击'
})
})
}
}
}
2.3 自定义组件 -成员函数变量
带=符号的可以外部传参
import window from '@ohos.window';
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column() {
MyPanel({
title: '我的订单',
extra: '全部订单',
getMore() {
AlertDialog.show({
message: '点击了全部订单'
})
}
})
MyPanel({
title: '小米有品众筹',
extra: '七款众筹中',
getMore() {
AlertDialog.show({
message: '点击了众筹'
})
}
}
)
}
.width('100%').height('100%')
.backgroundColor('#ccc')
.padding(20)
}
}
@Component
struct MyPanel {
// 成员变量 -数据
title: string = '默认的大标题'
extra: string = '查看更多'
// 成员变量 -函数 可以传入覆盖
getMore = () =>{
AlertDialog.show({
message: '查看更多'
})
}
// 成员函数 -- 不可以外部传入覆盖
sayHi (){
AlertDialog.show({
message: '打招呼 你好'
})
}
build() {
Column(){
Row(){
Text(this.title)
.fontSize(18)
Text(this.extra)
.fontSize(18)
.onClick( () => {
this.getMore()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text('内容部分')
.fontSize(18)
Button('按钮')
.onClick(() => {
this.sayHi()
})
}
.padding(20)
}
.width('100%').height(200)
.margin({bottom: 20})
.borderRadius(10)
.padding(10)
.backgroundColor(Color.White)
}
}
3、@BuilderParam
3.1 @BuilderParam 传递UI
import window from '@ohos.window';
@Component
struct SonCom {
// 1、 定义构建函数
@BuilderParam ContentBuilder: () => void = this.defaultBuilder
@Builder
defaultBuilder () {
Text('默认的内容')
}
build() {
// 2、 使用构建函数,构建结构
Column(){
this.ContentBuilder()
}
}
}
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column() {
SonCom() {
Button('传入的结构')
}
}
}
}
练习
import window from '@ohos.window';
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column() {
MyPanel({
title: '我的订单',
extra: '全部订单',
getMore() {
AlertDialog.show({
message: '点击了全部订单'
})
}
}) {
Text('我是订单相关的文本')
}
MyPanel({
title: '小米有品众筹',
extra: '七款众筹中',
getMore() {
AlertDialog.show({
message: '点击了众筹'
})
}
}) {
Button('我是小米众筹的按钮')
}
}
.width('100%').height('100%')
.backgroundColor('#ccc')
.padding(20)
}
}
@Component
struct MyPanel {
// 成员变量 -数据
title: string = '默认的大标题'
extra: string = '查看更多'
// 成员变量 -函数 可以传入覆盖
getMore = () =>{
AlertDialog.show({
message: '查看更多'
})
}
// 成员函数 -- 不可以外部传入覆盖
sayHi (){
AlertDialog.show({
message: '打招呼 你好'
})
}
// 1、 定义构建函数
@BuilderParam ContentBuilder: () => void = this.defaultBuilder
@Builder
defaultBuilder () {
Text('默认的内容')
}
build() {
Column(){
Row(){
Text(this.title)
.fontSize(18)
Text(this.extra)
.fontSize(18)
.onClick( () => {
this.getMore()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
// 这里结构不能写死,需要通过BUilderParam来进行构建
this.ContentBuilder()
}
.padding(20)
}
.width('100%').height(200)
.margin({bottom: 20})
.borderRadius(10)
.padding(10)
.backgroundColor(Color.White)
}
}
3.2 多个 @BuilderParam 参数
import window from '@ohos.window';
@Component
struct MyCard {
@BuilderParam tBuilder: () => void = this.tDefaultBuilder
@BuilderParam cBuilder: () => void = this.cDefaultBuilder
@Builder tDefaultBuilder () {
Text('我是默认的大标题')
}
@Builder cDefaultBuilder () {
Text('我是默认的内容')
}
build() {
// 卡片组件
Column() {
// 标题部分
Row() {
this.tBuilder()
}
.height(30)
.width('100%')
.border({ color: '#ccc', width: { bottom: 1 }})
.padding({ left: 10 })
// 内容部分
Row() {
this.cBuilder()
}
.width('100%')
.padding(10)
}
.width('100%')
.height(100)
.backgroundColor(Color.White)
.borderRadius(10)
.justifyContent(FlexAlign.Start)
}
}
@Entry
@Component
struct Index {
@State message: string = '@春天的菠菜';
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
@Builder ftBuilder () {
Text('我是传入的大标题结构')
}
@Builder fcBuilder () {
Text('我是内容部分')
.margin(5)
Text('我是内容部分')
.margin(5)
Text('我是内容部分')
.margin(5)
}
build() {
Column({ space: 10 }) {
MyCard()
MyCard({
tBuilder: this.ftBuilder,
cBuilder: this.fcBuilder
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#ccc')
}
}
4、 状态管理
4.1 状态管理概述
4.2 @ State 自己的状态
import window from '@ohos.window';
interface Car {
name: string
}
interface Person {
name: string
car: Car
}
const obj: Person = {
name: 'zs',
car: {
name: '小黄车'
}
}
console.log('查看第一层属性', Object.keys(obj))
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
// 状态变量
// 1. string number boolean 可以直接监视到变化
@State message: string = 'hello world'
// 2. 复杂类型 object class, 第一层随便改, 嵌套需要进行整个嵌套对象的替换
@State person: Person = {
name: 'jack',
car: {
name: '宝马车'
}
}
build() {
Column() {
Text(this.message).fontSize(20)
Button('改message').onClick(() => {
this.message = '你好'
})
Text(JSON.stringify(this.person))
Button('改person').onClick(() => {
// this.person = {
// name: 'amy',
// car: {
// name: '保时捷'
// }
// }
// this.person.name = 'tony'
// 如果不是对象的第一层属性, 修改时, 需要修改整个嵌套的对象
// this.person.car.name = '小火车'
// console.log('car name', this.person.car.name)
this.person.car = {
name: '老爷车'
}
})
}
}
}
4.3 @Prop -父子单向
import window from '@ohos.window';
@Component
struct SonCom {
// 保证父组件的数据变化了, 能够往下响应式的更新
@Prop sCar: string = ''
changeCar = (newCar: string) => {}
build() {
Column() {
Text(`子组件 ${this.sCar}`)
Button('换车').onClick((event: ClickEvent) => {
// 1. prop传值 → 单向传递
// 子组件, 可以修改到 prop 传值, 但是修改的更新不会同步到父组件
// 通常不太会直接修改 prop 传值, 父组件的状态一旦变化, 会自动向下同步
// 修改就被覆盖了
// this.sCar = '小黄车'
// 2. 如果实在想更新, 希望保证父子同步 => 调用父组件传递过来的方法
// 如果没有写箭头函数, 意味着, this 指向 调用者, 而此处执行环境 this → 子组件
this.changeCar('蹦蹦车')
})
}
.padding(20)
.backgroundColor(Color.Orange)
}
}
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
@State fCar:string = '劳斯莱斯'
build() {
Column() {
Text(`父组件 - ${this.fCar}`)
Button('换车').onClick(() => {
this.fCar = '三轮车'
})
SonCom({
sCar: this.fCar,
// 这里必须要用箭头函数, 否则会有 this 指向的问题
// 使用箭头函数的好处, 可以使用外部环境的 this, 不受传递过去后的执行环境影响
// 希望此处 this 指向 父组件
changeCar: (newCar: string) => {
this.fCar = newCar
}
})
}
.padding(50)
.backgroundColor(Color.Pink)
}
}
练习
import window from '@ohos.window';
@Component
struct NumberCount {
@Prop num: number = 0
SubFn = () => {}
AddFn = () => {}
build() {
Row({space: 5}) {
Button('-')
.onClick( () => {
this.SubFn()
})
Text(this.num.toString())
Button('+')
.onClick( () => {
this.AddFn()
})
}
}
}
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
@State num1: number = 5
@State num2: number = 3
build() {
Column({space: 5}){
Row({space: 5}){
Text('茄子')
NumberCount({
num: this.num1,
AddFn: () => this.num1++,
SubFn: () => this.num1--
})
}
Row({space: 5}){
Text('香蕉')
NumberCount({
num: this.num2,
AddFn: () => this.num2++,
SubFn: () => this.num2--
})
}
}
.padding(10)
}
}
4.4 掘金评论案例
4.4.1 头部组件
4.4.1.1 在ets/components下新建InfoTop.ets
@Extend(Button)
function fancyButton (isOn: boolean) {
.width(46)
.height(32)
.fontSize(12)
.padding({ left: 5, right: 5 })
.backgroundColor(isOn ? '#fff' : '#F7F8FA')
.fontColor(isOn ? '#2f2e33' : '#8e9298')
.border({ width: 1, color: isOn ? '#e4e5e6' : '#F7F8FA' })
}
@Component
struct InfoTop {
@State isOn: boolean = true
build() {
Row(){
Text('全部评论')
.fontColor('#222')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row(){
Button('最新',{ stateEffect: false})
.fancyButton(this.isOn)
.onClick( () => this.isOn = true)
Button('最热',{ stateEffect: false})
.fancyButton(!this.isOn)
.onClick( () => this.isOn = false)
}
.backgroundColor('F7F8FA')
.borderRadius(20)
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%').height(60)
.padding(16)
}
}
export default InfoTop
4.4.1.2 Index.ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 头部
InfoTop()
// 中间
Column(){
}
.width('100%')
.backgroundColor(Color.Orange)
.layoutWeight(1)
// 底部
Row(){
}
.width('100%').height(60)
.backgroundColor(Color.Pink)
}
.width('100%').height('100%')
}
}
4.4.2 评论案例 - List列表组件的使用
4.4.2.1 List列表基本使用
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 头部
InfoTop()
// 中间
List(){
ForEach(Array.from({ length: 20}), () => {
ListItem(){
Row().width('100%').height(100).backgroundColor(Color.Brown)
}
.padding(10)
})
}
.width('100%')
.backgroundColor(Color.Orange)
.layoutWeight(1)
.listDirection(Axis.Vertical) // 调整主轴方向 默认 Vertical .listDirection(Axis.Horizontal)
.lanes(2,5) //调整列和间距
.alignListItem(ListItemAlign.Center) //控制列的对齐方式
.scrollBar(BarState.Auto) //Auto 按需自动显示滚动条
// .divider({
// strokeWidth: 3, //线宽
// color: Color.Blue, //颜色
// startMargin: 10, //左边线距离边缘的间隙
// endMargin: 10 //右边线距离边缘的间隙
// })
// 底部
Row(){
}
.width('100%').height(60)
.backgroundColor(Color.Pink)
}
.width('100%').height('100%')
}
}
4.4.2.2 中间评论列表
4.4.2.2.1 在components下新增InfoItem.ets
@Component
struct InfoItem {
build() {
Column() {
// 1. 头像, 昵称, 等级
Row() {
Image($r('app.media.startIcon'))
.width(30).aspectRatio(1)
.margin({ top: 10 })
.borderRadius(15)
Text('黑暗王国')
.fontSize(13)
.fontColor(Color.Gray)
.margin({ top: 10, left: 8 })
Image($r('app.media.level_6'))
.width(20).aspectRatio(1)
.margin({ top: 10, left: 8 })
.borderRadius(15)
}
// 2. 评论内容
Text('安抚巾阿范德萨见附件')
.fontSize(13)
.fontColor(Color.Black)
.margin({ left: 40, top: 0, bottom: 8 })
// 3. 评论日期 和 点赞互动
Row() {
Text('3天前')
.fontSize(11)
.fontColor(Color.Gray)
Row() {
Image($r('app.media.like_select'))
.width(15).aspectRatio(1)
Text('99')
.fontSize(11)
.fontColor(Color.Gray)
}
}
.padding({ left: 40, top: 5 })
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({ left: 15, right: 15 })
.alignItems(HorizontalAlign.Start)
}
}
export default InfoItem
4.4.2.2.2 Index .ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
@Entry
@Component
struct Index {
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 头部
InfoTop()
// 中间
List(){
ForEach(Array.from({ length: 20}), () => {
ListItem(){
// 列表项组件
InfoItem()
}
})
}
.layoutWeight(1)
.padding({bottom: 10})
// 底部
Row(){
}
.width('100%').height(60)
.backgroundColor(Color.Pink)
}
.width('100%').height('100%')
}
}
4.4.3 底部评价
4.4.3.1 IconFont的使用
4.4.3.1.1 ets\fonts下存放下载的文件
略
iconfont-阿里巴巴矢量图标库
4.4.3.1.2 注册字体&使用字体
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
@Entry
@Component
struct Index {
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
Text('\ue600')
.fontFamily('myfont')
.fontSize(30)
.fontColor(Color.Red)
Text('\ue840')
.fontFamily('myfont')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List(){
ForEach(Array.from({ length: 20}), () => {
ListItem(){
// 列表项组件
InfoItem()
}
})
}
.layoutWeight(1)
.padding({bottom: 10})
// 底部
Row(){
}
.width('100%').height(60)
.backgroundColor(Color.Pink)
}
.width('100%').height('100%')
}
}
4.4.3.2 在components下新增InfoBottom.ets
@Component
struct InfoBottom {
build() {
Row() {
Row() {
Text('\ue840')
.fontFamily('myfont')
.fontSize(18)
.margin({ left: 20 })
TextInput({
placeholder: '写评论...'
})
.backgroundColor(Color.Transparent)
}
.height(40)
.backgroundColor('#f5f6f5')
.borderRadius(20)
.margin({ left: 15, right: 20, top: 10, bottom: 10 })
.layoutWeight(1)
Text('\ue600')
.fontFamily('myfont')
.fontSize(26)
.margin({ left: 6, right: 6 })
Text('\ue61d')
.fontFamily('myfont')
.fontSize(26)
.margin({ left: 6, right: 6 })
}
.height(60)
.width('100%')
}
}
export default InfoBottom
4.4.3.3 Index .ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
@Entry
@Component
struct Index {
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
//
// Text('\ue840')
// .fontFamily('myfont')
// .fontSize(30)
// .fontWeight(FontWeight.Bold)
// .fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List(){
ForEach(Array.from({ length: 20}), () => {
ListItem(){
// 列表项组件
InfoItem()
}
})
}
.layoutWeight(1)
.padding({bottom: 10})
// 底部
InfoBottom()
}
.width('100%').height('100%')
}
}
4.4.4 列表渲染
4.4.4.1 准备数据
ets\model\CommentData.ets
// 准备评论的数据类
export class CommentData {
avatar: string; // 头像
name: string; // 昵称
level: number; //用户等级
likeNum: number; //点赞数量
commentTxt: string; //评论内容
isLike: boolean; //是否喜欢
levelIcon: Resource // level等级
timeString: string // 发布时间-基于时间戳处理后,展示给用户看的属性(2天前)
time: number // 时间戳-数字格式的日期
constructor(avatar: string, name: string, time: number, level: number, lickNum: number, commentTxt: string, isLike: boolean, ) {
this.avatar = avatar
this.name = name
this.timeString = this.convertTime(time) // 拿到的时间格式, 通常是时间戳的格式 1645820201123
this.time = time
this.level = level
this.likeNum = lickNum
this.commentTxt = commentTxt
this.isLike = isLike
this.levelIcon = this.convertLevel(this.level) // 基于等级数字, 转换成图片路径
}
convertTime(timestamp: number) {
// 获取当前的时间戳
const currentTimestamp = new Date().getTime();
const timeDifference = (currentTimestamp - timestamp) / 1000; // 转换为秒
if (timeDifference < 5 || timeDifference == 0) {
return "刚刚";
} else if (timeDifference < 60) {
return `${Math.floor(timeDifference)}秒前`;
} else if (timeDifference < 3600) {
return `${Math.floor(timeDifference / 60)}分钟前`;
} else if (timeDifference < 86400) {
return `${Math.floor(timeDifference / 3600)}小时前`;
} else if (timeDifference < 604800) {
return `${Math.floor(timeDifference / 86400)}天前`;
} else if (timeDifference < 2592000) {
return `${Math.floor(timeDifference / 604800)}周前`;
} else if (timeDifference < 31536000) {
return `${Math.floor(timeDifference / 2592000)}个月前`;
} else {
return `${Math.floor(timeDifference / 31536000)}年前`;
}
}
// 基于传入的level,转换成图片路径
convertLevel(level: number) {
const iconList = [
$r('app.media.level_1'),
$r('app.media.level_2'),
$r('app.media.level_3'),
$r('app.media.level_4'),
$r('app.media.level_5'),
$r('app.media.level_6'),
]
return iconList[level]
}
}
// 封装一个方法, 创建假数据
export const createListRange = (): CommentData[] => {
let result: CommentData[] = new Array()
result = [
new CommentData(`https://fastly.picsum.photos/id/770/600/600.jpg?hmac=tuK9EHg1ifTU3xKAiZj2nHSdWy4mk7enhylgOc2BW7E`, "雪山飞狐", 1645820201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '23年一年干完的事😂😂😂真的非常仓促', false),
new CommentData(`https://fastly.picsum.photos/id/225/600/600.jpg?hmac=v97zt_t4mxeyMttX_m09pxhCvftiTxFR1MMBZi5HQxs`, "千纸鹤", 1677356201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), 'Netty对象池源码分析来啦!juejin.cn欢迎点赞[奸笑]', false),
new CommentData(`https://fastly.picsum.photos/id/122/600/600.jpg?hmac=1oA93YbjYVt96DcJcGQ5PLthzjUsdtrnBQaM0USBozI`, "烟雨江南", 1688772201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '有一个不听劝的Stable Diffusion出图的小伙伴,找我给她装填脑。 一个资深的IT工程师不能受这个委屈。', false),
new CommentData(`https://fastly.picsum.photos/id/654/600/600.jpg?hmac=ewnK6Bx_MKQLJa9waZOV1xNO7--K5oSwCShtz1JDYw8`, "魔法小精灵", 1697484201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '有一点可以确信 前后端开发界限越来越模糊。后端可以不写 但是不能不会。', false),
new CommentData(`https://fastly.picsum.photos/id/345/600/600.jpg?hmac=EQflzbIadAglm0RzotyKXM2itPfC49fR3QE7eW_UaPo`, "独行侠", 1704067200000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '今天看到一个帖子,挺有意思的。', false),
new CommentData(`https://fastly.picsum.photos/id/905/600/600.jpg?hmac=DvIKicBZ45DEZoZFwdZ62VbmaCwkK4Sv7rwYzUvwweU`, "枫叶飘零", 1706745600000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '我想了搞钱的路子, 1:投资理财, 后来发下,不投资就是最好的理财, 2:买彩票,后来发现彩票都是人家预定好的,根本不是随机的,卒, 3:开店创业,隔行如隔山,开店失败,卒。', false),
new CommentData(`https://fastly.picsum.photos/id/255/600/600.jpg?hmac=-lfdnAl71_eAIy1OPAupFFPh7EOJPmQRJFg-y7lRB3s`, "星空漫步", 1707523200000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '优胜劣汰,自然选择吧,没什么好怪的。都是crud,招个大学生就能干了。', false),
new CommentData(`https://fastly.picsum.photos/id/22/600/600.jpg?hmac=QEZq7KUHwBZCt3kGSEHMwJlZfnzCxCeBgHjYj7iQ-UY`, "剑指苍穹", 1708300800000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '白嫖ChatGPT4的功能。然后,抱着试一试的态度把玩了一下。发现真的好用。', false),
new CommentData(`https://fastly.picsum.photos/id/439/600/600.jpg?hmac=LC9k_bzrN0NhKRyV62fou3ix3cRFZKNfAyXgxGs6zh8`, "黑暗王国", 1708646400000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '字数越少,事情越大。', false),
]
return result
}
4.4.4.2 列表渲染完成
InfoItem.etc
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'
import { CommentData } from '../model/CommentData'
@Component
struct InfoItem {
@Prop itemObj: CommentData
@Prop index: number
onLikeClick = (index: number) => {}
build() {
Column() {
// 1. 头像, 昵称, 等级
Row() {
Image(this.itemObj.avatar)
.width(30).aspectRatio(1)
.margin({ top: 10 })
.borderRadius(15)
Text(this.itemObj.name)
.fontSize(13)
.fontColor(Color.Gray)
.margin({ top: 10, left: 8 })
Image(this.itemObj.levelIcon)
.width(20).aspectRatio(1)
.margin({ top: 10, left: 8 })
.borderRadius(15)
}
// 2. 评论内容
Text(this.itemObj.commentTxt)
.fontSize(13)
.fontColor(Color.Black)
.margin({ left: 40, top: 0, bottom: 8 })
// 3. 评论日期 和 点赞互动
Row() {
Text(this.itemObj.timeString)
.fontSize(11)
.fontColor(Color.Gray)
Row() {
Image($r('app.media.like_unselect'))
.width(15).aspectRatio(1)
Text(this.itemObj.likeNum.toString())
.fontSize(11)
.fontColor(Color.Gray)
}
}
.padding({ left: 40, top: 5 })
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({ left: 15, right: 15 })
.alignItems(HorizontalAlign.Start)
}
}
export default InfoItem
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))
// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
@State commentList: CommentData[] = createListRange()
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
//
// Text('\ue840')
// .fontFamily('myfont')
// .fontSize(30)
// .fontWeight(FontWeight.Bold)
// .fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List(){
ForEach(this.commentList, (item: CommentData, index: number) => {
ListItem(){
// 列表项组件 -- 将item对象, 通过prop传值, 传递下去
InfoItem({
itemObj: item
})
}
})
}
.layoutWeight(1)
.padding({bottom: 10})
// 底部
InfoBottom()
}
.width('100%').height('100%')
}
}
Index.ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))
// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
@State commentList: CommentData[] = createListRange()
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
//
// Text('\ue840')
// .fontFamily('myfont')
// .fontSize(30)
// .fontWeight(FontWeight.Bold)
// .fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List(){
ForEach(this.commentList, (item: CommentData, index: number) => {
ListItem(){
// 列表项组件 -- 将item对象, 通过prop传值, 传递下去
InfoItem({
itemObj: item
})
}
})
}
.layoutWeight(1)
.padding({bottom: 10})
// 底部
InfoBottom()
}
.width('100%').height('100%')
}
}
4.4.4.3 点赞
infoItem.ets
import { CommentData } from '../model/CommentData'
@Component
struct InfoItem {
@Prop itemObj: CommentData
@Prop index: number
onLikeClick = (index: number) => {}
build() {
Column() {
// 1. 头像, 昵称, 等级
Row() {
Image(this.itemObj.avatar)
.width(30).aspectRatio(1)
.margin({ top: 10 })
.borderRadius(15)
Text(this.itemObj.name)
.fontSize(13)
.fontColor(Color.Gray)
.margin({ top: 10, left: 8 })
Image(this.itemObj.levelIcon)
.width(20).aspectRatio(1)
.margin({ top: 10, left: 8 })
.borderRadius(15)
}
// 2. 评论内容
Text(this.itemObj.commentTxt)
.fontSize(13)
.fontColor(Color.Black)
.margin({ left: 40, top: 0, bottom: 8 })
// 3. 评论日期 和 点赞互动
Row() {
Text(this.itemObj.timeString)
.fontSize(11)
.fontColor(Color.Gray)
Row() {
Image(this.itemObj.isLike ? $r('app.media.like_select') : $r('app.media.like_unselect'))
.width(15).aspectRatio(1)
Text(this.itemObj.likeNum.toString())
.fontSize(11)
.fontColor(this.itemObj.isLike ? Color.Blue : Color.Gray)
}
.onClick(() => {
// 此处的this是子组件
// 让当前项的 likeNum 变化 (老爹的数据, 需要调用老爹的方法才能改)
this.onLikeClick(this.index)
})
}
.padding({ left: 40, top: 5 })
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({ left: 15, right: 15 })
.alignItems(HorizontalAlign.Start)
}
}
export default InfoItem
Index.ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))
// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
@State commentList: CommentData[] = createListRange()
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
// 处理喜欢
handleLike (index: number) {
// 1. 获取下标 或 id 加以区分
// 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
// 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
// 将来发请求 → id 作为区分值, 也可以
// AlertDialog.show({
// message: index.toString()
// })
// 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
// 会有 this 指向问题, this 通常直接指向调用者
// 需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
// console.log('父组件的数据', this.commentList)
// 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
let itemData = this.commentList[index]
if (itemData.isLike) {
itemData.likeNum -= 1
}
else {
itemData.likeNum += 1
}
itemData.isLike = !itemData.isLike
// 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
// 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
this.commentList.splice(index, 1, itemData)
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
//
// Text('\ue840')
// .fontFamily('myfont')
// .fontSize(30)
// .fontWeight(FontWeight.Bold)
// .fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List() {
ForEach(this.commentList, (item: CommentData, index: number) => {
ListItem() {
// 列表项组件 - 将item对象, 通过prop传值, 传递下去
InfoItem({
index: index,
itemObj: item,
onLikeClick: (index: number) => {
// 此处的this → 访问的是外部环境的 this → 就是父组件
this.handleLike(index)
}
})
}
})
}
.layoutWeight(1)
.padding({ bottom: 10 })
// 底部
InfoBottom()
}
.width('100%')
.height('100%')
}
}
4.4.4.4 添加评论
infoBottom.ets
@Component
struct InfoBottom {
@State txt: string = ''
onSubmitComment = (content: string) => {}
build() {
Row() {
Row() {
Text('\ue840')
.fontFamily('myfont')
.fontSize(18)
.margin({ left: 20 })
TextInput({
placeholder: '写评论...',
text: $$this.txt
})
.backgroundColor(Color.Transparent)
.fontSize(18)
.onSubmit(() => {
// 这里不能直接添加, 需要调用父组件传递过来的方法
this.onSubmitComment(this.txt)
})
}
.height(40)
.backgroundColor('#f5f6f5')
.borderRadius(20)
.margin({ left: 15, right: 20, top: 10, bottom: 10 })
.layoutWeight(1)
Text('\ue600')
.fontFamily('myfont')
.fontSize(26)
.margin({ left: 6, right: 6 })
Text('\ue61d')
.fontFamily('myfont')
.fontSize(26)
.margin({ left: 6, right: 6 })
}
.height(60)
.width('100%')
}
}
export default InfoBottom
Index.ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))
// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
@State commentList: CommentData[] = createListRange()
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
}
// 处理喜欢
handleLike (index: number) {
// 1. 获取下标 或 id 加以区分
// 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
// 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
// 将来发请求 → id 作为区分值, 也可以
// AlertDialog.show({
// message: index.toString()
// })
// 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
// 会有 this 指向问题, this 通常直接指向调用者
// 需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
// console.log('父组件的数据', this.commentList)
// 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
let itemData = this.commentList[index]
if (itemData.isLike) {
itemData.likeNum -= 1
}
else {
itemData.likeNum += 1
}
itemData.isLike = !itemData.isLike
// 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
// 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
this.commentList.splice(index, 1, itemData)
}
// 处理提交
handleSubmit (content: string) {
// 往数组的最前面, 新增一项
const newItem: CommentData = new CommentData(
"https://img0.baidu.com/it/u=3316636492,2799302396&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1708707600&t=fc2a4907d0fae5c7b2d2d5f1511c24b3",
"我", new Date().getTime(), 5, 0, content, false)
this.commentList = [newItem, ...this.commentList]
}
onPageShow(): void {
window.getLastWindow(AppStorage.get("context"), (err, data) => {
if (err.code) {
console.error('Failed to get last window. Cause:' + JSON.stringify(err));
return;
}
data.setFullScreen(true)
});
}
build() {
Column(){
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
//
// Text('\ue840')
// .fontFamily('myfont')
// .fontSize(30)
// .fontWeight(FontWeight.Bold)
// .fontColor(Color.Red)
// 头部
InfoTop()
// 中间
List() {
ForEach(this.commentList, (item: CommentData, index: number) => {
ListItem() {
// 列表项组件 - 将item对象, 通过prop传值, 传递下去
InfoItem({
index: index,
itemObj: item,
onLikeClick: (index: number) => {
// 此处的this → 访问的是外部环境的 this → 就是父组件
this.handleLike(index)
}
})
}
})
}
.layoutWeight(1)
.padding({ bottom: 10 })
// 底部
InfoBottom({
onSubmitComment: (content: string) => {
this.handleSubmit(content)
}
})
}
.width('100%')
.height('100%')
}
}
4.4.4.5 评论排序
infoTop.ets
@Extend(Button)
function fancyButton (isOn: boolean) {
.width(46)
.height(32)
.fontSize(12)
.padding({ left: 5, right: 5 })
.backgroundColor(isOn ? '#fff' : '#F7F8FA')
.fontColor(isOn ? '#2f2e33' : '#8e9298')
.border({ width: 1, color: isOn ? '#e4e5e6' : '#F7F8FA' })
}
@Component
struct InfoTop {
@State isOn: boolean = true
onSort = (type: number) => {}
build() {
Row() {
Text('全部评论')
.fontColor('#222')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Button('最新', { stateEffect: false })
.fancyButton(this.isOn)
.onClick(() => {
this.isOn = true
this.onSort(0)
})
Button('最热', { stateEffect: false })
.fancyButton(!this.isOn)
.onClick(() => {
this.isOn = false
this.onSort(1)
})
}
.backgroundColor('#F7F8FA')
.borderRadius(20)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
}
export default InfoTop
Index.ets
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import InfoBottom from '../components/InfoBottom'
import font from '@ohos.font'
import { CommentData, createListRange } from '../model/CommentData'
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))
// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
@State commentList: CommentData[] = createListRange()
// 一加载Index入口页面, 就进行注册
// aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
aboutToAppear(): void {
// 1. 注册字体
font.registerFont({
familyName: 'myfont',
familySrc: '/fonts/iconfont.ttf'
})
this.handleSort(0)
}
// 处理喜欢
handleLike (index: number) {
// 1. 获取下标 或 id 加以区分
// 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
// 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
// 将来发请求 → id 作为区分值, 也可以
// AlertDialog.show({
// message: index.toString()
// })
// 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
// 会有 this 指向问题, this 通常直接指向调用者
// 需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
// console.log('父组件的数据', this.commentList)
// 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
let itemData = this.commentList[index]
if (itemData.isLike) {
itemData.likeNum -= 1
}
else {
itemData.likeNum += 1
}
itemData.isLike = !itemData.isLike
// 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
// 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
this.commentList.splice(index, 1, itemData)
}
// 处理提交
handleSubmit (content: string) {
// 往数组的最前面, 新增一项
const newItem: CommentData = new CommentData(
"https://img0.baidu.com/it/u=3316636492,2799302396&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1708707600&t=fc2a4907d0fae5c7b2d2d5f1511c24b3",
"我", new Date().getTime(), 5, 0, content, false)
this.commentList = [newItem, ...this.commentList]
}
// 处理排序 0 最新 time时间戳, 1 最热 likeCount 点赞数
handleSort (type: number) {
if (type === 0) {
// 时间戳, 从大到小排序
this.commentList.sort((a, b) => {
// a 前一项, b 后一项
return b.time - a.time // 返回值如果 > 0, 交换位置
})
}
else {
// 点赞数, 从大到小排序
this.commentList.sort((a, b) => {
return b.likeNum - a.likeNum
})
}
}
build() {
Column() {
// 2. 使用字体测试
// Text('\ue600')
// .fontFamily('myfont')
// .fontSize(30)
// .fontColor(Color.Red)
// 头部标题组件
InfoTop({
onSort: (type: number) => {
this.handleSort(type)
}
})
// 中间
List() {
ForEach(this.commentList, (item: CommentData, index: number) => {
ListItem() {
// 列表项组件 - 将item对象, 通过prop传值, 传递下去
InfoItem({
index: index,
itemObj: item,
onLikeClick: (index: number) => {
// 此处的this → 访问的是外部环境的 this → 就是父组件
this.handleLike(index)
}
})
}
})
}
.layoutWeight(1)
.padding({ bottom: 10 })
// 底部
InfoBottom({
onSubmitComment: (content: string) => {
this.handleSubmit(content)
}
})
}
.width('100%')
.height('100%')
}
}
4.5 @Link双向同步
interface Person {
name: string
age: number
}
@Entry
@Component
// 父组件
struct KnowledgePage {
@State count: number = 0
@State person: Person = {
name: 'zs',
age: 18
}
build() {
Column() {
Text('父组件')
.fontSize(30)
Text(this.count.toString())
Text(JSON.stringify(this.person))
Button('修改数据')
.onClick(() => {
this.count++
})
SonComponent({
count: this.count,
person: this.person
})
}
.padding(10)
.height('100%')
.backgroundColor('#eee')
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 100 })
}
}
@Component
// 子组件
struct SonComponent {
@Link count:number
@Link person: Person
// 编写 UI
build() {
Column({ space: 20 }) {
Text('我是子组件')
.fontSize(20)
Text(this.count.toString())
Text(JSON.stringify(this.person))
Column() {
Button('修改count')
.onClick(() => {
// this.count++
this.person.age++
})
}
}
.backgroundColor('#a6c398')
.alignItems(HorizontalAlign.Center)
.width('80%')
.margin({ top: 100 })
.padding(10)
.borderRadius(10)
}
}
4.6 @Provide \@Consume后代组件
interface Car {
name: string
brand: string
}
@Entry
@Component
// 顶级组件
struct RootComponent {
@Provide themeColor: string = 'yellow'
@Provide car: Car = {
name: '小黄',
brand: '美团'
}
build() {
Column() {
Text('顶级组件')
.fontSize(30)
.fontWeight(900)
Text(this.themeColor)
Text(JSON.stringify(this.car))
// 二级组件
ParentComponent()
ParentComponent()
}
.padding(10)
.height('100%')
.backgroundColor('#ccc')
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 100 })
}
}
@Component
// 二级组件
struct ParentComponent {
@Consume themeColor: string
// 编写 UI
build() {
Column({ space: 20 }) {
Text('我是二级组件')
.fontSize(22)
.fontWeight(900)
Text(this.themeColor)
// 内层子组件
SonComponent()
}
.backgroundColor('#a6c398')
.alignItems(HorizontalAlign.Center)
.width('90%')
.margin({ top: 50 })
.padding(10)
.borderRadius(10)
}
}
@Component
// 内层组件
struct SonComponent {
@Consume themeColor: string
@Consume car: Car
// 编写 UI
build() {
Column({ space: 20 }) {
Text('我是内层组件' + this.themeColor)
.fontSize(20)
.fontWeight(900)
.onClick(() => {
// this.themeColor = 'orange'
this.car.name = '小绿'
})
Text(JSON.stringify(this.car))
}
.backgroundColor('#bf94e4')
.alignItems(HorizontalAlign.Center)
.width('90%')
.margin({ top: 50 })
.padding(10)
.borderRadius(10)
}
}
4.7 @ Observed &@ObjectLink嵌套对象数组属性变化
interface IPerson {
id: number
name: string
age: number
}
@Observed
class Person {
id: number
name: string
age: number
constructor(obj: IPerson) {
this.id = obj.id
this.name = obj.name
this.age = obj.age
}
}
@Entry
@Component
struct ObservedAndLink {
@State personList: Person[] = [
new Person({
id: 1,
name: '张三',
age: 18
}),
new Person({
id: 2,
name: '李四',
age: 19
}),
new Person({
id: 3,
name: '王五',
age: 20
})
]
build() {
Column({ space: 20 }) {
Text('父组件')
.fontSize(30)
List({ space: 10 }) {
ForEach(this.personList, (item: Person, index: number) => {
ItemCom({
info: item,
addAge: () => {
// 修改嵌套的数据 => 普通的情况, 监视不到更新
item.age++ // 如果能监视到
AlertDialog.show({
message: JSON.stringify(this.personList)
})
// this.personList.splice(index, 1, item) // 无需手动替换更新
}
})
})
}
}
.backgroundColor('#cbe69b')
.width('100%')
.height('100%')
.padding(20)
}
}
@Component
struct ItemCom {
@ObjectLink info: Person
addAge = () => {
}
build() {
ListItem() {
Row({ space: 10 }) {
Text('姓名:' + this.info.name)
Text('年龄:' + this.info.age)
Blank()
Button('修改数据')
.onClick(() => {
// this.addAge()
this.info.age++
})
}
.backgroundColor(Color.Pink)
.padding(10)
.width('100%')
}
}
}