在TS中,我们利用接口(interface)来定义对象的类型。
第一个接口
在前面的章节中,也有用到Interface的地方,在这里我将会再举一个简单的例子
let printObj = (labeledObj: { label: string, size ?: number }) => {
console.log(labeledObj);
};
let myObj = { size: 10, label: "Size 10 Object" };
printObj(myObj);
官网是这么描述的:
The type checker checks the call to
printLabel
. TheprintLabel
function has a single parameter that requires that the object passed in has a property calledlabel
of typestring
. Notice that our object actually has more properties than this, but the compiler only checks that at least the ones required are present and match the types required.
大体意思是:
类型检查器会查看
printLabel
的调用。printLabel
有一个参数,并要求这个对象参数有一个名为label
类型为string
的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
我们试着再用接口来将上面的例子重写一下:
interface ObjValue {
label: string;
size?: number;
}
function printObj(labeledObj: ObjValue) {
console.log(labeledObj);
}
let myObj = {size: 10, label: "Size 10 Object"};
printObj(myObj);
这里的
ObjValue
就是我们定义的一个接口,它规定了我们接收的对象的数据类型。只要我们传入的对象是满足它的规定的,就允许通过。 值得注意的是,类型检查器并不会去检查属性们的顺序,只需要相应的属性存在,且类型对即可。
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
下面是应用了“option bags”的例子:
interface ObjValue {
name?: string;
age?: number;
}
interface OutInfo {
name: string;
age: number;
}
function nameAge(date: ObjValue): OutInfo {
const infos: OutInfo = {name: "zxc", age: 24};
if (date.age) {
infos.age = date.age;
}
return infos;
}
let myInfo = nameAge({name: "zxc"});
只读属性
在TS中,某些对象的属性可能仅允许在创建时赋予其值,即该属性是一个只读属性,一般是用 readonly
关键字来标识它。
interface OnlyRead {
readonly age?: number;
readonly name: string;
}
let person: OnlyRead = {
age: 12,
name: "zxc",
};
console.log(person);
如果我们并未指定某属性只读,而在后续经过一系列的数据操作后,又想要将其保护,我们可以这么写:
interface OnlyRead {
age?: number;
name: string;
}
let person: OnlyRead = {
age: 12,
name: "zxc",
};
Reflect.defineProperty(person, "age", {
writable: false, // 不可改
configurable: false, // 不可配置
});
只读数组
如果我们想要让某一数组变成一个只读数组,我们可以这么写:
let arr: ReadonlyArray<number> = [];
如果我们想要将其赋值给一个其它的数组需要这么做:
let arr: number[] = [1, 2, 3];
let arr2: ReadonlyArray<number> = arr; // arr => arr2
let arr3 = arr2 as number[]; // arr2 => arr3
在这里,并不推荐在数组中这么使用。
一般而言,如果是一个数组从拿到开始就需要将其保护的话,我们更推荐使用proxy来处理这样的事情
// 初始接收数组我们拟定就是一个 list:[1,2,3]
// 以vue为示例,我们定义我们要用作使用的数据是 list:[] ,我们还需要定义一个空数据,比如说,我们定义它的名称是: proxy:''
// 我们在拿到数据后可以这样做:
proxy = new Proxy({}, {
get(target, key) {
/** 操作 */
},
set() {
return false;
},
});
额外属性
在某些时候,我们可能会额外地接收一些接口中没有定义过的属性,这些属性又是必要的,但是我们又不能准备地确定这些属性的名称。
官方为我们提供了一些解决方案,但我个人更倾向于推荐以下这种解决方案:
interface ObjValue {
age: number;
name?: string;
[propName: string]: any;
}
函数类型
接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
写一个简单的小例子:
type FuncIter = (name: string, age: number) => boolean;
let person: FuncIter = (name: string, age: number) => {
return true;
};
我们这里用type来表示我们需要定义的函数类型,表示接收的参数第一个需要是string,第二个需要是number,以及函数的返回值应该是一个boolean
如果这边不采用接口,而我们又想要写的更严谨的话,我们需要这样写:
let person: (name: string, age: number) => boolean = (name: string, age: number): boolean => {
return false;
};
可索引类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]
或ageMap["daniel"]
。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
Typescript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。
字符串索引签名能够很好的描述dictionary(字典)
模式,并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了obj.property
和obj["property"]
两种形式都可以。 下面的例子里,name
的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
在这里,我们应该将name这行改为: name: number
另外,我们可以将索引签名设为只读的,不过在平常的业务操作,我们会很少去这样使用:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
在一般遇到有这种需求的场景的时候,更远推荐使用proxy来进行完成,当然,这只是个人推荐。