这是我参与更文挑战的第八天,活动详情查看:更文挑战
接口:用来约束 对象,函数,类的结构和类型。是一种代码协作的契约
例子 1:假设从后端获取数据,然后渲染到界面上
场景一:后端有时候返回多余的字段
interface List {
id: number;
name: string;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// 传入多余的字段如: age 却不会报错。 原理: "鸭式辩型法"
{ id: 3, name: "C", age: 20 },
],
};
render(result);
复制代码
后端有时候返回多余的字段,但是 TS 中并不会报错,是因为 TS 中采用了 “鸭式辩型法“
解释:
打比方:如果一只鸟游起来像鸭子,走起来像鸭子,叫起来像鸭子 那么就可以定义为鸭子
TS 中只要传入的参数满足必要的条件,那么就是被允许的,即使传入多余的字段也可以通过类型检查
场景二: 函数直接传入对象字面量
interface List {
id: number;
name: string;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
// 注意:这里是直接给函数赋值
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// age会报错
{ id: 3, name: "C", age: 20 },
],
});
复制代码
解决方式一 变量作为函数参数
跟场景一 一样。 将对象字面量赋值给一个变量, 然后,在函数中, 以这个变量作为参数
解决方式二 类型断言
明确的告诉编译器 传入的参数是什么类型
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// age会出现报错
{ id: 3, name: "C", age: 20 },
],
} as Result);
复制代码
解决方式三 字符串索引签名
interface List {
id: number;
name: String;
// 用任意的字符串去索引List,可以得到任意的结果
[x: string]: any;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
});
复制代码
场景三: 判断数据中是否存在某一个字段
interface List {
id: number;
name: String;
// 加上一个问号, 表示 age字段可以没有,也可以有
age?: number;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
// 如果用到了age字段就必须进行判断
if (value.age) {
console.log(value.age);
}
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
};
render(result);
复制代码
场景四:字段只读
readonly: 只能读取,不能修改
interface List {
// 只能读取,不能修改 否则会报错
readonly id: number;
name: String;
age?: number;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
// 这里会报错
value.id = 1;
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
};
render(result);
复制代码
场景五: 可索引类型接口
当不确定数据字段的个数的时候,就可以使用可索引的接口,可以索引接口可以是字符串类型,也可以是数字类型
用数字表示的 可索引类型接口
interface StringArray {
/*
用任意数字去索引 StringArray 都会得到一个 string类型的结果
*/
[index: number]: string;
}
// 使用场景一: 字符串数组
let chars: StringArray = ["a", "b"];
// 使用场景二: 以数字为键 ,字符串为值得对象
let a: StringArray = { 1: "a", 2: "数字" };
复制代码
上面例子里,我们定义了 StringArray 接口,它具有索引签名。 这个索引签名表示了当用 number 去索引 StringArray 时会得到 string 类型的返回值。
用字符串表示的 可索引类型接口
interface Names {
/*
用任意字符串去索引 Names 都会得到一个 string类型的结果
相当于一个字符串对象
*/
[x: string]: string;
// 后面不可以再声明number类型的成员了,否则会报错
// y:number
}
let myObject: Names = { name: "小明", className: "一年级一班" };
复制代码
二者是可以混合使用的
// 既可以用数字去索引 Ohters,也可以用字符串索引 Ohters
interface Ohters {
[x: string]: string;
[y: number]: string;
}
let myObject: Ohters = {
1: "名称",
a: "a",
name: "a",
2: "数字",
};
复制代码
数字索引签名的返回值一定要是 字符串索引签名返回值的子类型
因为 JavaScript 会进行类型转换,将 number 转换为 string。这样就能保证类型的兼容性
TypeScript 支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。 也就是说用 100(一个 number)去索引等同于使用”100″(一个 string)去索引,因此两者需要保持一致。
// 这样就与string不兼容了
interface Ohters1 {
[x: string]: string;
// 这里会报错
[y: number]: number;
}
// 解决方式一
interface Ohters2 {
[x: string]: any;
[y: number]: number;
}
// 解决方式二
interface Ohters3 {
[x: string]: number;
[y: number]: number;
}
复制代码
一. 用变量定义函数类型
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number,
): number {
return x + y;
};
// 12
myAdd0(2, 10);
复制代码
函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的
二. 用接口定义函数类型
interface Add {
(x: number, y: number): number;
}
let myAdd: Add = (a, b) => a + b;
// 80
myAdd(30, 50);
复制代码
TypeScript 的类型系统会推断出参数类型,因为函数直接赋值给了
Add
类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是number
)。 如果让这个函数返回其他类型,类型检查器会警告我们函数的返回值类型与Add
接口中的定义不匹配。
三. 用类型别名定义函数类型
type Add1 = (x: number, y: number) => number;
let myAdd1: Add1 = (a, b) => a + b;
// 3
myAdd1(1, 2);
复制代码
四. 混合类型的接口
这种接口,既可以定义函数,也可以像对象一样,拥有属性和方法
interface Lib {
(): void;
version: string;
doSomeThing(): void;
}
function getLib() {
let lib: Lib = (() => {}) as Lib;
lib.version = "1.0";
lib.doSomeThing = () => {};
return lib;
}
let lib1 = getLib();
lib1();
lib1.doSomeThing();
let lib2 = getLib();
lib2();
lib2.doSomeThing();
复制代码
总结
定义函数的几种方式
方式一:function
明确的指出函数参数的类型,返回值的类型则可以通过 TS 的类型推断省略
function test(x: number, y: number) {
return x + y;
}
test(12, 15);
复制代码
方式二:通过变量定义函数类型
let myAdd0: (x: number, y: number) => number = function (
x: number,
y: number,
): number {
return x + y;
};
myAdd0(2, 10);
复制代码
方式三:通过类型别名定义函数类型
type Add1 = (x: number, y: number) => number;
let myAdd1: Add1 = (a, b) => a + b;
myAdd1(1, 2);
复制代码
方式四:通过接口定义函数类型
interface Add {
(x: number, y: number): number;
}
let myAdd: Add = (a, b) => a + b;
myAdd(30, 50);
复制代码
后三种只是定义了函数的类型,并没有真正的实现,因此在调用时,要书写函数体
在 TS 中形参和实参必须一一对应
函数中的可选参数
function myAdd(x: number, z: number, y?: number) {
if (y) {
return x + y + z;
} else return x + z;
}
myAdd(1, 2);
复制代码
可选参数必须位于必选参数之后
函数中的参数默认值
function myAdd1(x: number, z = 10, y?: number) {
if (y) {
return x + y + z;
} else return x + z;
}
// 30
myAdd1(20);
function myAdd2(x: number, z = 10, a: number) {
return x + z + a;
}
复制代码
function myAdd3(x: number, y = 0, z: number, q = 1) {
return x + y + z + q;
}
// 报错:An argument for 'y' was not provided.
myAdd3(1);
复制代码
在必选参数前,默认参数是不可省略的,必须明确的传入 undefined 来获取默认值
function myAdd3(x: number, y = 0, z: number, q = 1) {
return x + y + z + q;
}
// 5
console.log(myAdd3(1, undefined, 3));
复制代码
函数中的剩余参数的类型
function myAdd3(x: number, ...rest: number[]) {
return x + rest.reduce((pre, cur) => pre + cur);
}
// 16
myAdd3(10, 1, 2, 3);
复制代码
_函数重载, _
作用:不需要为了相似功能的函数取不同的函数名称。
JavaScript 本身是个动态语言。 JavaScript 里函数根据传入不同的参数而返回不同类型的数据是很常见的。
同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用
为了让编译器能够选择正确的检查类型,它与 JavaScript 里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
function add6(...rest: number[]): number;
function add6(...rest: string[]): string;
// 在一个类型更宽泛的函数中实现重载
function add6(...rest: any): any {
let first = rest[0];
if (typeof first === "string") {
return rest.join("|");
}
if (typeof first === "number") {
return rest.reduce((pre: number, cur: number) => pre + cur);
}
}
// 6
add6(1, 2, 3);
// a|b|c
console.log(add6("a", "b", "c"));
复制代码