TypeScript具有许多高级类型功能,这使编写动态类型的代码变得容易。由于它使我们能够在使用TypeScript的类型检查功能的同时保留JavaScript的动态功能,因此它也便于采用现有的JavaScript代码。TypeScript中有许多高级类型,例如交集类型,联合类型,类型防护,可空类型和类型别名,等等。在本文中,我们将研究this类型并创建具有索引签名和映射类型的动态类型。

this 类型

在TypeScript中,我们可以用this来表示类型。它表示包含的类或接口的子类型。如果我们让类中的每个方法都将返回类的实例,那么我们可以使用它轻松地创建流畅的接口。例如,我们可以使用它来定义具有可链式调用的方法的类,如以下代码所示:

class StringAdder {  
  value: string = '';  
  getValue(): string {  
    return this.value;  
  } 

  addFoo(): this {  
    this.value += 'foo';  
    return this;  
  } 

  addBar(): this {  
    this.value += 'bar';  
    return this;  
  } 

  addGreeting(name: string): this {  
    this.value += `Hi ${name}`;  
    return this;  
  }  
}
const stringAdder: StringAdder = new StringAdder();  
const str = stringAdder  
  .addFoo()  
  .addBar()  
  .addGreeting('Jane')  
  .getValue();  
console.log(str);

在上面的代码addFooaddBar以及addGreeting方法都返回StringAdder类的实例,这允许我们一次调用一个实例的多个方法。通过this每种方法中的返回类型,可以实现链接。

索引类型

使用索引类型可以让TypeScript编译器使用动态属性的名称检查代码。我们可以使用extends keyof关键字组合来表示一种类型具有另一种类型的属性名称。例如,我们可以这样写:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

然后我们可以像以下代码那样使用choose函数:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
choose(obj, ['a', 'b'])

最后的结果是:

[1, 2]

如果choose 函数的第二个参数数组中的某个元素不是第一个参数(obj 对象)的属性名,那么Typescript 编译器会帮助我们检查出响应的错误。比如当我们尝试编译以下代码:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
const arr = choose(obj, ['d']);

会得到如下的错误提示:

Type 'string' is not assignable to type '"a" | "b" | "c"'.(2322)

在上面的示例中,keyof U与字符串字面值类型“a” | “b” | “c”相同,这是因为我们传入了通用类型标记U,U的实际类型是依据第一个参数对象推断出来的。K extends keyof U 这一部分意味着无论第一个参数是什么类型,通用类型U都表示第一个参数的类型,第二个参数必须是包含第一个参数的部分或全部键名的数组。然后,我们将返回类型定义为一个数组,该数组的值是通过遍历传入的第一个参数的对象而获得的,因此我们拥有该U[K][]类型。U[K][]也称为索引访问运算符。

索引类型和索引签名

索引签名是TypeScript接口中必须为字符串或数字类型的参数。我们可以用它来表示动态对象的属性。例如,我们可以像在下面的代码中那样使用它:

interface DynamicObject<T> {  
  [key: string]: T;  
}  
let obj: DynamicObject<number> = {  
  foo: 1,  
  bar: 2  
};  
let key: keyof DynamicObject<number> = 'foo';  
let value: DynamicObject<number>['foo'] = obj[key];

在上面的代码中,我们定义了一个DynamicObject<T>接口,该接口为其成员采用了动态类型。我们有一个索引签名key,它是一个字符串,也可以是数字。动态成员的类型由T表示,T是通用类型标记。这意味着我们可以将任何类型的数据传递给它。

然后,我们定义了类型为DyanmicObject<number>的对象obj。这利用了我们之前创建的接口DynamicObject。然后,我们定义了key变量,其类型为keyof DynamicObject<number>,这意味着它必须是字符串或数字。这意味着key变量的取值必须是一个属性名称。然后,我们定义了value变量,该变量的取值必须是DynamicObject类型的对象。

这意味着我们不能将除字符串或数字以外的任何内容赋值给key。因此,如果写类似于如下的代码:

let key: keyof DynamicObject<number> = false;

那么,我们将从TypeScript编译器中收到以下错误消息:

Type 'false' is not assignable to type 'string | number'.(2322)

映射类型

我们可以通过将现有类型的成员映射到新类型中来创建新类型。这称为映射类型。

我们可以像下面的代码一样创建映射类型:

interface Person {  
  name: string;  
  age: number;  
}

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type PartialType<T> = {  
  [P in keyof T]?: T[P];  
}

type ReadOnlyPerson = ReadOnly<Person>;  
type PartialPerson = PartialType<Person>;let readOnlyPerson: ReadOnlyPerson = {  
  name: 'Jane',  
  age: 20  
}
readOnlyPerson.name = 'Joe';  
readOnlyPerson.age = 20;

在上面的代码中,我们创建了ReadOnly类型别名,以通过将类型的每个成员设置为来将现有类型的成员映射为新类型readonly。这本身并不是一个新类型,因为我们需要将一个类型传递给泛型类型标记T。然后,通过将Person类型分别传递给ReadOnly别名和Partial别名,为定义的类型创建别名。

接下来,我们定义了一个ReadOnlyPerson对象,该对象拥有nameage属性。然后,当我们尝试再次给name 或者age属性赋值时,就会出现以下错误:

Cannot assign to 'name' because it is a read-only property.(2540)Cannot assign to 'age' because it is a read-only property.(2540)

这意味着编译器将强制程序员只读ReadOnly类型别名中的属性。同样,我们可以对PartialType类型别名进行相同的操作。我们通过将Person类型的成员映射到PartialPerson 类型来定义PartialPerson类型。然后,我们可以像下面的代码中那样声明一个PartialPerson对象:

let partialPerson: PartialPerson = {};

如我们所见,我们可以根据需要省略对象partialPerson的属性。

我们可以用&运算符将新成员和类型别名映射到一个新的类型中。由于我们使用type关键字定义了映射类型,因此它们是类型。映射类型实际上是类型别名。这意味着即使它看起来像一个接口(interface),我们也不能将直接向其内部添加成员。

要添加成员,我们可以编写如下内容:

interface Person {  
  name: string;  
  age: number;  
}

type ReadOnly<T> = {  
  readonly [P in keyof T]: T[P];  
}

type ReadOnlyEmployee = ReadOnly<Person> & {  
  employeeCode: string;  
};

let readOnlyPerson: ReadOnlyEmployee = {  
  name: 'Jane',  
  age: 20,  
  employeeCode: '123'  
}

Readonly<T>并且Partial<T>包含在TypeScript标准库中。Readonly将我们传递给泛型类型占位符的类型的成员映射为只读成员。该Partial关键字让我们映射类型的成员到可空成员。

结论

在TypeScript中,我们可以将this用作类型。它可以表示this实例自身的类或接口及其子类型。通过让类中的每个方法都将返回类的实例,我们可以使用它轻松地创建流畅的接口。索引签名是在TypeScript接口中的必须为字符串或数字类型的参数。

我们可以用它来表示动态对象的属性。要转换类型的成员以向其添加一些属性,我们可以将现有类型的成员映射到新的类型,以将属性添加到具有映射类型的接口中。

相关文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据