1.HTTP 数据请求
1.1 HTTP 数据请求
让咱们开发的应用可以通过系统内置的 http 模块,通过 HTTP 协议和服务器进行通讯
核心概念:
- 什么是服务器?
a. 在网络上提供服务器的一台电脑,比如提供数据服务 - 什么是 http 模块?
a. 鸿蒙内置的一个模块,可以通过 【HTTP 协议】和服务器进行通信 - 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) })
})
注意:
- 模拟器发请求需要配置权限
- module.json5(模块配置)下添加如下代码
- 详细权限设置,可以参考地址
{
"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 插件,才可以看到格式化之后的效果)
- 为什么要认识 URL ? mdn
○ 虽然是后端给我的一个地址,但是哪部分标记的是服务器,哪部分标记的是资源呢?所以为了和服务器有效沟通我们要认识一下 - 什么是 URL ?
○ 统一资源定位符,简称网址,用于定位网络中的资源
○ 资源:网页,图片,数据,视频,音频等等 - URL 的组成?
○ 协议,域名,资源路径(URL 组成有很多部分,我们先掌握这3个重要的部分即可) - 什么是 http 协议 ?
○ 叫超文本传输协议,规定了浏览器和服务器传递数据的格式(而格式具体有哪些稍后我们就会学到) - 什么是域名 ?
○ 标记服务器在互联网当中的方位,网络中有很多服务器,你想访问哪一台,就需要知道它的域名才可以 - 什么是资源路径 ?
○ 一个服务器内有多个资源,用于标识你要访问的资源具体的位置
2.2 JSON
JSON 是一种按照 JavaScript 对象语法的数据格式,虽然它是基于 JavaScript 语法,但它独立于 JavaScript,许多【程序环境】能够读取(解读)和生成 JSON
JSON是一个字符串,常用于存储和传递数据,开发中常见于 2 个地方:
- 传递数据:
- 配置文件(xxx.json,xxx.json5)
语法规则:
- 是一个字符串(配置文件中两边可以不写引号)
- 属性名用双引号包裹,
- 属性值如果是字符串也必须用双引号包裹
- 对象{},数组[]
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 获取的数据,转为对应的格式
易错点:
- url 地址不能有空格
- 写类型的时候,不能出现属性名不一致的情况
3.案例-新闻
结合刚刚测试的获取新闻数据的 URL 地址,咱们来做一个案例:
需求:
- 打开页面时,获取新闻并渲染到页面上
分析:- 应该在什么时候获取数据
基础模板:
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
说明: 传递省份或直辖市名,比如 北京、广东省…
注意:
- 响应内容如果是 JSON,需要定义类型,并通过 JSON.parse完成转换 再进一步使用
- 如果传递的查询参数是中文,需要通过 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. 点击返回顶部
* */
@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.默认获取若干条笑话
核心步骤:
- 生命周期函数中获取数据
a. aboutToAppear
b. http 模块获取笑话,若干条 - 将获取到的数据转换格式并渲染
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)
}
}
}
易错点:
- JSON.parse(res.result.toString()) as 类型 (类型的属性名,必须和返回的值一样)
- 查询参数的传递格式有要求:url 地址?xxx=xxx
- 字符串方法
a. slice,传入索引返回一个切割之后的【字符串】,
b. split,根据传入的内容将字符串切割为【字符串】数组
5.2 下拉刷新
核心步骤:
- 通过 Refresh组件实现下拉效果
- 在 onRefreshing 中重新获取数据替换默认值即可
- 抽取获取笑话的方法,在 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 触底加载更多
核心步骤:
- 在 onReachEnd 事件中加载更多数据,list 组件
a. 重新获取数据
b. 和本地的合并到一起
c. this.jokes.push(…[‘笑话 1’,’笑话 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 点击返回顶部
核心步骤:
- 点击事件中通过 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协议要求的格式,发送给服务器的内容
请求报文的组成部分:
- 请求行:请求方法,URL,协议(第一行)
- 请求头:以键值对的格式携带的附加信息,比如:Content-Type(第二行开始到空行)
- 空行:分隔请求头,空行之后的是发送给服务器的资源(空行)
- 请求体:发送的资源(空行之后)
咱们一般只需要关注,请求头中的一些字段,以及请求体即可
// 代码中虽然没有设置请求头,但是会自动生成
req.request('http://hmajax.itheima.net/api/register', {
method: http.RequestMethod.POST,
extraData: `username=${this.username}&password=${this.password}`
})
req.request('地址', {
method: 方法,
extraData: `数据`,
header:{
// 以键值对的方式进行覆盖,或者新增请求头
}
})
咱们来测试一下:
- 额外增加一个请求头
- 修改请求头中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'
}
})
调整之后可以看到:
- 我们自己设置的信息上去了(虽然没有意义)
- 服务器无法正常解析提交的数据,(content-type改了)
8.2. 响应报文
响应报文:服务器按照HTTP协议要求的格式,返回给浏览器的内容
响应报文组成:
- 响应行(状态行):协议、HTTP响应状态码、状态信息(第一行)
- 响应头:以键值对的格式携带的附加信息,比如:Content-Type(第一行到空行)
- 空行:分隔响应头,空行之后的是服务器返回的资源(空行)
- 响应体:返回的资源(空行之后)
咱们一般需要关注,响应行,响应体即可
不同于请求报文,响应报文是由服务器返回的,咱们无法修改,根据响应报文提供的信息,做出处理即可
日常开发中,不同接口表示错误的方式不同:
- 有的通过状态码来标识
- 有的通过响应体中的信息来标识
根据实际情况来选择使用哪种来判断
重点:
- http 模块基于 HTTP 协议进行通讯
- 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)
}
}