05鸿蒙APP开发之加载网络列表

news2024/12/23 17:29:27

目录

  • 1、概述
  • 2、http请求封装
    • 2.1、为什么要封装Http请求?
    • 2.2、封装后的网络请求
  • 3、发起请求并渲染列表数据
    • 第一步:准备一个目前能用的,测试API地址,如下:
    • 第二步:创建对应的实体对象
    • 第三步:调用请求,获取数据,渲染页面
    • 小结
  • 4、运行效果
  • 5、参考资料:

1、概述

为了把上一节(04鸿蒙APP开发之创建一个底部菜单栏及首页列表)中的测试数据,换成网络列表数据,实现动态加载。

官方的DEMO封装得太深,看不出最简化的结构。

基于这些原因,开始写下本篇博文,内容主要包含,网络请求封装、发起请求,及页面渲染。

项目最新的目录结构,如下图所示:

在这里插入图片描述

2、http请求封装

本段内容主要参考了这篇文章,【鸿蒙应用ArkTS开发系列】- http网络库使用讲解和封装,并完善了appendQueryParams、isValidUrl这两个函数,修改回调函数名称为success、fail。

2.1、为什么要封装Http请求?

官方的Http请求,需要在各个页面每次都要写很长一段代码,为了复用,且降低使用复杂度,所以对Http请求做了二次封装。

2.2、封装后的网络请求

包含三个文件:RequestOption.ets、HttpManager.ets、HttpCore.ets,它们的作用如下:

RequestOption.ets

是一个枚举类,主要是构造一个请求所需的参数集合,包括 地址、请求方式、请求参数、返回数据类型、设置请求头信息。具体实现如下:

export interface RequestOptions {
  url?: string;
  method?: RequestMethod;
  queryParams?: Record<string, string>;
  extraData?: string | Object | ArrayBuffer;
  header?: Object;
}

export enum RequestMethod {
  OPTIONS = "OPTIONS",
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  HEAD = "HEAD",
  DELETE = "DELETE",
  TRACE = "TRACE",
  CONNECT = "CONNECT"
}

HttpManager.ets
是一个类,使用单例的方式创建实例,作为页面与Http请求类的桥梁,通过调用HttpCore.request()函数,发起请求。具体实现如下:

//注意这一行非常关键,需要别名,不然无法调用request(options)函数
import { httpCore as HttpCore } from './HttpCore';
import { RequestOptions } from './RequestOption';


export class HttpManager {
  private static mInstance: HttpManager;
  //不允许实例
  private constructor() {
  }

  static getInstance(): HttpManager {
    if (!HttpManager.mInstance) {
      HttpManager.mInstance = new HttpManager();

    }
    return HttpManager.mInstance;
  }

  request<T>(options: RequestOptions): Promise<T> {
    return HttpCore.request(options);
  }
}

HttpCore.ets
是封装原官方的Http请求,进行了回调函数扩展,方便在页面中捕捉回调,获取返回的网络数据,实现页面的状态更新。具体实现如下:

import http from '@ohos.net.http';
import { RequestOptions } from '../net/RequestOption';

export class HttpCore {
  request<T>(requestOption: RequestOptions): Promise<T> {
    return new Promise<T>((success, fail) => {
      this.sendRequest(requestOption)
        .then((response) => {
          if (typeof response.result !== 'string') {
            fail(new Error("Invalid data type"));
          } else {
            let bean: T = JSON.parse(response.result);
            if (bean) {
              success(bean);
            } else {
              fail(new Error("JSON to T failed"));
            }
          }
        })
        .catch((error) => {
          fail(error);
        })
    });
  }

  private sendRequest(requestOption: RequestOptions): Promise<http.HttpResponse> {
    let httpRequest = http.createHttp();
    let successFunction, failFunction;
    const resultPromise = new Promise<http.HttpResponse>((success, fail) => {
      successFunction = success;
      failFunction = fail;
    })

    if (!this.isValidUrl(requestOption.url)) {
      return Promise.reject(new Error("URL format error"));
    }

    let promise = httpRequest.request(this.appendQueryParams(requestOption.url, requestOption.queryParams), {
      method: requestOption.method,
      header: requestOption.header,
      extraData: requestOption.extraData,
      expectDataType: http.HttpDataType.STRING
    });

    promise.then((response) => {
      console.info('Result:' + response.result);
      console.info('code:' + response.responseCode);
      console.info('header:' + JSON.stringify(response.header));

      successFunction(response);
    }).catch((err) => {
      failFunction(err);
    }).finally(() => {
      httpRequest.destroy();
    });

    return resultPromise;
  }

  /**
   *  typescript中高级类型Record
   *  组装参数
   * @param url
   * @param queryParams
   * @returns
   */
  private appendQueryParams(url: string, queryParams: Record<string, string>): string {
    var params: string;
    var i: number = 0;
    for (const key in queryParams) {
      if (i == 0) {
        params += key + "=" + queryParams[key];
      } else {
        params += "&" + key + "=" + queryParams[key];
      }
      i++;
      console.log(key + ":" + queryParams[key]);
    }
    if (params != null) {
      return url + "?" + params
    }
    return url
  }

  /**
   * 校验url是否合法,正则
   * @param url
   * @returns
   */
  private isValidUrl(url: string): boolean {
    var regular = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/;
    if (regular.test(url)) {
      return true;
    }
    return false;
  }
}

//注意这一行非常关键
export const httpCore = new HttpCore();


这三段代码中用到了非常多的陌生关键字,Promise、typeof、.then、.catch、.finally、Record<string, string>、.test,它们是做什么的
,我们来逐一说明。

  • Promise:是一种用于处理异步操作的对象,有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。用于一次网络请求、一次文件读写等操作。
  • .then:then方法的回调函数接收Promise对象的成功结果作为参数,并将其输出到控制台上。
  • .catch:如果Promise对象进入rejected状态,则catch方法的回调函数接收错误对象作为参数,并将其输出到控制台上。
  • .finally、不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法中指定的代码。
  • typeof:操作符来获取变量的类型。
  • Record<string, string>:Record<string, any>
    属性名必须是字符串类型,而属性值可以是任意类型。有点类似数据库存放的一行数据结构,如下例:
 const myRecord:Record<string, string>={ name:'张三' , city: 'Beijing', country: 'China' }
  • .test:是RegExp对象的test()函数,用来测试一个字符串是否与正则表达式匹配。

3、发起请求并渲染列表数据

第一步:准备一个目前能用的,测试API地址,如下:

https://opentdb.com/api.php?amount=10&category=23&difficulty=easy&type=multiple

此API返回的JSON数据如下:

{
	"response_code": 0,
	"results": [{
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "Who was among those killed in the 2010 Smolensk, Russia plane crash tragedy?",
		"correct_answer": "The Polish President",
		"incorrect_answers": ["Pope John Paul II", "Bang-Ding Ow", "Albert Putin"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "What was the first sport to have been played on the moon?",
		"correct_answer": "Golf",
		"incorrect_answers": ["Football", "Tennis", "Soccer"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "Which of these countries remained neutral during World War II?",
		"correct_answer": "Switzerland",
		"incorrect_answers": ["United Kingdom", "France", "Italy"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "How many manned moon landings have there been?",
		"correct_answer": "6",
		"incorrect_answers": ["1", "3", "7"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "In what year was the M1911 pistol designed?",
		"correct_answer": "1911",
		"incorrect_answers": ["1907", "1899", "1917"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "During WWII, in 1945, the United States dropped atomic bombs on the two Japanese cities of Hiroshima and what other city?",
		"correct_answer": "Nagasaki",
		"incorrect_answers": ["Kawasaki", "Tokyo", "Kagoshima"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "These two countries held a commonwealth from the 16th to 18th century.",
		"correct_answer": "Poland and Lithuania",
		"incorrect_answers": ["Hutu and Rwanda", "North Korea and South Korea", "Bangladesh and Bhutan"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "The idea of Socialism was articulated and advanced by whom?",
		"correct_answer": "Karl Marx",
		"incorrect_answers": ["Vladimir Lenin", "Joseph Stalin", "Vladimir Putin"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "Which German field marshal was known as the `Desert Fox`?",
		"correct_answer": "Erwin Rommel",
		"incorrect_answers": ["Ernst Busch", "Wolfram Freiherr von Richthofen", "Wilhelm List"]
	}, {
		"type": "multiple",
		"difficulty": "easy",
		"category": "History",
		"question": "Which one of these was not a beach landing site in the Invasion of Normandy?",
		"correct_answer": "Silver",
		"incorrect_answers": ["Gold", "Juno", "Sword"]
	}]
}

第二步:创建对应的实体对象

主要是包含两个类TestApiBean和ResTestAPiBean。

ResTestAPiBean.ets类实现如下:

import ArrayList from '@ohos.util.ArrayList';
import { TestApiBean } from './TestApiBean';

  export class ResTestAPiBean {
  response_code?: string;
  results?: Array<TestApiBean>;
}

TestApiBean.ets类实现如下:


export class TestApiBean {
  id?:string;
  question?: string;
  correct_answer?: string;
  type?: string;

  constructor(
    id:string,
    question: string,
    correct_answer: string,
    type: string
  ) {
    this.id=id;
    this.question = question;
    this.correct_answer = correct_answer;
    this.type = type;
  }
}

第三步:调用请求,获取数据,渲染页面

Index.ets类的实现如下:

import prompt from '@ohos.promptAction'
import util from '@ohos.util';
//引入自定义类
import { HttpManager } from './net/HttpManager'
import { RequestMethod } from './net/RequestOption';
import { TestApiBean } from './entity/TestApiBean';
import { ResTestAPiBean } from './entity/ResTestAPiBean';

@Entry
@Component
struct Index {
  private tabsController = new TabsController();
  @State index: number = 0; // 选项卡下标,默认为第一个

  //组件Index自带的函数,类似于重写@Override,在build()之前就会执行
  aboutToAppear() {
    this.loadApiTestData();
  }

  //@State此时的状态就非常有用了,当你上下拉刷新UI的时候,只需要重新给此变量赋值就会自动刷新界面(UI)
  @State testApiBeanList: Array<TestApiBean> = new Array<TestApiBean>();

  /**
   * 调用封装的网络请求类,访问网络API并获取列表数据
   */
  loadApiTestData() {
    HttpManager.getInstance().request<ResTestAPiBean>({
      url: "https://opentdb.com/api.php?amount=10&category=23&difficulty=easy&type=multiple",
      method: RequestMethod.GET
    }).then((result) => {
      for (let i = 0; i < result.results.length; i++) {
        let item = result.results[i];
        this.testApiBeanList.push({
          id: util.generateRandomUUID(true),
          question: item.question,
          correct_answer: item.correct_answer,
          type: item.type
        });
      }
    }).catch((error) => {
      console.log(error);
    });
  }

  //开始构建UI
  build() {
    Column() {
      Tabs({ controller: this.tabsController }) {
        TabContent() {
          Column() {
            List({ space: 10, initialIndex: 0 }) {
              ForEach(this.testApiBeanList, (item: TestApiBean, index: number) => {
                // 循环渲染ListItem
                ListItem() {
                  Row() {
                    Column() {
                      Text(item.correct_answer).fontSize(16).fontColor($r("app.color.color_list_title"))
                      Text(item.question).fontSize(14).fontColor($r("app.color.color_list_abstract"))
                    }.width('100%').alignItems(HorizontalAlign.Start)
                  }.width('100%').justifyContent(FlexAlign.Start).padding({ left: 16, right: 16, top: 8, bottom: 8 })
                  .onClick(() => {
                    //选项单击事件
                    prompt.showToast({ message: this.testApiBeanList[index].question, duration: 2000 })
                  })
                }
              }, item => item.id)
            }.width("100%").listDirection(Axis.Vertical).scrollBar(BarState.Auto)

          }.width('100%').height('100%').justifyContent(FlexAlign.Start).backgroundColor("#eeeeee")
        }.tabBar(this.tabHome())

        TabContent() {
          Column() {
            Text("订阅内容").fontSize(32)
          }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor("#eeeeee")
        }.tabBar(this.tabSub)

        TabContent() {
          Column() {
            Text("我的内容").fontSize(32)
          }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor("#eeeeee")
        }.tabBar(this.tabMe)

      }.barPosition(BarPosition.End)
    }.width('100%').height('100%')
  }

  @Builder tabHome() {
    Column() {
      Image(this.index == 0 ? $r('app.media.ic_menu_home_focus') : $r('app.media.ic_menu_home_normal'))
        .size({ width: 25, height: 25 }).margin({ top: 5, bottom: 5 })
      Text("首页").fontSize(16).fontColor(this.index == 0 ? "#2a58d0" : "#6b6b6b")
    }.width('100%').height('100%').onClick(() => {
      this.index = 0;
      this.tabsController.changeIndex(this.index)
    })
  }

  @Builder tabSub() {
    Column() {
      Image(this.index == 1 ? $r('app.media.ic_menu_sub_focus') : $r('app.media.ic_menu_sub_normal'))
        .size({ width: 25, height: 25 }).margin({ top: 5, bottom: 5 })
      Text("订阅").fontSize(16).fontColor(this.index == 1 ? "#2a58d0" : "#6b6b6b")
    }.width('100%').height('100%').onClick(() => {
      this.index = 1;
      this.tabsController.changeIndex(this.index)
    })
  }

  @Builder tabMe() {
    Column() {
      Image(this.index == 2 ? $r('app.media.ic_menu_me_focus') : $r('app.media.ic_menu_me_normal'))
        .size({ width: 25, height: 25 }).margin({ top: 5, bottom: 5 })
      Text("我的").fontSize(16).fontColor(this.index == 2 ? "#2a58d0" : "#6b6b6b")
    }.width('100%').height('100%').onClick(() => {
      this.index = 2;
      this.tabsController.changeIndex(this.index)
    })
  }
}

小结

  • ForEach必须要求使用Array数据类型来加载数据,Array需要使用push来放入对象数据。
  • aboutToAppear(),是页面自带的函数,在Build构建之前执行,类似java重写函数,加载请求可写在此函数中。

4、运行效果

如下图所示:

在这里插入图片描述

5、参考资料:

网络请求封装:https://blog.csdn.net/q919233914/article/details/130643452






原创不易,求个关注。

在这里插入图片描述

微信公众号:一粒尘埃的漫旅
里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。

公众号与博客的内容不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1326648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10 Vue3中v-html指令的用法

概述 v-html主要是用来渲染富文本内容&#xff0c;比如评论信息&#xff0c;新闻信息&#xff0c;文章信息等。 v-html是一个特别不安全的指令&#xff0c;因为它会将文本以HTML的显示进行渲染&#xff0c;一旦文本里面包含一些恶意的js代码&#xff0c;可能会导致整个网页发…

对大学生创新创业某赛事目前存在的烂尾楼现象的一些研究的分享(1)

经过对”某某网”大学生创新创业大赛国赛第五届-第八届部分金奖项目的研究&#xff0c;进行较为充分的信息溯源、穿透调查&#xff0c;我发现不少项目存在赛事材料画大饼&#xff0c;严重不切合实际&#xff0c;参赛人员并非真正创新创业&#xff0c;赛后迅速销声匿迹、烂尾切割…

2023年全球架构师峰会(ArchSummit北京站2023)-核心PPT资料下载

一、峰会简介 ArchSummit聚焦业界强大的技术成果&#xff0c;秉承“实践第一、案例为主”的原则&#xff0c;展示先进技术在行业中的典型实践&#xff0c;以及技术在企业转型、发展中的推动作用。旨在帮助技术管理者、CTO、架构师做好技术选型、技术团队组建与管理&#xff0c…

机器学习——分类评价指标

【说明】文章内容来自《机器学习——基于sklearn》&#xff0c;用于学习记录。若有争议联系删除。 1、评价指标 对于模型的评价往往会使用损失函数和评价指标&#xff0c;两者的本质是一致的。一般情况下&#xff0c;损失函数应用于训练过程&#xff0c;而评价指标应用于测试过…

Apache Superset如何实现无公网ip实时远程访问本地数据【内网穿透】

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…

CloudCanal x Debezium 打造实时数据流动新范式

简述 Debezium 是一个开源的数据订阅工具&#xff0c;主要功能为捕获数据库变更事件发送到 Kafka。 CloudCanal 近期实现了从 Kafka 消费 Debezium 格式数据&#xff0c;将其 同步到 StarRocks、Doris、Elasticsearch、MongoDB、ClickHouse 等 12 种数据库和数仓&#xff0c;…

[linux]进程间通信-管道pipe的实际用法(写入/读取)

一、需求 现有两个进程A和B&#xff0c;B进程含较为独立且复杂的业务逻辑&#xff0c;A进程为主控进程&#xff0c;现A进程需要控制B进程执行对应的功能&#xff0c;且要保持响应及时。 二、分析 典型进程间通信案例&#xff0c;因此使用linux下的管道方法&#xff08;pipe&…

大创项目推荐 深度学习 机器视觉 人脸识别系统 - opencv python

文章目录 0 前言1 机器学习-人脸识别过程人脸检测人脸对其人脸特征向量化人脸识别 2 深度学习-人脸识别过程人脸检测人脸识别Metric Larning 3 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 人脸识别系统 该项目…

在RT-Thread中使用SystemView进行调试分析

一、SystemView SystemView is a toolkit for visual analysis of any embedded system. SystemView gives complete insight into an application, to gain a deep understanding of the runtime behavior, going far beyond what a debugger is offering. This is particula…

maven学习和maven聚合工程搭建

1.学习maven maven的概念 项目管理工具 &#xff0c;对jar进行依赖管理&#xff0c;编译&#xff0c;打包&#xff0c;单元测试&#xff0c;安装&#xff0c;部署&#xff0c;贯穿整个项目 为什么要学maven 要解决的问题&#xff1a; 不同的开发工具开发出来的项目目录结构…

元旦厦门游--ChatGPT/GPT4科研实践应用与AI绘图技术

2023年我们进入了AI2.0时代。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车&#xff0c;就有可能被淘汰在这个数字化时代&#xff0c;如何能高效地处理文本、文献查阅、PPT…

马蹄集第37周

1、最小子串覆盖 考点&#xff1a;双指针 代码&#xff1a; def judge(s,t,l,r):m [0] * (256)for i in t:m[ord(i)] 1for i in range(l,r1):if s[i] in t:m[ord(s[i])] - 1for i in m:if i > 0:return Falsereturn Truedef main():result "No"s input()t i…

EMNLP 2023 亮点回顾:大模型时代下的 NLP 研究

作为自然语言处理&#xff08;NLP&#xff09;领域的顶级盛会&#xff0c;EMNLP 每年都成为全球研究者的关注焦点。2023 年的会议在新加坡举行&#xff0c;聚集了数千名来自世界各地的专家学者&#xff0c;也是自疫情解禁以来&#xff0c;中国学者参会最多的一次。巧的是&#…

windows 快捷键 win+tab 图标显示工具栏设置

在Windows中&#xff0c;按下Win Tab组合键会打开任务视图&#xff0c;该视图显示当前打开的窗口以及工作区的虚拟桌面。如果你希望在任务视图中显示工具栏设置&#xff0c;可以按照以下步骤进行&#xff1a; 1. 打开设置&#xff1a; 使用快捷键Win I或在任务栏上右键单击开…

南京大学-软件分析-课程05-数据流分析基础理论2

7. 迭代算法与不动点定理关联 我们需要证明自己的Transfer Function是单调的 代码什么时候会停止 8. May/Must Analysis, A Lattice View May Analysis Unsafe result->Safe result 如果我是一个查错的定义&#xff0c; 没有no definition的错误&#xff0c;这是一个不安…

Java开发框架和中间件面试题(2)

8.说说自己对Spring MVC的了解&#xff1f; MVC是一种设计模式&#xff0c;Spring MVC是一款很优秀的MVC框架。Spring MVC可以帮助我们进行更简洁的Web层开发&#xff0c;并且它天生与Spring框架集成。SpringMVC下我们一般把后端项目分为Service&#xff08;处理业务&#xff0…

21、Web攻防——JavaWeb项目JWT身份攻击组件安全访问控制

文章目录 一、JavaWeb二、JWT攻击 一、JavaWeb webgoat 1.java web的配置文件&#xff0c;配置文件一般在META-INF目录下&#xff0c;文件名常为pom.xml或web.xml 2.如何通过请求&#xff0c;查看运行的java代码。 地址信息PathTraversal/profile-upload 直接找到以该字符P…

【网络技术设备安全】BGP 基础与概述-2-中转 AS 中的 IBGP 路由传递

0x01 中转 AS 中的 IBGP 路由传递 参考该图&#xff1a; 上图&#xff0c;我们模拟一个 1.0 的路由通过 AS 65101 来传递 1&#xff1a;通过图可知&#xff0c;A 与 B 之间的 Peer 为 EBGP&#xff0c;B 与 E 之间为 Peer IBGP&#xff0c;E 与 F 之间为 Peer EBGP 邻接 2&a…

python爬取网页图片并下载

python爬取网页图片并下载之GET类型 准备工作 【1】首先需要准备好pycharm&#xff0c;并且保证环境能够正常运行 【2】安装request模块 pip install requestsimport request导入request内置模块 【3】安装lxml模块 pip install lxmlfrom lxml import etree导入lxml.etre…