uni-app x之uts数据类型

242 阅读13分钟

uts是什么

uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。uni-app x 是一个庞大的工程,它包括uts语言、uvue渲染引擎、uni的组件和API、以及扩展机制。

uts在Android平台编译为kotlin、在iOS平台编译为swift、在鸿蒙next平台上编译为ArkTS、在Web和小程序平台编译为js。

在Android平台,uni-app x 的工程被整体编译为kotlin代码,本质上是换了vue写法的原生kotlin应用,在性能上与原生kotlin一致。

uts 全称 uni type script,是一门跨平台的、高性能的、强类型的现代编程语言。它在不同平台,会被编译为不同平台的native语言。

uts 采用了与 ts 基本一致的语法规范,支持绝大部分 ES6 API。

但为了跨端,uts进行了一些约束和特定平台的增补。

过去在js引擎下运行支持的语法,大部分在uts的处理下也可以平滑的在kotlin和swift中使用。但有一些无法抹平,需要使用条件编译。

和uni-app的条件编译类似,uts也支持条件编译。写在条件编译里的,可以调用平台特有的扩展语法。

uts是一门语言。也仅是一门语言,不包含ui框架。

uvue是DCloud提供的跨平台的、基于uts的、使用vue方式的ui框架。

uts相当于js,uvue相当于html和css。它们类似于v8和webkit的关系,或者类似于dart和flutter的关系。

uts这门语言,有2个用途:

  1. 开发uni-app 和 uni-app x 的原生扩展插件:因为uts可以调用所有原生能力。
  2. uts和uvue一起组合,开发原生级的项目,也就是 uni-app x 项目

创建uniappx项目

uni-app x的项目,manifest.json中会多一个节点"uni-app-x" : {}。这是HBuilder识别项目类型的标记。如手动增删这个节点,需对项目点右键"重新识别项目类型"

强类型

js是无类型的,TypeScript 的 type 就是类型的意思,给js加上了类型。它的类型定义方式是在变量名后面通过加冒号和类型来进行定义。uts 中声明变量可以用 let 或 const。

let str :string = "hello"; // 声明一个字符串变量
str = "hello world"; // 重新赋值
const str :string = "hello"; // 声明一个字符串常量
str = "hello world"; // 报错,不允许重新赋值

方法的参数、返回值,也通过冒号定义。

function test(score: number): boolean {
	return (score>=60)
}
test(61) // 返回true

方法无返回值时,使用:void

function add(x :string, y :string) :void {
    let z :string = x + " " + y
	console.log(z)
	// 不需要return
}

vue 选项式开发时,冒号被用于赋值,无法通过let、const和冒号来定义data数据的类型。

此时可以使用字面量赋值自动推导;也可以使用 as 关键字来显式声明类型。

<script lang="uts">
	export default {
		data() {
			const date = new Date()
			return {
				s1 : "abc", // 根据字面量推导为string
				n1 : 0 as number, // 这里其实可以根据字面量自动推导,as number写不写都行
				n2, // 不合法,必须指定类型
				n3 as number, // 不合法,uts不支持undefined,必须通过冒号初始化赋值,哪怕赋值为null,见下
				n4 : null as number | null // 合法。定义为可为null的数字,初始值是null,但在使用n4前必须为其赋值数字。(number | null)是一个或的写法,前后顺序没有关系。uts的联合类型只支持 |null 。
				year: date.getFullYear() as number, // 在data里,目前无法通过变量类型推导data项的类型,需使用 as 显式声明类型为number
			}
		}
	}
</script>

现代语言(ts、kotlin、swift),都具备自动识别字面量,进行类型推导的功能。

即:如果开发者声明变量的同时,进行了初始化赋值。那么编译器可以根据赋值的字面量,自动推导出变量类型,不必开发者显式声明。

在定义变量时如果直接赋值字面量,而不使用冒号声明类型,也可以合法运行。

如下2种写法都是合法的,两个变量都是string类型:

// 以下每组写法是等价的
let s1 = "hello" // 根据字面量 "hello",自动推导为string类型
let s2 : string = "hello"
let s3 : string
s3 = "hello"

let b1 = true // 根据字面量 true,自动推导为boolean类型
let b2 : boolean = true

// 以上为字符串和布尔的字面量类型自动推导,数字和数组也支持字面量类型推导,但规则比较复杂,需另见文档

类型判断

判断类型,有好几种方案:typeof、instanceof、isArray。

使用 typeof 可以判断布尔值、数字、字符串、函数。

typeof(true) == "boolean"
typeof("abc") == "string"

let n1 : number = 1
typeof(n1) == "number"

但如果使用 typeof 验证数组,会发现返回的类型值是"object",这与浏览器是相同的逻辑。

如果想验证数组类型,需要使用如下方法:

const a1 = ["uni-app", "uniCloud", "HBuilder"]
console.log(Array.isArray(a1)) // 返回true
console.log(a1 instanceof Array) // 返回true

instanceof,除了验证数组,还可以验证类型,但注意它返回的不是具体类型,而是根据入参的一个boolean值。

对于可为null的类型,调用时需要加问号,否则编译器会报错。

const s: string | null = null // s为一个可为null的字符串
console.log(s?.length) //除非前面已经给s赋值,否则调用s的方法和属性必须加?

uts的多个代码语句,以回车或分号分割。

多行时行尾的分号可以省略。如果写在一行,应以分号分割。

数据类型

UTS 的类型有:

  • 基础类型:boolean、number、string、any、null,都是小写。前3个typeof返回类型名称,null的typeof是object,any的typeof是运行时值的类型。
  • 对象类型:Date、Array、Map、Set、UTSJSONObject,首字母大写。typeof返回"object",判断准确类型需使用 instanceof
  • 使用 type 来自定义类型
  • 特殊类型:function、class、error。
  • 平台专有类型:BigInt、Int、Float、Double、NSString、kotlin.Array...

除了特殊类型,其他类型都可以在变量后面通过:加类型名称来给这个变量声明类型。

字面量可以直接用于赋值、传参,比如 let a = 42,就是把42这个数字字面量赋值给了a

一个表明 null 值的特殊关键字,作为字面量赋值使用时用于表示“空值”或“不存在的值”。

// 正确写法
let t1:any|null = null;
let t2:any|null = null;
function t3(t:any|null) {
}
function t4(): any|null {
	return null;
}

如果要允许为空,可以声明一个变量为可空字符串(写作 string | null)

let b: string | null = "abc" // 可以设置为空
b = null // ok

除了变量,类型的属性也可以为null。此时可以和变量一样使用| null,还可以用?:来代表可选

type obj = {
	id : number,
	name : string,
	age : number | null,
	sex ?: boolean
}

安全的访问null

如果你的代码已经判空,则编译器不会再告警。你可以显式检测 b 是否为 null,在不为 null 的情况下调用 b 的属性和方法。

if (b != null) {
  console.log(b.length) //返回3
}

不判空,使用?.进行安全调用

访问可空变量的属性的第二种选择是使用安全调用操作符 ?.

const a = "uts"
const b: string | null = null
console.log(a.length) // a是明确的string类型,它的属性可以直接调用,无需安全调用
console.log(b?.length) // b可能为null,null没有length属性,在没有判空时,.操作符前面必须加?标记
bob?.department?.head?.name

如果任意一个属性(环节)为 null,这个链式调用就会返回 null。

空值合并运算符(??)是一个逻辑运算符,当左侧的操作数为 null 时,返回其右侧操作数,否则返回左侧操作数。

const foo = null ?? 'default string';
console.log(foo);
// Expected output: "default string"

非空断言运算符(!)将任何值转换为非空类型。可以写 b! ,这会返回一个非空的 b 值(例如:在我们示例中的 string)或者如果 b 为 null,就会抛出一个异常。

const l = b!.length

undefined

此类型仅在目标语言为js时支持

undefined是弱类型语言的特色,在js环境下,undefined == null的结果是true

在kotlin和swift环境下,是没有undefined的,空就是null。

数组

Array,即数组,支持在单个变量名下存储多个元素,并具有执行常见数组操作的成员。

jsswift的Array,是可变长的泛型Array。

  1. 字面量创建
let a1 = [1,2,3];//支持
let a2 = [1,'2',3];//支持

// 需要注意的是,字面量创建的数组,不支持出现空的缺省元素
let a3 = [1,,2,3];//不支持
  1. 使用:Array<>定义数组项的类型
const a1:Array<string> = ["uni-app", "uniCloud", "HBuilder"] //表示数组内容都是string。如不能确定可以写Array<any>
let a2:Array<number> = []; //定义一个数字类型的空数组

typeof 一个 array 得到的是 object。需使用 Array.isArray 或 instanceof 来判断数组类型。

  • 注意:uts 不支持以 Array(arrayLength) 指定数组长度的方式创建一个数组。
// 下面的写法中,a1 将是一个当前 length 为1, 元素是 100 的整型数组。而不是 length 为 100 ,由 100 个空槽组成的数组。
const a1: Array(100); // [100], 当前数组长度为1

Map

Map 是一种 key value 形式的数据类型。

与二维数组相比,Map的key不能重复,并且读写的方式是get()、set()。与UTSJSONObject相比,Map的性能更高,但对数据格式有要求。

//定义一个map1,key为string类型,value也是string类型
const map1: Map<string,string> = new Map();
map1.set('key1', "abc");
console.log(map1.get('key1')) //返回 abc

//定义一个map1,key为number类型,value是Map类型
const map2: Map<number,Map<string,string>> = new Map();
map2.set(1, map1); //把map1作为value传进来
console.log(map2.get(1)); //返回map1
console.log(map2.get(1)?.get("key1")); //返回 abc。因为名为1的key不一定存在,map2.get(1)可能为null,此时需使用 ?. 才能链式调用

通过元素是键值对的数组来创建 Map

const myMap = new Map<number,string>([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

还可以把一个UTSJSONObject转为Map

let userA = {
	name: "zhangsan",
	age: 12,
	sex: 0
} // userA 被推导为UTSJSONObject
let userMap = userA.toMap() //UTSJSONObject有toMap方法

在 uts 中使用 JSON,有3种方式:

  1. 把 json数据转 type,变成一个自定义类型。这不是本章节的内容,详见 type
  2. uts 新增了 UTSJSONObject 对象,可以把 json数据通过字面量赋值 或 JSON.parse()方式,赋值给 uts 内置的 UTSJSONObject 对象。
  3. 由于 UTSJSONObject有toMap()方法,所以也可以转为Map后使用json数据。

UTSJSONObject比较适合初学者入门,也方便兼容js生态的代码。但代码提示和运行性能不及type。

UTSJSONObject,是一个类型,可以在变量的冒号后面使用。
也就是对于形如{x:something}的对象字面量,如果赋值时不指定类型,在 uts 中会被自动推导为 UTSJSONObject。如果你需要转 type,则需显式声明。

除了字面量定义UTSJSONObject对象,经常用到的是通过 JSON.parse(),把一个 JSON 字符串转成UTSJSONObject对象。

uts 内置了大写的 JSON 对象,有parse()、stringify()等方法。注意JSONUTSJSONObject不是一个对象。大写 JSON 内置对象,web端也是存在的。而 UTSJSONObject 是 uts 新增的。

当然,还有更简短的写法,使用HBuilderX 3.9新增的JSON的parseObject()和parseArray()方法:

let s = `{"result":true, "count":42}` // 常见场景中,这个字符串更多来自于网络或其他应用传输。
let jo = JSON.parseObject(s)

let sr = `[{"x":1, "y":2},{"x":3, "y":4}]` // 常见场景中,这个字符串更多来自于网络或其他应用传输。
let jr = JSON.parseArray(s)

访问 UTSJSONObject 中的属性数据

1、操作符

即 `rect.x`、`rect.size.width`。

这种写法比较简单,和js习惯一致,但在 UTS 存在以下限制:
- web可以正常使用
- HBuilderX 4.41+ iOS/Android 也支持`.`操作符,但返回的数据,类型是any | null,想继续使用需要`as`为具体类型,比如:`(rect.size as UTSJSONObject).width`。

2、[""]下标

即 `rect["x"]`。

这是一种通用的方式,不管通过字面量定义的 UTSJSONObject,还是通过 `JSON.parse()`,不管是 web、Android、iOS 哪个平台,都可以使用下标方式访问 UTSJSONObject 属性。

但下标返回的数据,类型是any,想继续使用需要`as`为具体类型。

尤其是有子对象时,需要 `as` 后才能继续访问下一层数据。

3、通过 keyPath 访问 UTSJSONObject 数据

console.log(utsObj.getString("address.detailInfo.street")) // 结果:the wall street

除了直接使用UTSJSONObject外,在 uts 中使用json数据还有2种方式:

  1. UTSJSONObject.toMap() 转为Map对象
  2. 把json字符串或对象字面量通过type转为自定义类型,这是ts里经常使用的方式

type自定义类型

type是关键字,用于给一个类型起别名,方便在其他地方使用。

下面是一个简单的示例,给number类型起个别名tn,在定义变量i时,可以用:tn

type tn = number
let i:tn = 0  // 等同于 let i:number = 0

上述简单的例子在实际开发中没有意义。

在 ts 中常见的用途是给联合类型命名,方便后续简化使用。在 uts 中用的比较多的场景是:

  1. 给函数类型定义别名,以便在共享给其他模块使用。
  2. 用于json对象的定义,在编译为kotlin和swift时,会编译为class。

在 uts 中,type最常见的用途是把json数据转为自定义类型。也就是为json数据提供了一个类型描述。这样json数据就可以继续使用.操作符了。

type PersonType = {
	id : number,
	name : string,
	age : number
}
let person : PersonType = { id: 1, name: "zhangsan", age: 18 }
console.log(person.name) //返回zhangsan

把一个json数组 as 成自定义类型的数组,就可以像在js中那样随便使用json数据了。

let personList = [
	{ id: 1, name: "zhangsan", age: 18 },
	{ id: 2, name: "lisi", age: 16 },
] as PersonType[]
console.log(personList[0].name); //返回zhangsan

但是需要注意,json数据可能不规范,有些属性缺失,此时就需要在定义type时设可为空:

type PersonType = {
	id : number,
	name : string,
	age : number | null, //属性可为null
	enable ?: boolean // 属性可为null
}

如果要给Person类型再加一个子对象 address,要解决这个问题,需要把 address 作为一个单独的类型来定义。

type PersonAddressType = {
	city: string,
	street: string
}
type PersonType = {
	id: number,
	name: string,
	age: number,
	address: PersonAddressType // 把address定义为PersonAddress类型
}

通过JSON.parse转type

type PersonType = {
	id: number,
	name: string
}
let jsonString:string = `{
	"id": 1,
	"name": "zhangsan"
}` // 注意属性必须使用引号包围,否则parse会解析失败返回null

let person = JSON.parse<PersonType>(jsonString) //这是一种泛型的写法,在方法名后面使用<>传入PersonType类型,就可以返回传入的类型。
console.log(person?.name);  // 返回zhangsan。由于person可能为null,parse可能失败,所以需要通过?.来访问属性

联合类型

联合类型(Union Types) 表示取值可以为多种类型中的一种。联合类型使用 | 操作符来分隔每个类型。

// 可为空的 string 类型
type NullableString = string | null;
// 基本类型的联合
type StringOrNumber = string | number;
// 字面量类型的联合
type Alignment = "left" | "right" | "center";
// 对象类型的联合
type Shape = Circle | Square | Triangle;
  • 在uts插件中,对 js 环境(即:uni-app、uni-app x iOS 平台)导出时,暂不支持联合类型

欢迎关注vx:blingbit

搜索框传播样式-白色版.png