这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
接口 Interfaces
在 typescript
中,有一项比较重要的核心知识,那就是接口Interfaces
接口是什么
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对对象的形状(Shape)进行描述。在TS中,接口的作用是为这些类型命名和为你的代码或第三方代码定义契约。
并且接口只对typescript
编译时有影响,对运行时影响为0
简单的接口
下面的例子中,定义了一个名为Cat
的接口,定义了两个个变量 tom
和 jerry
, 类型是Cat
。
这样给tom和Jerry
赋值时就约束了tom
和 jerry
的形状必须和接口Cat
一致,也就是定义的变量不能比接口多出属性或者少出属性。
但是可以运行改变赋值时属性的顺序,因为类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以
接口一般首字母大写
interface Cat {
name: string;
age: number;
}
let tom: Cat = {
name: 'Tom',
age: 10,
}
let jerry: Cat = {
age: 10,
name: 'jerry',
}
复制代码
在给一个方法的参数定义接口时,方法调用时有个地方需要注意:
interface Pear {
name: string;
size: number
};
function fun2( arg1: Pear ): void {
console.log(arg1);
}
复制代码
此方法调用传参时,如果直接传入对象,对象需要和接口一一对应,如果有多余属性会出现报错,当然有两种方法可以避免:
- 一种就是使用
类型断言
将传入对象断言为所需要的类型。 - 一种是先把传参的对象赋值给一个变量再传入方法内,只要需要的属性存在,其他多余的属性也不会受影响而报错。
PS:这个把传入对象赋值给变量后多余属性传入没有报错我也很奇怪,没找到原因,如果有好心人可以在评论区讲解一下,多谢多谢
fun2({ name: 'Pear', size: 10})
fun2({ name: 'Pear', size: 10, age: 5}) // error: Object literal may only specify known properties, and 'age' does not exist in type 'Pear'.
fun2({ name: 'Pear', size: 10, age: 5} as Pear) // 使用类型断言
var argobj = { name: 'Pear', size: 10, age: 5};
fun2(argobj)
复制代码
接口的属性
在接口中,也有许多可以调控的属性
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
可选属性在应用option bags选择包模式
时很常用,即给函数传入的参数对象中只有部分属性赋值了。
例子:
interface Options {
check1: boolean,
check2?: boolean, // check2和check3都是可选属性
check3?: boolean
}
// 定义赋值时可不附值可选属性
let c1: Options = {
check1: false
}
let c2: Options = {
check1: true,
check3: false
}
复制代码
可以通过只读属性配置一个ajax参数接口,这样options和dataType就是可传可不传:
interface Configajax {
url: string,
options?: (string|number|null|undefined|object)[],
method: string,
dataType?: any
}
复制代码
ajax的方法:
// 利用promise封装一个ajax,可以使用then返回数据
// 如果使用promise等es6,在编译时可以指定运行环境 tsc --target es6 tsc03.ts
function getAjaxNew(config: Configajax) {
let promise = new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
let readystatechange = ()=>{
if(req.readyState === 4) { //判断响应状态是否成功
let responseHeaders = req.getAllResponseHeaders(); //获取响应头信息
let data = req.responseText; //获取响应数据
// 数据处理
resolve(req.response);
}
}
req.onreadystatechange = readystatechange;
if (config.dataType)
req.responseType = config.dataType;
if (config.method == 'GET') {
if (config.options)
req.open(config.method, config.url + '?' + config.options.join('&'), true); // true代表异步
else
req.open(config.method, config.url , true); // true代表异步
req.setRequestHeader('X-Requested-with','XMLHttpRequest');//设置请求头信息
req.send();//发送请求
} else {
req.open(config.method, config.url, true); // true代表异步
req.setRequestHeader('Content-Type','application/json;charset=UTF-8');//设置请求头信息,请求头需要在open之后
if (config.options) {
let data = JSON.stringify(config.options);
req.send(data);//发送请求
} else{
req.send();//发送请求
}
}
})
return promise;
}
复制代码
传参调用方法:
let doptions = [{ user:'kong', newUrl:'www.baidu.com' },{ user:'z', newUrl:'www.zhihu.com' },{ user:'k', newUrl:'www.kantu.com' }];
ajax({
url:"http://localhost:3001/typescript",
method:'GET',
options: doptions
}).then((response)=>{
console.log(response)
});
复制代码
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值, 可以在属性名前用readonly来指定只读属性。
此属性与es6中的const常量块级作用域有些类似,不允许后续继续改变
interface Changes {
readonly id: number,
type: number|string
}
let changenum: Changes = {
id: 10,
type: 765
}
changenum.id = 5; // error: Cannot assign to 'id' because it is a read-only property.
changenum.type = '好'
复制代码
注意:只读的约束存在于第一次给
对象
赋值的时候,而不是第一次给只读属性
赋值
最简单判断该用readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
interface Changes {
readonly id?: number,
type: number|string
}
let changenum: Changes = {
type: 765
}
changenum.id = 5; // 此时虽然是对id属性第一次赋值,但是changenum对象已经赋值过了,所以还是会报错
复制代码
在TypeScript
中有只读数组readonlyArray
的定义,readonlyArray
的用法与Array
类似,但是赋值后是不可变的
let changearr: ReadonlyArray< number > = [1, 2, 3, 4, 5]
changearr[4] = 0; // error: Index signature in type 'readonly number[]' only permits reading.
let arr5:number[] = []
arr5.push(changearr) // error: Argument of type 'readonly number[]' is not assignable to parameter of type 'number'.
复制代码
除了readonlyArray
外,在TypeScript
中还有只读MapReadonlyMap
的存在
let oldMap = new Map();
oldMap.set("1", { name: '123' })
let changeMap: ReadonlyMap<string, object> = new Map(oldMap); // 深拷贝
changeMap.set("2", { name: '2222' }) // error: Property 'set' does not exist on type 'ReadonlyMap<string, object>'.
复制代码
任意属性
有时候我们希望一个接口允许有任意的属性
任意属性有两种定义方式:
- 属性签名是string字符串的,
[propName: string]: any;
- 属性签名是number数值类型的,
[propIndex: number]: any;
注意:在赋值时,这两种方式还是略有不同的
interface anyinter {
[propName: string]: any;
}
// 这里的属性名使用了number类型,但是也没有出错,这是因为数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 相当于moreinter[0] 等价于 moreinter['0']
let moreinter: anyinter = {
1: false,
"two": 123,
"three": '888',
0: 1
}
复制代码
interface anyinter2 {
[propIndex: number]: any;
}
let moreinter2: anyinter = [
false,
123,
'888',
]
复制代码
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
如果同时使用两种定义方式:
interface anyinter3 {
[propName: string]: boolean;
[propIndex: number]: false; // 必须是boolean的子集
// [propIndex: number]: number; // error: Numeric index type 'number' is not assignable to string index type 'boolean'
}
let moreinter3: anyinter3 = {
'1': false,
'2': true // error: Type 'true' is not assignable to type 'false' 不能将类型“true”分配给类型“false”
}
复制代码
终于又学完一些东西啦!!!