05鸿蒙APP开发之加载网络列表_queryparams 鸿蒙-程序员宅基地

技术标签: Promise  网络  鸿蒙开发  http  鸿蒙  加载列表  网络请求  

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






原创不易,求个关注。

在这里插入图片描述

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/huangbin123/article/details/135135051

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签