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);
}