接口
接口的定义
- TypeScript的核心原则之一是对值所具有的的结构进行类型检查,我们可以通过基本类型的标注,我们也还可以通过Interface(接口)来进行标注。
- 接口是一种对复杂的对象类型进行标注的方式。
- 一个简单的使用接口的方式
interface userValue{
userName:string,
passWord:string
}
function Login(User:userValue){
console.log(User.userName,User.passWord);
}
let person1 = {userName:'jack',passWord:'jack123'};
let person2 = {userName:123,passWord:'ben123'};
Login(person1);
Login(person2);//如果传入的值不是string的话,编辑器会提示属性“userName”的类型不兼容,不能将类型“number”分配给类型“string”
复制代码
- 值得注意的是:接口只是一种类型,它不能作为值来使用
let person3 = userValue;//“userValue”仅表示类型,但在此处却作为值使用
复制代码
可选属性
- 接口中并不是所有属性都是必须的,有些属性可以再某些情况不用改变,也可以再某些情况下去改变,在接口中我们通过**“?”**进行标注。
interface Person{
name?:string,
age?:number,
hoppy?:string
}
function person(msg:Person):{name:string,age:number,hoppy:string}{
let person1 = {name:"jack",age:14,hoppy:'打篮球'};
person1.hobby = msg.hobby?msg.hobby:person1.hobby;
person1.name = msg.name?msg.name:person1.name;
person1.age = msg.age?msg.age:person1.age;
return person1
}
//其中name,age,hoppy都是可选的
let person2 = {name:'ben',hoppy:"踢足球"};
console.log(person(person2));//{name: "ben", age: 14, hoppy: "踢足球"}
复制代码
只读属性
- 接口中可能需要某些属性只可以在首次创建对象的时候去修改,在接口中我们可以用“readonly”进行标注。
interface Person{
readonly name:string,
readonly age:number
}
let person1:Person = {name:'jack',age:14};
person1.name = 'ben';//无法分配到 "name" ,因为它是只读属性。
复制代码
函数类型
- 在某些情况下我们可能需要去用一个接口去描述函数
interface Person {
(name: string, age: number): string//返回值是一个字符串
}
let person1: Person = function (x: string, y: number) {
return x + y
}
let person2: Person = function (x: number, y: number) {
return x + y//如果返回值是一个number的话,编辑器会提示不能将类型“string”分配给类型“number”
}
复制代码
可索引类型
- 我们还可以通过接口去描述一个可以“索引”的类型,例如数组中的arr[0]
interface Color{
[index:number]:string
}
let colorArray:Color;
colorArray = ['red','blue','black'];
let color1:string = colorArray[2];
console.log(color1);//black
复制代码
- 并集
interface Color{
[index:string]:number|Array<string>,
length:number,
color:Array<string>
};
let colorArray:Color;
colorArray = {
color:['red','blue','black'],
length:3
}
let color1:Array<string>= colorArray.color;
console.log(color1);//['red','blue','black']
复制代码
- 我们还可以利用readonly(只读)属性防止修改分配给它们的索引
interface Color{
readonly [index:number]:string
}
let colorArray:Color;
colorArray = ['red','blue'];
colorArray[1] = 'black';//编辑器会提示类型“Color”中的索引签名仅允许读取
复制代码
类
- 我们可以通过一个接口去描述一个类。
interface ColorInterFace{
Colors:Array<string>
}
class Color implements ColorInterFace{
Colors:Array<string> = ['red','blue'];
constructor(){
}
}
复制代码
接口扩展
- 接口像类一样是可以互相扩展的,我们可以通过接口的扩展将一个接口中的成员复制到另外一个接口的成员中去。
interface Name{
name:string
}
interface Person extends Name{
age:number
}
let person = {} as Person
person.age = 14;
person.name = 'jack'
console.log(person);
复制代码
- 一个接口扩展还可以扩展多个接口,从而创建所有接口的组合
interface Name{
name:string
};
interface Age{
age:number
};
interface Person extends Name,Age{
hobby:Array<string>
};
let person = {} as Person;
person.name = 'jack';
person.age = 15;
person.hobby = ['打篮球','踢足球'];
console.log(person);
复制代码
混合类型
- 在接口中我们还可以去定义一个既具有功能又有附加属性的对象
interface Person {
(index: number): string,
name: string,
hobby(vlaue): void
}
function personalHobbies(): Person {
let person = function (index: number) { } as Person;
person.name = 'jack';
person.hobby = function (vlaue) {
console.log("我喜欢" + vlaue)
}
return person
}
let person1 = personalHobbies();
person1(10);
person1.name = 'ben';
person1.hobby('踢足球');
复制代码
接口扩展类
- 接口类型扩展类类型时,它可以继承该类的成员,但不继承其实现。
class Person{
private name:string
}
interface PersonalHobbies extends Person{
hobby():void
}
class Play extends Person implements PersonalHobbies{
hobby(){}
}
class Sing extends Person{
hobby(){}
}
class Dance implements PersonalHobbies{
private name:string;
hobby(){}
}//编辑器会提示该类型具有私有属性“name”的单独声明。
复制代码
泛型
什么是泛型
- 泛型是能够创建一种可以在多种类型而不是单个类型上工作的组件,方便用户在使用这些组件时也可以使用自己的类型。
为什么要使用泛型
- 大多数的时候我们在标注具体类型并不能直接确定该标注需要什么类型,比如一个函数的参数类型
function getValue(obj,k){
return obj[k]
}
复制代码
从上面的函数我们可以知道的是该函数需要实现的是获取一个对象指定的K所对应的值,但是在实际使用的时候我们不知道obj的类型是不确定的,自然我们也可以知道K值的取值范围也是不确定的,需要我们在具体调用的时候我们才能确定,在这个情况下这种定义过程不确定类型的需求我们可以通过泛型来解决。
function getVal<T>(obj: T, k: keyof T) {
return obj[k]
}
复制代码
泛型函数
- 用typescript编写代码的过程中我们经常性的会遇到如下所示的编写方式
function Person<T>(name: T, age: number): T {
console.log(age);
let person = name + '已经' + age + '岁'
console.log(person);
return name
}
复制代码
上面函数中**中的T**我们称之为类型变量,相当于一个自定义类型变量,我们在调用这个函数中可以自己选择T的标注类型,例如:
Person<string>('jack',15)
Person<number>(7,15)
Person('jack',15)
复制代码
第三种的语法不同于上面两种需要去选择一个标注类型,因为编辑器能够知道参数的类型是什么,不需要开发人员去显式标注该类型。
泛型类
- 用泛型也可以去定义一个类
class Person<T>{
name: T;
age: number;
person() {
console.log(this.name + "已经" + this.age + '岁')
}
}
let person1 = new Person<string>();
person1.name = 'jack';
person1.age = 14;
person1.person()
复制代码
泛型接口
- 我们也可以用泛型去定义一个接口,比如说在开发项目中我们需要定义一个单一接口去获取数据,但是又可能数据里面的具体格式可能不一致,在这里我们就可以使用泛型去定义这个接口
//比如说依据后端返回的数据格式的定义如下所示
interface ReturnData {
code: number;
message?: string;
data: any
}
//根据该接口我们可以封装方法去获取数据
function getData(url:string){
return fetch(url).then(res =>{
return res.json();
}).then((data:ReturnData)=>{
return data
})
}
复制代码
从上面的代码我们可以得知的是data项的具体格式不确定,不同的接口返回的数据是不一样的,那么我们可以用泛型去定义ReturnData,这样我们就可以根据具体当前请求接口返回具体的data格式。
// 泛型接口
interface ReturnData<T>{
code: number;
message?: string;
data: T
}
function getData<U>(url:string){
return fetch(url).then(res =>{
return res.json();
}).then((data:ReturnData<U>)=>{
return data
})
}
// 用户的接口
interface ReturnUserData{
id:number,
username:string
}
// 用户相关的文章数据接口
interface ReturnUserAboutData{
id:number,
title:string,
author:ReturnUserData
}
// 调用
(async function(){
let user = await getData<ReturnUserData>('/user');
console.log(user.data.username);//只能获取用户的数据
let article = await getData<ReturnUserAboutData>("/article");
console.log(article.data.title);
console.log(article.data.author.username);//不仅可以获取用户的数据,并且也可以获取该用户相关的文章数据
})
复制代码
总结
接口和泛型这两块在实际的开发过程中是非常有必要的;接口是一种可以对复杂的对象类型进行标注的方式,泛型则是能够创建一种可以在多种类型而不是单个类型上工作的组件,方便用户在使用这些组件时也可以使用自己的类型。