HTTP 数据请求、JSON、HTTP 协议

1.HTTP 数据请求

1.1 HTTP 数据请求

让咱们开发的应用可以通过系统内置的 http 模块,通过 HTTP 协议和服务器进行通讯
在这里插入图片描述
核心概念:

  1. 什么是服务器?
    a. 在网络上提供服务器的一台电脑,比如提供数据服务
  2. 什么是 http 模块?
    a. 鸿蒙内置的一个模块,可以通过 【HTTP 协议】和服务器进行通信
  3. HTTP协议?
    a. 规定了客户端和服务器返回的内容【格式】
    b. 在通讯的时候需要按照格式发送内容,才可以进行通讯

1.2 http模块基本用法

咱们来看看 http 模块如何使用

// 1. 导入
import { http } from '@kit.NetworkKit'

// 2. 创建请求对象
const req = http.createHttp()
// 3. 根据提供好的 地址发送请求,并在 then 中获取服务器响应的内容
req.request('请求地址')
  .then((res: http.HttpResponse) => {
    AlertDialog.show({ message: JSON.stringify(res) })
  })

注意:

  1. 模拟器发请求需要配置权限
  2. module.json5(模块配置)下添加如下代码
  3. 详细权限设置,可以参考地址
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet",
      "2in1"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

其中配置网络权限

 "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ],

获取一条随机笑话:https://api-vue-base.itheima.net/api/joke
在这里插入图片描述

import http from '@ohos.net.http';


@Entry
@Component
struct Notebook_use {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
        Button('看笑话')
          .onClick(() => {
            const req = http.createHttp()
            req.request('https://api-vue-base.itheima.net/api/joke')
              .then((res: http.HttpResponse) => {
                // AlertDialog.show({ message: JSON.stringify(res) })
                this.message = res.result.toString()
              })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

2. 认识URL与JSON

2.1 认识URL

刚刚请求的地址并不是随意的一些字符串,他叫做 URL,也可以叫做统一资源定位符

URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。理论上说,每个有效的 URL 都指向一个唯一的资源。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像,等等。

参考 URL 地址:
https://www.itheima.com/index.html
https://www.itheima.com/images/logo.png
https://hmajax.itheima.net/api/news(结合 Chrome 插件,才可以看到格式化之后的效果)
在这里插入图片描述

  1. 为什么要认识 URL ? mdn
    ○ 虽然是后端给我的一个地址,但是哪部分标记的是服务器,哪部分标记的是资源呢?所以为了和服务器有效沟通我们要认识一下
  2. 什么是 URL ?
    ○ 统一资源定位符,简称网址,用于定位网络中的资源
    ○ 资源:网页,图片,数据,视频,音频等等
  3. URL 的组成?
    ○ 协议,域名,资源路径(URL 组成有很多部分,我们先掌握这3个重要的部分即可)
  4. 什么是 http 协议 ?
    ○ 叫超文本传输协议,规定了浏览器和服务器传递数据的格式(而格式具体有哪些稍后我们就会学到)
  5. 什么是域名 ?
    ○ 标记服务器在互联网当中的方位,网络中有很多服务器,你想访问哪一台,就需要知道它的域名才可以
  6. 什么是资源路径 ?
    ○ 一个服务器内有多个资源,用于标识你要访问的资源具体的位置

2.2 JSON

JSON 是一种按照 JavaScript 对象语法的数据格式,虽然它是基于 JavaScript 语法,但它独立于 JavaScript,许多【程序环境】能够读取(解读)和生成 JSON

JSON是一个字符串,常用于存储和传递数据,开发中常见于 2 个地方:

  1. 传递数据:
  2. 配置文件(xxx.json,xxx.json5)

语法规则:

  1. 是一个字符串(配置文件中两边可以不写引号)
  2. 属性名用双引号包裹,
  3. 属性值如果是字符串也必须用双引号包裹
  4. 对象{},数组[]
const jsonObj='{"name":"jack","age":18}'
const arr = '["你好","西兰花炒蛋"]'

如果要在 JSON 和对象之间转换,就需要用到对应api 啦:(数组也是对象)
JSON.stringify(对象)// 转为 JSON
JSON.parse(JSON) // 转为 对象,需要设置类型(通过变量类型或 as)

interface Person{
  name:string
  age:number
}
// JSON.parse
const jsonObj='{"name":"jack","age":18}'
const obj:Person =  JSON.parse(jsonObj) as Person
const obj2 =  JSON.parse(jsonObj) as Person
const jsonArr = '["你好","西兰花炒蛋"]'
const arr:string[] = JSON.parse(jsonArr)

// JSON.stringify
const p:Person={
  name:'rose',
  age:20
}
AlertDialog.show({
  message:JSON.stringify(p)
})

试一试:
测试完基础语法,将
通过 URL https://hmajax.itheima.net/api/news 获取的数据,转为对应的格式

易错点:

  1. url 地址不能有空格
  2. 写类型的时候,不能出现属性名不一致的情况

3.案例-新闻

结合刚刚测试的获取新闻数据的 URL 地址,咱们来做一个案例:
在这里插入图片描述

需求:

  1. 打开页面时,获取新闻并渲染到页面上
    分析:
  2. 应该在什么时候获取数据

基础模板:


interface NewsResponse {
  message: string
  data: News[]
}

interface News {
  id: number
  title: string
  source: string
  cmtcount: number
  img: string
  time: string
}

@Entry
@Component
struct Day01_02_URL {
  @State newsList: News[] = [{
    "id": 1,
    "title": "5G渗透率持续提升,创新业务快速成长",
    "source": "新京报经济新闻",
    "cmtcount": 58,
    "img": "http://ajax-api.itheima.net/images/0.webp",
    "time": "2222-10-28 11:50:28"
  }]

  build() {
    List({ space: 10 }) {
      ForEach(this.newsList, (news: News) => {
        ListItem() {
          Row({ space: 10 }) {
            Column() {
              Text(news.title)
                .fontSize(16)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .maxLines(3)

              Row({ space: 10 }) {
                Text(news.source)
                  .textExtend()
                Text(`${news.cmtcount}评论`)
                  .textExtend()
                Blank()

                Text(news.time.split(' ')[0])
                  .textExtend()
              }
              .width('100%')

            }
            .justifyContent(FlexAlign.SpaceBetween)
            .alignItems(HorizontalAlign.Start)
            .height(80)
            .layoutWeight(1)

            Image(news.img)
              .width(110)
              .height(80)
              .borderRadius(10)
          }
          .padding(10)
        }
        .backgroundColor(Color.White)
      })

    }
    .padding(10)
    .height('100%')
    .backgroundColor('#f0f0f0')

  }
}

@Extend(Text)
function textExtend() {
  .fontColor(Color.Gray)
  .fontSize(12)
  .textAlign(TextAlign.Start)
}

完整代码:

import http from '@ohos.net.http'

interface NewsResponse {
  message: string
  data: News[]
}

interface News {
  id: number
  title: string
  source: string
  cmtcount: number
  img: string
  time: string
}

@Entry
@Component
struct Day01_02_URL {
  @State newsList: News[] = [{
    "id": 1,
    "title": "5G渗透率持续提升,创新业务快速成长",
    "source": "新京报经济新闻",
    "cmtcount": 58,
    "img": "http://ajax-api.itheima.net/images/0.webp",
    "time": "2222-10-28 11:50:28"
  }]

  aboutToAppear(): void {
    const req = http.createHttp()
    req.request('http://hmajax.itheima.net/api/news')
      .then(res => {
        const newsResponse = JSON.parse(res.result.toString()) as NewsResponse
        this.newsList = newsResponse.data
      })
  }

  build() {
    List({ space: 10 }) {
      ForEach(this.newsList, (news: News) => {
        ListItem() {
          Row({ space: 10 }) {
            Column() {
              Text(news.title)
                .fontSize(16)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .maxLines(3)

              Row({ space: 10 }) {
                Text(news.source)
                  .textExtend()
                Text(`${news.cmtcount}评论`)
                  .textExtend()
                Blank()

                Text(news.time.split(' ')[0])
                  .textExtend()
              }
              .width('100%')

            }
            .justifyContent(FlexAlign.SpaceBetween)
            .alignItems(HorizontalAlign.Start)
            .height(80)
            .layoutWeight(1)

            Image(news.img)
              .width(110)
              .height(80)
              .borderRadius(10)
          }
          .padding(10)
        }
        .backgroundColor(Color.White)
      })

    }
    .padding(10)
    .height('100%')
    .backgroundColor('#f0f0f0')

  }
}

@Extend(Text)
function textExtend() {
  .fontColor(Color.Gray)
  .fontSize(12)
  .textAlign(TextAlign.Start)
}

4.URL 查询参数

目前咱们只能获取固定的数据,如果要查询特定的数据怎么办呢?可以通过 URL 查询参数来实现

4.1 什么是URL查询参数

URL查询参数是提供给网络服务器的额外参数。这些参数是用 & 符号分隔的键/值对列表。在返回资源之前,Web 服务器可以使用这些参数来执行额外的操作。每个 Web 服务器都有自己关于参数的规则,唯一可靠的方式来知道特定 Web 服务器是否处理参数是通过询问 Web 服务器所有者
简而言之: 携带给服务器额外信息,让服务器返回我想要的某一部分数据而不是全部数据

咱么先通过 2 个预先准备好的地址来确认一下
https://www.baidu.com/s?wd=黑马程序员
https://www.baidu.com/s?wd=传智播客
https://www.baidu.com/s?wd=华为
在这里插入图片描述
在这里插入图片描述
拼接在 URL 末尾的就是查询参数:
? 隔开之后使用&分隔的键值对列表

4.2 如何确认 URL 查询参数

实际开发时,服务器的所有者会提供 URL 查询参数的信息,比如

地址: https://api-vue-base.itheima.net/api/joke/list
根据查询参数获取若干条随机笑话
参数名: num
说明: 传递数量即可,比如 5

地址:http://hmajax.itheima.net/api/city
说明获取某个省所有的城市查询
参数名:pname
说明: 传递省份或直辖市名,比如 北京、广东省…

注意:

  1. 响应内容如果是 JSON,需要定义类型,并通过 JSON.parse完成转换 再进一步使用
  2. 如果传递的查询参数是中文,需要通过 encodeURIComponent()进行转换

在这里插入图片描述

4.2.1 基本结构

import http from '@ohos.net.http';


const req = http.createHttp()

@Entry
@Component
struct Day01_03_QueryParams {
  @State pname: string = '';
  @State jokeNum: string = ''
  @State jokes: string[] = []
  @State cities: string[] = []

  build() {
    Column() {
      Column({ space: 10 }) {
        Text('开心一笑')
          .fontSize(30)
        TextInput({ placeholder: '输入笑话条数', text: $$this.jokeNum })
          .type(InputType.Number)
          .onSubmit(() => {
            // url:https://api-vue-base.itheima.net/api/joke/list
            // 参数: num 笑话数量
            AlertDialog.show({
              message: '输入的数值是:' + this.jokeNum
            })
          })
        ForEach(this.jokes, (joke: string) => {
          Text(joke)
        })
      }
      .layoutWeight(1)
      .width('100%')
      .padding(10)

      Divider()
        .color(Color.Pink)
        .strokeWidth(3)
      Column({ space: 10 }) {
        Text('省份城市查询')
          .fontSize(30)
        TextInput({ placeholder: '请输入查询的省份名', text: $$this.pname })// 键盘事件
          .onSubmit(e => {
            // url:https://hmajax.itheima.net/api/city
            // 参数: pname 省份名
            AlertDialog.show({
              message: '输入的省份名是:' + this.pname
            })
          })
        Grid() {
          ForEach(this.cities, (city: string) => {
            GridItem() {
              Text(city)
            }
            .border({ width: 1 })
          })
        }
        .columnsTemplate('1fr '.repeat(4))
        .columnsGap(10)
        .rowsGap(10)
      }
      .layoutWeight(1)
      .width('100%')
      .padding(10)
    }
    .height('100%')
  }
}

4.2.2 示例代码

import http from '@ohos.net.http';

interface JokeResponse {
  msg: string
  code: number
  data: string[]
}

interface CityResponse {
  message: string
  list: string[]
}

const req = http.createHttp()

@Entry
@Component
struct Day01_03_QueryParams {
  @State pname: string = '';
  @State jokeNum: string = ''
  @State jokes: string[] = []
  @State cities: string[] = []

  build() {
    Column() {
      Column({ space: 10 }) {
        Text('开心一笑')
          .fontSize(30)
        TextInput({ placeholder: '输入笑话条数', text: $$this.jokeNum })
          .type(InputType.Number)
          .onSubmit(() => {
            // url:https://api-vue-base.itheima.net/api/joke/list
            // 参数: num 笑话数量
            req.request(`https://api-vue-base.itheima.net/api/joke/list?num=${this.jokeNum}`)
              .then(res => {
                const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
                this.jokes = jokeRes.data
              })
          })
        ForEach(this.jokes, (joke: string) => {
          Text(joke)
        })
      }
      .layoutWeight(1)
      .width('100%')
      .padding(10)

      Divider()
        .color(Color.Pink)
        .strokeWidth(3)
      Column({ space: 10 }) {
        Text('省份城市查询')
          .fontSize(30)
        TextInput({ placeholder: '请输入查询的省份名', text: $$this.pname })// 键盘事件
          .onSubmit(e => {
            // url:https://hmajax.itheima.net/api/city
            // 参数: pname 省份名
            req.request(`http://hmajax.itheima.net/api/city?pname=${encodeURIComponent(this.pname)}`)
              .then(res => {
                const cityRes = JSON.parse(res.result.toString()) as CityResponse
                this.cities = cityRes.list
              })
          })
        Grid() {
          ForEach(this.cities, (city: string) => {
            GridItem() {
              Text(city)
            }
            .border({ width: 1 })
          })
        }
        .columnsTemplate('1fr '.repeat(4))
        .columnsGap(10)
        .rowsGap(10)
      }
      .layoutWeight(1)
      .width('100%')
      .padding(10)
    }
    .height('100%')
  }
}

5.案例-开心一笑

在这里插入图片描述
接下来完成一个案例,需求:

  1. 默认获取若干条笑话,并渲染到页面上
  2. 下拉刷新
  3. 触底加载更多
  4. 点击返回顶部
/**
 * 1. 默认加载
 * 2. 下拉刷新
 * 3. 触底加载更多
 * 4. 点击返回顶部
 * */
@Entry
@Component
struct Day01_07_Jokes {
  @State jokes: string [] = ['笑话 1']
  jokeNum: number = 5
  @State refreshing: boolean = false
  listScroller: Scroller = new Scroller()

  build() {
    Refresh({ refreshing: $$this.refreshing }) {
      Column() {
        // 顶部
        this.HeaderBuilder()
        // 笑话列表
        List({ space: 10, scroller: this.listScroller }) {
          ForEach(this.jokes, (joke: string) => {
            ListItem() {
              Column({ space: 10 }) {
                Text('笑话标题')
                  .fontSize(20)
                  .fontWeight(600)
                Row({ space: 15 }) {
                  titleIcon({ icon: $r('app.media.ic_public_time'), info: '2024-1-1' })
                  titleIcon({ icon: $r('app.media.ic_public_read'), info: '阅读(6666)' })
                  titleIcon({ icon: $r('app.media.ic_public_comments'), info: '评论(123)' })
                }

                Text(joke)
                  .fontSize(15)
                  .fontColor(Color.Gray)
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)
              .padding(20)

            }
            .borderRadius(10)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: Color.Gray })
          })

        }
        .padding(10)
        .layoutWeight(1)

      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f6f6f6')
    }
  }

  @Builder
  HeaderBuilder() {
    Row() {
      Image($r('app.media.ic_public_drawer_filled'))
        .width(25);

      Image($r('app.media.ic_public_joke_logo'))
        .width(30)

      Image($r('app.media.ic_public_search'))
        .width(30);
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .height(60)
    .padding(10)
    .border({ width: { bottom: 2 }, color: '#f0f0f0' })
    .backgroundColor(Color.White)
  }
}

@Component
struct titleIcon {
  icon: ResourceStr = ''
  info: string = ''

  build() {
    Row() {
      Image(this.icon)
        .width(15)
        .fillColor(Color.Gray)
      Text(this.info)
        .fontSize(14)
        .fontColor(Color.Gray)
    }
  }
}

5.1.默认获取若干条笑话

核心步骤:

  1. 生命周期函数中获取数据
    a. aboutToAppear
    b. http 模块获取笑话,若干条
  2. 将获取到的数据转换格式并渲染
    a. JSON.parse as 类型
/**
 * 1. 默认加载
 * 2. 下拉刷新
 * 3. 触底加载更多
 * 4. 点击返回顶部
 * */
import http from '@ohos.net.http'

interface JokeResponse {
  msg: string
  code: number
  data: string[]
}

@Entry
@Component
struct Day01_07_Jokes {
  @State jokes: string [] = ['笑话 1']
  jokeNum: number = 5
  @State refreshing: boolean = false
  listScroller: Scroller = new Scroller()

  getJokes() {
    const req = http.createHttp()
    return req.request(`https://api-vue-base.itheima.net/api/joke/list?num=${this.jokeNum}`)
  }

  aboutToAppear(): void {
    this.getJokes()
      .then(res => {
        const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
        this.jokes = jokeRes.data
      })
  }

  build() {
    Refresh({ refreshing: $$this.refreshing }) {
      Column() {
        // 顶部
        this.HeaderBuilder()
        // 笑话列表
        List({ space: 10, scroller: this.listScroller }) {
          ForEach(this.jokes, (joke: string) => {
            ListItem() {
              Column({ space: 10 }) {
                // 标题部分,截取笑话的开头部分
                Text(joke.split(',')[0])
                  .fontSize(20)
                  .fontWeight(600)
                Row({ space: 15 }) {
                  titleIcon({ icon: $r('app.media.ic_public_time'), info: '2024-1-1' })
                  titleIcon({ icon: $r('app.media.ic_public_read'), info: '阅读(6666)' })
                  titleIcon({ icon: $r('app.media.ic_public_comments'), info: '评论(123)' })
                }

                Text(joke)
                  .fontSize(15)
                  .fontColor(Color.Gray)
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)
              .padding(20)

            }
            .borderRadius(10)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: Color.Gray })
          })

        }
        .padding(10)
        .layoutWeight(1)

      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f6f6f6')
    }
  }

  @Builder
  HeaderBuilder() {
    Row() {
      Image($r('app.media.ic_public_drawer_filled'))
        .width(25);

      Image($r('app.media.ic_public_joke_logo'))
        .width(30)

      Image($r('app.media.ic_public_search'))
        .width(30);
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .height(60)
    .padding(10)
    .border({ width: { bottom: 2 }, color: '#f0f0f0' })
    .backgroundColor(Color.White)
  }
}

@Component
struct titleIcon {
  icon: ResourceStr = ''
  info: string = ''

  build() {
    Row() {
      Image(this.icon)
        .width(15)
        .fillColor(Color.Gray)
      Text(this.info)
        .fontSize(14)
        .fontColor(Color.Gray)
    }
  }
}

易错点:

  1. JSON.parse(res.result.toString()) as 类型 (类型的属性名,必须和返回的值一样)
  2. 查询参数的传递格式有要求:url 地址?xxx=xxx
  3. 字符串方法
    a. slice,传入索引返回一个切割之后的【字符串】,
    b. split,根据传入的内容将字符串切割为【字符串】数组

5.2 下拉刷新

核心步骤:

  1. 通过 Refresh组件实现下拉效果
  2. 在 onRefreshing 中重新获取数据替换默认值即可
  3. 抽取获取笑话的方法,在 2 个地方调用即可
    a. aboutToAppear
    b. onRefreshing
    ⅰ. 延迟关闭下拉刷新的效果
/**
 * 1. 默认加载
 * 2. 下拉刷新
 * 3. 触底加载更多
 * 4. 点击返回顶部
 * */
import http from '@ohos.net.http'

interface JokeResponse {
  msg: string
  code: number
  data: string[]
}

@Entry
@Component
struct Day01_07_Jokes {
  @State jokes: string [] = ['笑话 1']
  jokeNum: number = 5
  @State refreshing: boolean = false
  listScroller: Scroller = new Scroller()

  getJokes() {
    const req = http.createHttp()
    return req.request(`https://api-vue-base.itheima.net/api/joke/list?num=${this.jokeNum}`)
  }

  aboutToAppear(): void {
    this.getJokes()
      .then(res => {
        const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
        this.jokes = jokeRes.data
      })
  }

  build() {
    Refresh({ refreshing: $$this.refreshing }) {
      Column() {
        // 顶部
        this.HeaderBuilder()
        // 笑话列表
        List({ space: 10, scroller: this.listScroller }) {
          ForEach(this.jokes, (joke: string) => {
            ListItem() {
              Column({ space: 10 }) {
                Text(joke.split(',')[0])
                  .fontSize(20)
                  .fontWeight(600)
                Row({ space: 15 }) {
                  titleIcon({ icon: $r('app.media.ic_public_time'), info: '2024-1-1' })
                  titleIcon({ icon: $r('app.media.ic_public_read'), info: '阅读(6666)' })
                  titleIcon({ icon: $r('app.media.ic_public_comments'), info: '评论(123)' })
                }

                Text(joke)
                  .fontSize(15)
                  .fontColor(Color.Gray)
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)
              .padding(20)

            }
            .borderRadius(10)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: Color.Gray })
          })

        }
        .padding(10)
        .layoutWeight(1)

      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f6f6f6')

    }
    .onRefreshing(() => {
      this.getJokes()
        .then(res => {
          const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
          this.jokes = jokeRes.data
          this.refreshing = false
        })
    })
  }

  @Builder
  HeaderBuilder() {
    Row() {
      Image($r('app.media.ic_public_drawer_filled'))
        .width(25);

      Image($r('app.media.ic_public_joke_logo'))
        .width(30)

      Image($r('app.media.ic_public_search'))
        .width(30);
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .height(60)
    .padding(10)
    .border({ width: { bottom: 2 }, color: '#f0f0f0' })
    .backgroundColor(Color.White)

  }
}

@Component
struct titleIcon {
  icon: ResourceStr = ''
  info: string = ''

  build() {
    Row() {
      Image(this.icon)
        .width(15)
        .fillColor(Color.Gray)
      Text(this.info)
        .fontSize(14)
        .fontColor(Color.Gray)
    }
  }
}

5.3 触底加载更多

核心步骤:

  1. 在 onReachEnd 事件中加载更多数据,list 组件
    a. 重新获取数据
    b. 和本地的合并到一起
    c. this.jokes.push(…[‘笑话 1’,’笑话 2‘])
  2. 获取到的数据和本地数据合并
/**
 * 1. 默认加载
 * 2. 下拉刷新
 * 3. 触底加载更多
 * 4. 点击返回顶部
 * */
import http from '@ohos.net.http'
import { curves } from '@kit.ArkUI'

interface JokeResponse {
  msg: string
  code: number
  data: string[]
}

@Entry
@Component
struct Day01_07_Jokes {
  @State jokes: string [] = ['笑话 1']
  jokeNum: number = 5
  @State refreshing: boolean = false
  @State isLoadingMore: boolean = false
  listScroller: Scroller = new Scroller()

  getJokes() {
    const req = http.createHttp()
    return req.request(`https://api-vue-base.itheima.net/api/joke/list?num=${this.jokeNum}`)

  }

  aboutToAppear(): void {
    this.getJokes()
      .then(res => {
        const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
        this.jokes = jokeRes.data
      })
  }

  build() {
    Refresh({ refreshing: $$this.refreshing }) {
      Column() {
        // 顶部
        this.HeaderBuilder()
        // 笑话列表
        List({ space: 10, scroller: this.listScroller }) {
          ForEach(this.jokes, (joke: string) => {
            ListItem() {
              Column({ space: 10 }) {
                Text(joke.split(',')[0])
                  .fontSize(20)
                  .fontWeight(600)
                Row({ space: 15 }) {
                  titleIcon({ icon: $r('app.media.ic_public_time'), info: '2024-1-1' })
                  titleIcon({ icon: $r('app.media.ic_public_read'), info: '阅读(6666)' })
                  titleIcon({ icon: $r('app.media.ic_public_comments'), info: '评论(123)' })
                }

                Text(joke)
                  .fontSize(15)
                  .fontColor(Color.Gray)
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)
              .padding(20)

            }
            .borderRadius(10)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: Color.Gray })
          })

          // 底部的加载更多组件
          ListItem() {
            LoadingProgress()
              .width(50)
          }
          .width('100%')
          .animation({ curve: curves.springMotion() })
        }
        .padding(10)
        .layoutWeight(1)
        .onReachEnd(() => {
          if (this.isLoadingMore === false) {
            this.isLoadingMore = true
            this.getJokes()
              .then(res => {
                const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
                this.jokes.push(...jokeRes.data)
                this.isLoadingMore = false
              })
          }
        })

      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f6f6f6')

    }
    .onRefreshing(() => {
      this.getJokes()
        .then(res => {
          const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
          this.jokes = jokeRes.data
          this.refreshing = false
        })
    })
  }

  @Builder
  HeaderBuilder() {
    Row() {
      Image($r('app.media.ic_public_drawer_filled'))
        .width(25);

      Image($r('app.media.ic_public_joke_logo'))
        .width(30)

      Image($r('app.media.ic_public_search'))
        .width(30);
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .height(60)
    .padding(10)
    .border({ width: { bottom: 2 }, color: '#f0f0f0' })
    .backgroundColor(Color.White)

  }
}

@Component
struct titleIcon {
  icon: ResourceStr = ''
  info: string = ''

  build() {
    Row() {
      Image(this.icon)
        .width(15)
        .fillColor(Color.Gray)
      Text(this.info)
        .fontSize(14)
        .fontColor(Color.Gray)
    }
  }
}

5.4 点击返回顶部

核心步骤:

  1. 点击事件中通过 Scroller 滚到顶部
    a. 通过控制器的方法,滚到顶部即可
/**
 * 1. 默认加载
 * 2. 下拉刷新
 * 3. 触底加载更多
 * 4. 点击返回顶部
 * */
import http from '@ohos.net.http'
import { curves } from '@kit.ArkUI'

interface JokeResponse {
  msg: string
  code: number
  data: string[]
}

@Entry
@Component
struct Day01_07_Jokes {
  @State jokes: string [] = ['笑话 1']
  jokeNum: number = 5
  @State refreshing: boolean = false
  @State isLoadingMore: boolean = false
  listScroller: Scroller = new Scroller()

  getJokes() {
    const req = http.createHttp()
    return req.request(`https://api-vue-base.itheima.net/api/joke/list?num=${this.jokeNum}`)

  }

  aboutToAppear(): void {
    this.getJokes()
      .then(res => {
        const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
        this.jokes = jokeRes.data
      })
  }

  build() {
    Refresh({ refreshing: $$this.refreshing }) {
      Column() {
        // 顶部
        this.HeaderBuilder()
        // 笑话列表
        List({ space: 10, scroller: this.listScroller }) {
          ForEach(this.jokes, (joke: string) => {
            ListItem() {
              Column({ space: 10 }) {
                Text(joke.split(',')[0])
                  .fontSize(20)
                  .fontWeight(600)
                Row({ space: 15 }) {
                  titleIcon({ icon: $r('app.media.ic_public_time'), info: '2024-1-1' })
                  titleIcon({ icon: $r('app.media.ic_public_read'), info: '阅读(6666)' })
                  titleIcon({ icon: $r('app.media.ic_public_comments'), info: '评论(123)' })
                }

                Text(joke)
                  .fontSize(15)
                  .fontColor(Color.Gray)
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)
              .padding(20)

            }
            .borderRadius(10)
            .backgroundColor(Color.White)
            .shadow({ radius: 2, color: Color.Gray })
          })

          // 底部的加载更多组件
          ListItem() {
            LoadingProgress()
              .width(50)
          }
          .width('100%')
          .animation({ curve: curves.springMotion() })
        }
        .padding(10)
        .layoutWeight(1)
        .onReachEnd(() => {
          if (this.isLoadingMore === false) {
            this.isLoadingMore = true
            this.getJokes()
              .then(res => {
                const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
                this.jokes.push(...jokeRes.data)
                this.isLoadingMore = false
              })
          }
        })

      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f6f6f6')

    }
    .onRefreshing(() => {
      this.getJokes()
        .then(res => {
          const jokeRes = JSON.parse(res.result.toString()) as JokeResponse
          this.jokes = jokeRes.data
          this.refreshing = false
        })
    })
  }

  @Builder
  HeaderBuilder() {
    Row() {
      Image($r('app.media.ic_public_drawer_filled'))
        .width(25);

      Image($r('app.media.ic_public_joke_logo'))
        .width(30)
        .onClick(() => {
          this.listScroller.scrollTo({ yOffset: 0, xOffset: 0, animation: true })
        })
      Image($r('app.media.ic_public_search'))
        .width(30);
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .height(60)
    .padding(10)
    .border({ width: { bottom: 2 }, color: '#f0f0f0' })
    .backgroundColor(Color.White)

  }
}

@Component
struct titleIcon {
  icon: ResourceStr = ''
  info: string = ''

  build() {
    Row() {
      Image(this.icon)
        .width(15)
        .fillColor(Color.Gray)
      Text(this.info)
        .fontSize(14)
        .fontColor(Color.Gray)
    }
  }
}

6. 请求方法和数据提交

到目前为止都是为服务器获取数据,接下来咱们来看看如何提交数据
在这里插入图片描述
日常开发中较为常见的需要提交数据给服务器的场景:用户登录,在线买东西。

  const req = http.createHttp()
  req.request('请求地址', {
    method: http.RequestMethod.POST, // 通过枚举的方式设置请求方法,如果是 get 可以省略
    extraData: {} // 数据写在 extraData 中 支持 对象 字符串 ArrayBuffer(二进制数据)
    extraData:'key=value&key2=value2'// 字符串的话 写成 'key=value&key2=value2'....
    // 对象的话需要额外设置其他参数,先用
  })

测试用 url
请求地址(url):http://hmajax.itheima.net/api/register
注册用户
请求方法:POST
参数:username,password
说明:username 用户名(中英文和数字组成, 最少8位),password 密码(最少6位)

import http from '@ohos.net.http'

const req = http.createHttp()

@Entry
@Component
struct Day01_05_SubmitData {
  @State username: string = ''
  @State password: string = ''

  build() {
    Column({ space: 15 }) {
      Text('请求方法和数据提交')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      TextInput({ text: $$this.username, placeholder: '用户名' })
      TextInput({ text: $$this.password, placeholder: '密码' })
        .type(InputType.Password)

      Button('提交数据')
        .width('100%')
        .onClick(() => {
          AlertDialog.show({
            message: `用户名:${this.username}| 密码:${this.password}`
          })
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

在这里插入图片描述

咱们通过这个例子来验证一下能否将输入的数据提交到服务器去

import http from '@ohos.net.http'

const req = http.createHttp()

@Entry
@Component
struct Day01_05_SubmitData {
  @State username: string = ''
  @State password: string = ''

  build() {
    Column({ space: 15 }) {
      Text('请求方法和数据提交')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      TextInput({ text: $$this.username, placeholder: '用户名' })
      TextInput({ text: $$this.password, placeholder: '密码' })
        .type(InputType.Password)

      Button('提交数据')
        .width('100%')
        .onClick(() => {

          req.request('http://hmajax.itheima.net/api/register', {
            method: http.RequestMethod.POST,
            extraData: `username=${this.username}&password=${this.password}`
          })
            .then(res => {
              AlertDialog.show({ message: res.result.toString() })
            })
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

7. 接口文档

到目前为止,咱们请求的 URL 地址,请求的方法都是直接提供好了,日常开发中如何获取这些信息呢?

日常开发中一般通过【接口文档】来获取这部分信息,【接口文档】一般由后端工程师编写
咱们通过接口文档和服务器通讯这个操作,也可以叫做 【调接口】

咱们结合 登录-form 接口来完成用户,关注红色部分
在这里插入图片描述

import http from '@ohos.net.http'

const req = http.createHttp()

@Entry
@Component
struct Day01_05_SubmitData {
  @State username: string = ''
  @State password: string = ''

  build() {
    Column({ space: 15 }) {
      Text('请求方法和数据提交')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      TextInput({ text: $$this.username, placeholder: '用户名' })
      TextInput({ text: $$this.password, placeholder: '密码' })
        .type(InputType.Password)

      Button('注册')
        .width('100%')
        .onClick(() => {

          req.request('http://hmajax.itheima.net/api/register', {
            method: http.RequestMethod.POST,
            extraData: `username=${this.username}&password=${this.password}`
          })
            .then(res => {
              AlertDialog.show({ message: res.result.toString() })
            })
        })
      Button('登录')
        .width('100%')
        .onClick(() => {

          req.request('http://hmajax.itheima.net/api/login', {
            method: http.RequestMethod.POST,
            extraData: `username=${this.username}&password=${this.password}`
          })
            .then(res => {
              AlertDialog.show({ message: res.result.toString() })
            })
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

8. HTTP协议

HTTP协议规定了浏览器发送及服务器返回内容的格式

咱们使用 http 模块和服务器通讯的时候,本质发送,以及接收符合 HTTP 协议规定的内容,接下来咱们来看看内容长啥样

在这里插入图片描述

8.1. 请求报文

请求报文:浏览器按照HTTP协议要求的格式,发送给服务器的内容
在这里插入图片描述
在这里插入图片描述
请求报文的组成部分:

  1. 请求行:请求方法,URL,协议(第一行)
  2. 请求头:以键值对的格式携带的附加信息,比如:Content-Type(第二行开始到空行)
  3. 空行:分隔请求头,空行之后的是发送给服务器的资源(空行)
  4. 请求体:发送的资源(空行之后)
    咱们一般只需要关注,请求头中的一些字段,以及请求体即可
// 代码中虽然没有设置请求头,但是会自动生成
req.request('http://hmajax.itheima.net/api/register', {
    method: http.RequestMethod.POST,
    extraData: `username=${this.username}&password=${this.password}`
  })
  req.request('地址', {
    method: 方法,
    extraData: `数据`,
    header:{
      // 以键值对的方式进行覆盖,或者新增请求头
    }
  })

咱们来测试一下:

  1. 额外增加一个请求头
  2. 修改请求头中contentType的值
req.request('http://hmajax.itheima.net/api/register', {
      method: http.RequestMethod.POST,
      extraData: `username=${this.username}&password=${this.password}`,
      header: {
        // 以键值对的方式进行覆盖,或者新增请求头
        contentType: 'test',
        info: 'good good study'
      }
    })

在这里插入图片描述

调整之后可以看到:

  1. 我们自己设置的信息上去了(虽然没有意义)
  2. 服务器无法正常解析提交的数据,(content-type改了)

8.2. 响应报文

响应报文:服务器按照HTTP协议要求的格式,返回给浏览器的内容
在这里插入图片描述
响应报文组成:

  1. 响应行(状态行):协议、HTTP响应状态码、状态信息(第一行)
  2. 响应头:以键值对的格式携带的附加信息,比如:Content-Type(第一行到空行)
  3. 空行:分隔响应头,空行之后的是服务器返回的资源(空行)
  4. 响应体:返回的资源(空行之后)
    咱们一般需要关注,响应行,响应体即可

不同于请求报文,响应报文是由服务器返回的,咱们无法修改,根据响应报文提供的信息,做出处理即可
在这里插入图片描述
日常开发中,不同接口表示错误的方式不同:

  1. 有的通过状态码来标识
  2. 有的通过响应体中的信息来标识
    根据实际情况来选择使用哪种来判断

重点:

  1. http 模块基于 HTTP 协议进行通讯
  2. HTTP 协议:规定了提交的【数据格式】,以及响应的数据格式
    a. 提交的:请求报文
    ⅰ. 请求行(方法,地址)
    ⅱ. 请求头(Content-Type,提交的数据类型)
    ⅲ. 空行
    ⅳ. 请求体(提交的数据)
    b. 响应的:响应报文
    ⅰ. 响应行
    ⅱ. 响应头
    ⅲ. 空行
    ⅳ. 响应体

8.3. 注册+登录 提交 JSON

注册和登录已经搞定了,但是提交数据的时候需要拼接字符串,较为繁琐

在这里插入图片描述
日常开发中提交数据绝大多数时候使用的是 JSON,咱们来看看如何提交 JSON 给服务器

  req.request(请求地址, {
    method: 请求方法,
    header: {
      // 设置为空 或者
      contentType: 'application/json'
    },
    extraData: {
      // 以对象的形式 会自动转为 JSON
    },
  })

还是刚刚的例子,调整一下 header ,就可以以对象的形式提交 JSON 给服务器啦

import http from '@ohos.net.http'

const req = http.createHttp()

@Entry
@Component
struct Day01_05_SubmitData {
  @State username: string = ''
  @State password: string = ''

  build() {
    Column({ space: 15 }) {
      Text('请求方法和数据提交')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      TextInput({ text: $$this.username, placeholder: '用户名' })
      TextInput({ text: $$this.password, placeholder: '密码' })
        .type(InputType.Password)

      Button('注册')
        .width('100%')
        .onClick(() => {
          req.request('http://hmajax.itheima.net/api/register', {
            method: http.RequestMethod.POST,
            header: {
              contentType: 'application/json'
            },
            extraData: {
              username: this.username,
              password: this.password
            }
          })
            .then(res => {
              AlertDialog.show({ message: res.result.toString() })
           
            })
        })
      Button('登录')
        .width('100%')
        .onClick(() => {
          req.request('http://hmajax.itheima.net/api/login', {
            method: http.RequestMethod.POST,
            header: {
              contentType: 'application/json'
            },
            extraData: {
              username: this.username,
              password: this.password
            }
          })
            .then(res => {
              AlertDialog.show({ message: res.result.toString() })
            })
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值