![uni-app](https://web-assets.dcloud.net.cn/unidoc/zh/logo-en-D.png)
![uni-app](https://web-assets.dcloud.net.cn/unidoc/zh/logo-en.png)
English
在js环境中,服务器下发的字符串,通过uni.request获取后,会默认转成json对象,开发者可以通过.操作符
获取对象的属性,绑定到界面上。
// js写法
// 假使服务器返回的json数据是:{code:0,data:[{"plugin_name":"插件名称A"}]}
// 这是一个对象,拥有code、data这2个子属性,data属性的值是一个数组,该数组内又有若干子对象,子对象有属性`plugin_name`。
uni.request({
url: "https://ext.dcloud.net.cn/plugin/uniappx-plugin-list",
success: (res) => {
let resData = res.data.data
console.log(resData[0].plugin_name) // resData是一个js对象数组,通过下标拿到第一个对象,然后通过.来访问对象的plugin_name属性
}
})
但在uts等强类型语言中无法这样,会报resData[0]无法安全访问、没有plugin_name属性,因为resData是个可为空的any类型,你确实没有为它定义过任何属性。
在uts中,提供了2种方案:
UTSJSONObject是uts的内置对象,它无法使用.操作符
,但可以用下标和keypath来访问json数据。
// uts写法
// 假使服务器返回的json数据是:{code:0,data:[{"plugin_name":"插件名称A"}]}
uni.request({
url: "https://ext.dcloud.net.cn/plugin/uniappx-plugin-list",
success: (res) => {
console.log(res.data) // 这是一个any类型,不能直接使用
console.log(res.data as UTSJSONObject) // 需要把这个any类型,as成UTSJSONObject类型
console.log(res.data as UTSJSONObject["data"]) //UTSJSONObject支持通过下标访问属性data,但返回的仍然是any类型
let resData = (res.data as UTSJSONObject)["data"] as UTSJSONObject[] // as成UTSJSONObject数组
if (resData!=null) {
console.log((resData[0]) // 访问数组的第一个数组项目,仍然是any类型,仍然需要转换
console.log((resData[0] as UTSJSONObject)["plugin_name"]) // 转为UTSJSONObject后通过下标访问plugin_name属性
}
}
})
上面代码中打印日志部分是为了方便初学者理解,实际开发时代码行数不会多几行,主要是多几次as做类型转换。
上面的写法是每层数据都as,通过下标来访问。UTSJSONObject还支持keypath,这是一种可穿透多层数据访问的写法,传入一个path字符串,返回相应数据。
// uts写法
// 假使服务器返回的json数据是:{code:0,data:[{"plugin_name":"插件名称A"}]}
uni.request({
url: "https://ext.dcloud.net.cn/plugin/uniappx-plugin-list",
success: (res) => {
let resData = res.data as UTSJSONObject
if (resData!=null) {
console.log(resData.getString("data[0].plugin_name")) //直接访问data属性的第一个数组项目里的plugin_name属性
}
}
})
除了getString,还有getNumber、getBoolean、getJSON、getArray、getAny。只要keypath的路径输入正确、类型正确,就可以取得值。当然path没有代码提示。
更多详见UTSJSONObject
为json数据定义一个type的自定义类型,明确好对象的属性名称及类型,然后把这个type通过泛型传给request方法,res拿出来的就是转换好的类型,就可以直接.操作符
获取属性了。。
转换功能需要HBuilderX 3.9+、安装了uni-app x真机运行插件、且打开的标签卡是json编辑器。(在ctrl+t新建空白md标签卡,粘贴json数据,会自动切换成json编辑器)
上面的截图是复用了其他图片,如果我们使用https://ext.dcloud.net.cn/plugin/uniappx-plugin-list
这个服务器接口,服务器返回的数据内容,也就是res.data
的数据是这样:
{code:200,desc:"",data:[{"plugin_name":"插件名称A","plugin_id":123}]}
注意: 不要把res和res.data搞错了,要看res.data
的数据格式,而不是res
的格式。res
的格式是不变的。
观察返回的数据,res.data
返回一个对象,拥有code、desc、data这3个子属性,code是number类型,desc是string类型,而data的类型又是一个数组。
该数组内又是若干子对象,子对象有属性plugin_id
和plugin_name
等。
那么使用转换工具,生成的类型定义是这样:
type Data = {
plugin_id: number;
plugin_name: string;
}
type IRootType = {
code: number;
desc: string;
data: Data[];
}
因type不可嵌套,生成了2个type。注意顺序,Data这个type需写在前面,因为后面要引用它。引用代码执行时如未定义该类型,会报错。
<script>
根下,也就是export default{}之前。然后给uni.request传入泛型参数<IRootType>
,返回的res.data自动转换好了类型,可以直接.
属性了。注意: 因为res.data
是对象,所以泛型那里直接使用<IRootType>
。有的服务器接口返回的res.data
是数组,就需要在泛型那里写成<IRootType[]>
<script>
type Data = {
plugin_id : number;
plugin_name : string;
}
type IRootType = {
code : number;
desc : string;
data : Data[];
}
export default {
onLoad() {
uni.request<IRootType>({ // 通过<IRootType>要求request方法的返回值转为IRootType类型
url: "https://ext.dcloud.net.cn/plugin/uniappx-plugin-list",
success: (res) => {
console.log(res.data)
console.log(res.data instanceof IRootType) //true res.data已经被转换为type了
console.log(res.data?.data) //因为联网数据不可控,转换可能失败,所以这里需要用?.的方式做安全访问
let resData = res.data?.data
if(resData!=null && resData.length>0){ //判断一下数组不为空
console.log(resData[0])
console.log(resData[0].plugin_name)
}
}})
},
}
</script>
与UTSJSONObject方式相比,type方式在使用数据时可以使用.操作符
,有代码提示。虽然需要定义type,但由于有工具可以自动生成type,所以整体使用体验,比UTSJSONObject方式方便一点。
type+泛型这个方式,也是ts开发者惯用的方式。
但不熟悉ts的开发者,可能不了解type和泛型。下面讲解下。
type就是自定义一个类型。下面定义了一个数据类型DataType,该类型有2个属性,plugin_id
和plugin_name
,这2个属性的类型分别是number和string。
type DataType = {
plugin_id : number,
plugin_name : string
}
有了这个类型,再给它json数据进行实例化,就达到了给json数据定义类型的目标。给json数据定义好类型,就可以自由的使用.操作符
获取属性了。
详见type
而泛型,是一个对方法参数进行通用的类型描述。它告诉一个支持泛型的方法,给方法传入什么类型,方法就会返回什么类型。
不过uts的泛型支持还没有达到ts的泛型完善度,详见泛型
uni.request方法是支持泛型的,这意味着返回结果可以有很多种类型。
所以可以把你定义的DataType类型通过<T>
的方式传给uni.request方法,尖括号要写在方法名和左圆括号中间。
这个方法就会把返回的res.data
转换为你传入的DataType类型。
其实如果type不用于vue data数据的类型,那么type只需要定义在执行前就可以。
但实际开发中,type大多用于data的类型定义,而想给data定义类型,那就得写在data的前面,也就是export default{}之前了。
网络返回的数据,有可能缺少某些属性,导致报错。测试需要把可为空的属性类型声明为"或null",或者使用?:
type DataType = {
plugin_id : number,
plugin_name : string,
age : number | null, //属性可为null
}
如您不了解null的安全使用,详见
如果res.data返回的是对象,那么泛型调用时直接uni.request<IRootType>(
如果res.data返回的是数组,那么传泛型时必须传入数组格式uni.request<IRootType[]>(
,注意多了一个[]
服务器返回的json数据,其属性键名有可能存在一些type不支持的字符。
比如{"a:b":"123","a-b":"456"}
,这些键名对于type来讲都是非法的。转换type就会失败。
hx的json转type工具,会对一些敏感符合和关键字自动转义。但也有无法转移的符号和词,详见:type
如果你的服务器数据涉及这类问题且数据格式不可改,那只能改用UTSJSONObject方式。
再举一个实际中更常见的例子。联网获取插件市场的插件列表数据,并绑定在模板上,还可以翻页。
翻页需要用到...展开操作符
<template>
<list-view style="flex: 1;background-color: #ffffff;" @scrolltolower="loadData">
<template v-for="(item, index) in dataList" :key="index">
<list-item style="flex-direction: row; margin-top: 10px; padding: 10px; height: 100px;">
<image :src="item.plugin_img_link" style="width: 120px;"></image>
<text style="flex: 1;">{{item.plugin_name}}</text>
</list-item>
<list-item key="loading" v-if="index==dataList.length-1">
<text style="padding: 10px; text-align: center; background-color: #f8f8f8; margin-bottom: 1px;">{{loadingText}}</text>
</list-item>
</template>
</list-view>
</template>
<script>
type Data = {
plugin_id : number;
plugin_name : string;
plugin_img_link : string;
plugin_intro : string;
is_paid : number;
collection_count : number;
support_count : number;
buy_count : number;
download_count : number;
score : number;
rater_count : number;
tags : string[];
category_level1_name : string;
category_level2_name : string;
update_date : string;
author_avatar_link : string;
author_name : string;
}
type IRootType = {
code : number;
desc : string;
data : Data[];
}
export default {
data() {
return {
dataList: [] as Data[],
loading: false,
isEnded: false,
loadingError: '',
$currentPage: 1
}
},
computed: {
loadingText() : string {
if (this.loading) {
return "加载中..."
} else if (this.isEnded) {
return "没有更多了"
} else if (this.loadingError.length > 0) {
return this.loadingError
} else {
return ""
}
}
},
onLoad() {
this.loadData()
},
methods: {
loadData() {
if (this.loading || this.isEnded) {
return
}
this.loading = true
uni.request<IRootType>({
url: "https://ext.dcloud.net.cn/plugin/uniappx-plugin-list",
data: {
page: this.$currentPage, //当前页码
page_size: 10 //每页列表项目数量
},
success: (res) => {
const responseData = res.data
if (responseData == null) {
return
}
//...是展开运算符,本句用于把联网获取的数组合并到data数组里。当第一次执行时,dataList为空,push进去了第一页的数据,后续页面也同理
this.dataList.push(...responseData.data)
if (responseData.data.length == 0) {
this.isEnded = true
} else {
this.$currentPage++
}
},
fail: (err) => {
this.loadingError = err.errMsg
},
complete: () => {
this.loading = false
}
})
},
}
}
</script>