一、问题概述与前提准备
1.问题概述
遇到从接口中获取的数据层次非常多的情况该如何处理?
2.前提
(1)任务界面实现状态与目标
已经有了静态页面结构,希望从远端API接口中获取动态数据
静态的界面:
目标界面:
(2)任务数据来源
数据从以下接口获取(具体取得方法详见juejin.cn/spost/73671…)
// 数据获取接口
https://api-harmony-teach.itheima.net/hm/question/list
二、处理过程与分析
1.需要调整的目标代码
实质上仅有两处代码需要调整
(1)src/main/ets/views/home/HomeCategoryComp.ets:
import { HdHttp } from '../../common/utils/request'
import { iQuestionType } from '../../models/CategoryModel'
import { QuestionListComp } from './QuestionListComp'
@Entry
@Component
export struct HomeCategoryComp {
@State
questionTypeList: iQuestionType[] = [
{
id: 1,
name: 'html',
displayNewestFlag: 0
},
{
id: 2,
name: 'css',
displayNewestFlag: 1
}
]
@State index: number = 0
aboutToAppear(): void {
this.loadData()
}
// 1.0 获取题目分类数据
async loadData() {
// 2.0 调用接口,注意:泛型传的是 iQuestionType[]
let res = await HdHttp.get<iQuestionType[]>('hm/question/type')
// 3. 0 将接口响应回来的数组赋值给状态属性
this.questionTypeList = res.data
}
@Builder
TabItemBuilder(q: iQuestionType, index: number) {
Row() {
Stack({ alignContent: Alignment.Bottom }) {
Text(q.name)
.fontSize(15)
.height(43)
.fontColor(this.index === index ? Color.Black : Color.Gray)
Text()
.width(this.index === index ? 20 : 0)
.height(2)
.backgroundColor(Color.Black)
.animation({ duration: this.index === index ? 300 : 0 })
}
.padding({ left: index === 0 ? 16 : 0, })
if (q.displayNewestFlag === 1) {
Image($r("app.media.ic_home_new"))
.width(32)
.height(14)
.objectFit(ImageFit.Contain)
.margin({ left: 4 })
}
}
.padding({ right: this.questionTypeList.length === index + 1 ? 54 : 16 })
}
build() {
Stack({ alignContent: Alignment.TopEnd }) { //设置堆叠位置为右上角
Tabs({ index: this.index }) {
ForEach(this.questionTypeList, (item: iQuestionType, index: number) => {
TabContent() {
// 每个tab标签都有一个自己的List组件
QuestionListComp()
}.tabBar(this.TabItemBuilder(item, index))
})
}
.height(450)
.divider({
strokeWidth: $r('app.float.common_border_width'),
color: $r('app.color.common_gray_border')
}) //设置tabbar下面的横线样式
.barMode(BarMode.Scrollable) // 设置tabs可以滚动
.barHeight(44)
.onChange(i => this.index = i) // 切换tabbar内容
Row() {
// 过滤条件按钮
Image($r('app.media.ic_home_filter'))
.width(22)
.height(44)
.objectFit(ImageFit.Contain) // 设置图片按照容器大小填满
}
.width(54)
.height(44)
.justifyContent(FlexAlign.Center) // 图片居中对齐
.backgroundColor(Color.White) // 设置背景色位白色,使过滤条件图标能遮盖住tabbar的文字
}
}
}
(2)src/main/ets/views/home/QuestionListComp.ets:
import { QuestionItemComp, QuestionItem } from '../QuestionItemComp'
@Component
export struct QuestionListComp {
build() {
Column() {
List() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9], (item:number) => {
ListItem(){
QuestionItemComp({
item: {
id: '1',
stem: '请总结一下Vue2中生命周期函数有哪些'+item,
difficulty: 3,
likeCount: 10,
views: 20,
readFlag: 1
} as QuestionItem
})
.padding({left:10,right:10})
}
})
}
}
}
}
2.调整部分
下面直接给出调整过的部分及说明(完整源代码附在最后):
QuestionListComp.ets
这是要处理的主体内容:
(1)接口部分:
(2)必要元素部分
分为数据、方法两个部分,其中数据可分为来源数据和处理数据,方法可分为过程与时机,下面给出本例的对应:
-
数据
- 来源数据:typeid,使用@Prop修饰,表示从父组件中获取数据,但不能传递给父组件
- 处理数据:list,使用@State修饰,说明是可见的、实时变动的状态数据
-
方法
- 过程方法:getListByTypeId(),是本部分的主体方法
- 时机方法:即生命周期函数aboutToAppear(),表示在页面一显示时即执行内部方法。过程方法需时机方法中生效。
(3)结构呈现部分
该部分放在build(){}方法中,是前端页面直接可见的部分,相当与将所得数据以前端需要的布局方式渲染出来的过程。
下面改动了ForEach方法中的内容,替换掉了作为遍历数组的参数1,也替换掉了参数2中限制必要的item参数的数据类型,这个类型是先前已经迁入项目的静态页面组件QuestionItemComp.ets中预先定义好的
下面是QuestionItemComp.ets中定义的接口类型,和上图中注释部分可以对应理解
export interface QuestionItem {
id: string
stem: string
difficulty: number
likeCount: number
views: number
readFlag: 0 | 1
}
ListItem中的实质变动只有一行,即注释掉写死的内容后,改用:
// 后面的item应上方传入的item
QuestionItemComp({item:item})
这里留下一个伏笔, 前面的item是QuestionItemComp.ets中定义的数据 item: QuestionItem = {} as QuestionItem
在上面的定义中,前面不需要加@State修饰,不会出现问题,而在本组件QuestionListComp.ets中,不加@State即无法正常显示,这又是什么原因呢?
如果大家有类似的问题欢迎在评论区回复讨论。日后我也会做一个总结说明。
HomeCategoryComp.ets
仅改动一处
三、总结
1.项目过程流程图(数据动态化)
下面用流程图来对本次研究内容进行说明:
2.结构与路径的总结
注意几种结构与路径
(一)父子组件结构
(二)组件元素结构(典型)
重点理解一下本案例,虽然是典型案例,而有些案例可能更简单,另一些案例可能更复杂,但本案例难度适中,是相对有代表性的。
下面再对本项目结构做一下总结
本项目的结构:
- 限制部分(限制格式、功能,如接口、类等,本例中是接口。在规范度高的项目中可作为组件抽取出)
主体部分(分为数据与方法)
数据
- 来源数据(初始数据)
- 处理数据(加工数据)
方法
- 过程方法
- 时机方法
呈现部分
- 前端文本内容与布局方法
3.改动及批准后代码(源码)
QuestionListComp.ets
import { HdHttp } from '../../common/utils/request';
import { QuestionItemComp, QuestionItem } from '../QuestionItemComp'
// 限制部分-接口
/**
* 报文数据
*/
export interface iListData {
/**
* 总页数
*/
pageTotal: number;
/**
* 数据集合
*/
rows: QuestionItem[];
/**
* 总数
*/
total: number;
}
@Component
export struct QuestionListComp {
// 必要元素-初始数据
// 这个typeid是从cate组件传入的
@Prop typeid: number = 0
// 必要元素-加工数据
// 定义状态属性,类型:QuestionItem[]
@State list: QuestionItem[] = []
// 必要元素-时机方法
aboutToAppear(): void {
this.getListByTypeId()
}
// 必要元素-处理方法
async getListByTypeId(){
// 1.请求接口传入typeid获取到数据 get https://api-harmony-teach.itheima.net/hm/question/list
let res = await HdHttp.get<iListData>(`/hm/question/list?questionBankType=10&type=${this.typeid}`)
// 2.将服务器的数据赋值给状态变量
// AlertDialog.show({message:JSON.stringify(res.data,null,2)})
this.list = res.data.rows
AlertDialog.show({message:JSON.stringify(this.list,null,2)})
}
build() {
Column() {
List() {
ForEach(this.list, (item:QuestionItem) => {
ListItem(){
// QuestionItemComp({
// item: {
// id: '1',
// stem: '请总结一下Vue2中生命周期函数有哪些'+item,
// difficulty: 3,
// likeCount: 10,
// views: 20,
// readFlag: 1
// } as QuestionItem
// })
QuestionItemComp({item:item})
.padding({left:10,right:10})
}
})
}
}
}
}
HomeCategoryComp.ets
仅在77行有一处改动,详见:
二、1.需要调整的目标代码 (1)src/main/ets/views/home/HomeCategoryComp.ets
// 每个tab标签都有一个自己的List组件
QuestionListComp({typeid:item.id})