JS 中最常用的数据形式莫过于对象了。TS 中也有对应的类型 object type.
function greet(person: {name: string; age: number}) {...}
或者用接口 interface 定义对象类型
interface Person {
name: string;
age: number;}
function greet(person: Person) {...}还可以使用 type 别名定义
type Person = {
name: string;
age: number;
}
以上我们编写了一个函数,这个函数接收一个对象参数,且属性 name 必须为 string, 属性 age 必须为 number。
🔯 属性修饰符:可选
可选参数,之前也介绍了:在属性名后紧跟一个 ? 即可。使用可选参数后,如果不传入一个可选参数,访问时则为 undefined 。
我们结合解构赋值,达到默认值的效果
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos);
console.log("y coordinate at", yPos);
}
🔯 属性修饰符:readonly 只读
1. 属性可以标记为 readonly 只读。这里的只读,只是 TS 检查中标记不可被重写的语法,并不会强制只读。
interface SomeType {
readonly prop: string;
}
function doSomething(obj: SomeType) {
// 读取属性
console.log(`prop has the value ${obj.prop}.`);
// 重写会有错误提醒,但不强制
obj.prop = "hello";
console.log(obj.prop)
}
doSomething({prop: 'Bowen'}) // 会打印出什么呢?
2. 对于引用类型的值TS并不会提示 ’只读‘,这就好比 const obj = {prop: value},你是可以修改prop的值,但是不可以给 obj 重新赋值
interface Home {
readonly resident: { name: string; age: number };
}
function visitForBirthday(home: Home) {
// 读取并修改对象内的属性
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}
function evict(home: Home) {
// 不能重新赋值
home.resident = {
name: "Victor the Evictor",
age: 42,
};
}
🔯 索引签名
当你不知道对象中某个属性名称,但是知道属性名称的类型时,可是使用索引签名描述属性名称的类型:
interface StringArray {
[index: number]: string;
}const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
其中 index 不是固定形式,你可以使用任何表意明确的字符表示。举一个常见的例子:
interface UnkonwKeyName {
[a: string]: object,
[b: symbol]: number
}
const sy = Symbol();
const obj: UnkonwKeyName = {
aString: {},
[sy]: 123
}
如果你定义的一个接口中既有未知属性名称又有明确的属性名称时,一定要做好兼容:下面是一个反例:
interface NumberDictionary {
[index: string]: number; length: number; // 😊
name: string; //🤢 这里必须是 number
}
兼容修正:使用类型的联合声明修正
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // 😊number
name: string; // 😊string
}
🔯 泛型对象类型
在没有使用泛型之前,我们定义一个可以接受多类型的对象会用到 any,例如:
interface Box {
contents: any;
}
但是 any 会引起类型不确定导致的问题,如类型缺少某种方法。
也可以使用 unknown 配合类型判断。
interface Box {
contents: unknown;
}let x: Box = {
contents: '你好‘
}// 类型判断:
if (typeof x.contents === 'string') {
console.log(x.contents.toLowerCase())
}
如果你不想做这样的判断,就需要按照不同类型创建不同的接口或者别名,
interface NumberBox {
contents: number
}
interface StringBox {
contents: string
}
...
这种方式是安全可靠的,但不利于拓展,而且还会影响到类型声明,导致不得不采用多个函数或者重载去实现多类型的操作:
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: StringBox, newContents: string): void;function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents
}
如此编写最大问题在于定义类型和调用时都重复编写了类型声明。
下面使用泛型对象解决这个问题:
interface Box<Type> {
contents: Type;
}
其中尖括号中的Type可以放入任何类型,可以看作是其他类型的占位值:
let box: Box<string> = { contents: '你好' };
let box1: Box<number> = { contents: 123 };
对象的泛型配合函数的泛型可以达到很好的效果,避免使用函数重载:
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
上面都是基于接口 interface 来编写泛型的,其实你也可以用 类型别名定义泛型:
type Box<Type> = {
contents: Type;
};
由于类型别名与接口不同,它可以描述的不仅仅是对象类型,我们也可以使用它们来编写其他类型的通用辅助类型
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
🔯 使用泛型声明数组
声明一个数组可以用类型 + [] 的形式,也可以使用泛型数组,如下:
普通类型方式:let fruits: string[] = ['apple', 'Orange', 'Banana'];
泛型数组方式:let fruits: Array<string> = ['apple', 'Orange', 'Banana'];
泛型数组也有一个只读的泛类型:ReadonlyArray<>
泛型数组只是声明数组类型的方式,不能用于创建一个数组实例:
new ReadonlyArray('red', 'green', 'blue'); // 🤢
正确的用法:
const roArray: ReadonlyArray<string> = ["red", "green", "blue"]; // 😊
🔯 元组类型
元组是一种已知了元素个数的数组,所以声明一个元组,需要明确指定包含元素的个数和类型:
type StringNumberPair = [string, number];
可以配合解构使用元组:
function doSomething(stringHash: [string, number]) {
const [inputString, hash] = stringHash; console.log(inputString);
console.log(hash);
}