1. 接口补充
接口可以用来定义类型,也有一些其他的用法。
1. 1 接口继承
接口继承关键字为extends
基本代码为:
interface 接口1{
属性1:类型
}
interface 接口2 extends 接口1 {
属性2:类型
}
举例:
/*
* 接口继承:
* 接口1 extends 接口2
* 注意点:
* 1. 接口1中属性如果在接口2中也有同名同类型的,则自动合并
* */
interface iPerson {
name: string
}
interface iStudent extends iPerson {
// name1:boolean
age:number
}
let stu: iStudent = {
name: '张三',
age: 0
}
1.2 接口实现(类来实现接口)
可以通过接口结合 implements 来限制 类 必须要有某些属性和方法
基本代码:
interface 接口{
属性:类型
方法:方法类型
}
class 类 implements 接口{
// 必须实现 接口中定义的 属性、方法,否则会报错
}
举例:
/*
* 一个类要实现接口
* 作用:接口是用来约束类定义的最小单元
* 语法结构:1.准备一个接口,定义相关的单元(属性,方法)
* 2. 准备一个类来实现这个接口的最小单元
*
* 注意点:类可以扩展自己的属性,方法
* */
interface IDog {
name: string
bark: () => void // 约定狗叫方法是没有参数没有返回值的
}
class Dog implements IDog {
name: string = ''
age: number = 1
bark() {
console.log('汪汪')
}
eat() {
console.log('吃')
}
}
// 使用:
// 两种写法区别
// 1. 使用类作为对象的类型,可以调用类中的所有属性和方法
let dog:Dog = new Dog()
dog.eat()//吃
dog1.bark()//汪汪
// 2. 使用接口作为对象的类型,可以调用类中实现了接口中的属性和方法
let dog1:IDog = new Dog()//因为dog1类型为IDog,而IDog是用classDog类来实现,所以这里也需要进行new
dog1.bark()//汪汪
2.泛型
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用,通俗一点就是: 类型 是可变的!
// 1. 字符串
function getStr(arg: string) {
return [arg] // 返回 string[]类型数组
}
// 2. 数字
function getNum(arg: number) {
return [arg] // 返回 number[]类型数组
}
// 3. 布尔
function getBoolean(arg: boolean) {
return [arg] // 返回 boolean[]类型数组
}
2.1 泛型函数
基本结构
// 定义泛型参数 Type,后续可以使用类型的位置均可以使用比如: 形参、函数内部、返回值
// 1. 参数类型
function 函数名<Type>(形参:Type){
}
// 2. 返回值类型
function 函数名<Type>(形参:Type):Type{
}
举例:
/*
* 泛型函数演示:
* 1. 泛型函数语法:
* function 函数名称<T>(形参:T) {
* let tmp:T = 形参
* return [形参] // T[]
* }
*
* ✨✨总结:泛型函数的使用步骤
* 1. 泛型函数定义(类型使用T来占位)
* 2. 泛型函数调用(传入具体类型)
* */
// 需求:传入一个参数,返回这个参数的数组形式
// 1. 定义泛型函数
function getDataList<T>(args: T) {
return [args] //返回T[]
}
// 2. 调用泛型函数,注意:要传入具体类型
let nums = getDataList<number>(100)
console.log('',nums)
//使用
getDataList<string>('100')
getDataList<boolean>(true)
getDataList<string[]>(['100','200'])
说明:当定义函数的时候不知道将来会传入一个什么类型的数据时,就可以使用泛型函数。
2.2 泛型约束
如果开发中不希望任意的类型都可以传递给 类型参数 , 就可以通过泛型约束来完成
2.2.1 联合类型的举例
// 泛型函数getData的T约束只能传入数字和字符串这两个类型的参数
function getData<T extends number | string>(args:T){
return [args]
}
getData('ok') // ✔️
getData(100) // ✔️
getData(true) // ❌
说明:其中的number | string也可以改为: type 别名 = number | string
2.2.2 枚举类型约束
// 约束T只能是Color枚举中的一个值
function getColor<T extends Color>(color: T) {
return color
}
getColor(Color.White)// ✔️
getColor('White')// ❌
2.2.3 接口类型的约束
function getDataInterface<T extends iPerson>(args:T){
return args
}
getDataInterface<iPerson>({name:'明明',age:20})//✔️
// getDataInterface<iPerson>({})//❌
说明:接口类型的约束必须严格按照接口的类型来,除非接口类型里面有可选参数,不然的话必须严格传参数。
2.3. 多个泛型参数
举例:
function funA<T1 extends string, T2 extends number>(a1: T1, a2: T2) {
console.log('a1=', a1, 'a2=', a2)
}
funA<string, number>('明明', 22)//✔️
// funA<string, boolean>('明明', true) //❌
说明:extends后面跟的类型可以是基本类型如number,string等等,也可以是复杂类型数组等等,也可以是联合类型,枚举类型
2.3.1 例题
例题:
- ① 需要对泛型参数进行类型约定为: string | number
- ② 需要在方法体内部判断形参类型判断 typeof 形参
- ③ 根据形参类型来决定是返回字符串还是返回数字
参考答案
// 1. 定义泛型函数
function getRandomHW<T extends string | number>(args?: T) {
// 1.1 生成一个100以内的随机数
let rdNum = Math.floor(Math.random() * 101)
// return `${rdNum}%`
// return rdNum
// 1.2 判断返回的是字符串还是数字?
// typeof args 获得args这个形参的类型,再与string,number来比较
if (typeof args == 'string') {
return `${rdNum}%`
} else {
return rdNum
}
}
// 2. 调用函数
// let res = getRandomHW<string>()
// // console.log(res.toString())
//
// let res1 = getRandomHW<number>()
// console.log(res1.toString())
@Entry
@Component
struct Index {
build() {
Column() {
}
//3.UI界面上使用
// .height(getRandomHW<string>())
.height(getRandomHW<number>())
.width('100%')
.backgroundColor(Color.Pink)
}
}
说明:在定义泛型函数时,在形参加上一个?号变成一个可选参数,就可以在height(getRandomHW/number/())的括号里不用传入参数,因为number以及指定了是一个数据类型的参数
2.4 泛型接口
例如:
interface iData<T> {
code: number,
msg: string,
data: T
}
let obj1: iData<string[]> = {
code: 200,
msg: 'ok',
//data:[1,2] // ❌
data:['1','2'] // ✔️
}
let obj2: iData<number[]> = {
code: 200,
msg: 'ok',
data:[1,2] // ✔️
//data:['1','2'] // ❌
}
// 要使用JSON.stringify才能正常打印出数组类型和对象类型的具体内容
console.log('',JSON.stringify(data1))
说明:如果在创建一个对象接口时不知道将来里面参数的类型,则就可以使用泛型接口进行创建,然后当在定义的对象的时候在明确类型就可以了。
2.5 泛型类
泛型类跟泛型接口类似,当在创建一个类时不知道将来会传入什么类型的数据,则可以使用泛型类进行先占位。 举例:
class Person<T> {
id: T
constructor(id: T) {
this.id = id
}
sayHi() {
//这里需要使用this进行调用id,因为以及实例化了
return this.id
}
}
//跟接口不同的是在进行引用时要进行new实例化
let obj = new Person<string>('hello')
let obj1 = new Person<number>(100)
3.工具类型
ArkTS提供了4 中工具类型,来帮助我们简化编码
3.1. Partial
基于传入的Type类型构造一个【新类型】,将Type的所有属性设置为可选。
举例:
class Person {
name: string = ''
age: number = 0
friends: string[] = []
}
type ParPerson = Partial<Person>
// 因为都是可选的,可以设置为空对象
let p: ParPerson = {}
说明:原本类Person里面的参数为必选参数,在进行Partial后,ParPerson里面的参数就变为了可选参数。
3.2.Required
基于传入的Type类型构造一个【新类型】,将 Type 的所有属性设置为必填,它跟Partial作用相反。
class Person {
name?: string
age?: number
friends?: string[]
}
type RequiredPerson = Required<Person>
//原来都为可选参数,现在都是必须属性,必须设置值
let p: Required<Person> = {
name: 'jack',
age: 10,
friends: []
}
说明:作用跟Required相反,原本类Person里面的参数为可选参数,在进行Required后,p里面的参数就变成了必选参数了。
3.3.Readonly
基于 Type构造一个【新类型】,并将Type 的所有属性设置为readonly
class Person {
name: string = ''
age: number = 0
}
type ReadonlyIPerson = Readonly<Person>
let newp: ReadonlyIPerson = {
name: 'jack',
age: 10
}
newp.name = 'rose' // 报错 属性全部变成只读
说明:原本定义好了一个类,然后用type给原本的类取了一个别名,在给这个新对象进行赋值,但是在进行改name的时候会报错,因为这个时候ReadonlyIPerson里面的参数全部改为仅可读。
3.4.Record
构造一个对象类型,其属性键为Keys,属性值为Type。该实用程序可用于将一种类型的属性映射到另一种类型。
/*
* Record<Key,Value>
* 作用:可以用来修饰一个对象的key和value的类型,将来可以省略interface定义来直接使用变量存储一个对象
* 语法: Record<key,value> -> key表示对象的属性类型,value表示的对象的属性值的类型
*
* 注意:取值的时候,使用 对象名称['属性名'] -> 属性值
* */
//场景:
@State person = {'name':'张三'} // ❌这样定义报错 -> 因为没有使用interface指定对象类型
//所以可以这样不用定义interface,快速进行读取值
let obj:Record<string,string> = {'name' : '张三'}
let nameStr = obj['name']
console.log(nameStr)// 张三
let obj1:Record<string,number> = {'age':20}
let age = obj1['age']
console.log(age)// 20
说明:如果不想定义interface,就可以直接使用Record<Key,Value>来指定对象类型即可
4. 空安全
默认情况下,ArkTS中的所有类型都是不可为空的。如果要设置为空,需要进行特殊的处理,并且在获取 可能为空的值的时候也需要特殊处理。
4.1. 联合类型设置为空
举例:
//场景:
let x: number = null // 编译时错误
let y: string = null // 编译时错误
let z: number[] = null // 编译时错误
// 通过联合类型设置为空
let x: number | null = null
x = 1 // ok
x = null // ok
4.2. 非空断言运算符
举例:
let x: number | null
let y: number
y = x + 1; // 编译时错误:无法对可空值作加法
y = x! + 1; // 通过非空断言,告诉编译器 x不为 null
说明:在变量后面加!叫做非空断言。
4.3. 空值合并运算符
举例:
class Person {
name: string | null = null
getName(): string {
//三元表达式
// return this.name != null ? this.name : ''
// 上面的三元表达式等同于 如果 name不为空 就返回 name 反之返回 ''
return this.name ?? ''
}
}
说明:a ?? b等价于三元运算符a != null ? a : b
4.4. 可选链
举例:
interface iPerson{
name:string
dogAge?:number // 人有宠物狗也有可能没有宠物狗,所以狗的年龄dogAge为可选
}
//定义人对象
let person:iPerson = {name:'明明'}
// 打印人拥有的宠物狗年龄
console.log(person.dogAge.toString()) // 报错❌
// 解决方案1:使用if判断来判断如果为空就不执行,编译器认为这个代码安全,运行执行✔️
if(person.dogAge){
console.log(person.dogAge.toString())
}
// 解决方案2:使用?可选链来断定可能为空,编译器认为这个代码安全,运行执行✔️
console.log(person.dogAge?.toString())