TypeScript高级类型-this类型和动态类型
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);
在上面的代码addFoo
,addBar
以及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
对象,该对象拥有name
和age
属性。然后,当我们尝试再次给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接口中的必须为字符串或数字类型的参数。
我们可以用它来表示动态对象的属性。要转换类型的成员以向其添加一些属性,我们可以将现有类型的成员映射到新的类型,以将属性添加到具有映射类型的接口中。
相关文章
- Type Inference in TypeScriptSince TypeScript entities have data types associated with them, the TypeScript compiler can guess the…
- TypeScript Advanced Types — Type GuardsTypeScript has many advanced type capabilities which make writing dynamically typed code easy. It also…
- TypeScript Data Types – Numbers, Strings, and ObjectsJavaScript, like any other programming language, has its own data structures and types. JavaScript has…