鸿蒙对接口数据的处理1——以三层数据案例切入研究

488 阅读5分钟

一、问题概述与前提准备

1.问题概述

遇到从接口中获取的数据层次非常多的情况该如何处理?

2.前提

(1)任务界面实现状态与目标

已经有了静态页面结构,希望从远端API接口中获取动态数据

静态的界面:

1715595682635.png

目标界面:

1715595713848.png

(2)任务数据来源

数据从以下接口获取(具体取得方法详见juejin.cn/spost/73671…

// 数据获取接口
https://api-harmony-teach.itheima.net/hm/question/list

1715599257861.png

二、处理过程与分析

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)接口部分:

1715596121488.png

(2)必要元素部分

分为数据、方法两个部分,其中数据可分为来源数据和处理数据,方法可分为过程与时机,下面给出本例的对应:

  • 数据

    • 来源数据:typeid,使用@Prop修饰,表示从父组件中获取数据,但不能传递给父组件
    • 处理数据:list,使用@State修饰,说明是可见的、实时变动的状态数据
  • 方法

    • 过程方法:getListByTypeId(),是本部分的主体方法
    • 时机方法:即生命周期函数aboutToAppear(),表示在页面一显示时即执行内部方法。过程方法需时机方法中生效。

1715596285025.png

(3)结构呈现部分

该部分放在build(){}方法中,是前端页面直接可见的部分,相当与将所得数据以前端需要的布局方式渲染出来的过程。

下面改动了ForEach方法中的内容,替换掉了作为遍历数组的参数1,也替换掉了参数2中限制必要的item参数的数据类型,这个类型是先前已经迁入项目的静态页面组件QuestionItemComp.ets中预先定义好的

1715597085920.png

下面是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.项目过程流程图(数据动态化)

下面用流程图来对本次研究内容进行说明:

1715598303070.png

2.结构与路径的总结

注意几种结构与路径

(一)父子组件结构

1715598519103.png

(二)组件元素结构(典型)

重点理解一下本案例,虽然是典型案例,而有些案例可能更简单,另一些案例可能更复杂,但本案例难度适中,是相对有代表性的。

下面再对本项目结构做一下总结

本项目的结构:

  • 限制部分(限制格式、功能,如接口、类等,本例中是接口。在规范度高的项目中可作为组件抽取出)
  • 主体部分(分为数据与方法)

    • 数据

      • 来源数据(初始数据)
      • 处理数据(加工数据)
    • 方法

      • 过程方法
      • 时机方法
  • 呈现部分

    • 前端文本内容与布局方法

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})