docs: finish chapter declare
This commit is contained in:
287
docs/declare.md
287
docs/declare.md
@@ -4,7 +4,7 @@
|
||||
|
||||
declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。
|
||||
|
||||
它的直接作用,就是让当前文件可以使用其他文件声明的类型。因此,编译单个脚本时,不会因为使用了外部类型而报错。
|
||||
它的主要作用,就是让当前文件可以使用其他文件声明的类型。举例来说,自己的脚本使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用`declare`关键字,告诉编译器外部函数的类型。这样的话,编译单个脚本就不会因为使用了外部类型而报错。
|
||||
|
||||
declare 关键字可以描述以下类型。
|
||||
|
||||
@@ -14,9 +14,9 @@ declare 关键字可以描述以下类型。
|
||||
- enum
|
||||
- 函数(function)
|
||||
- 模块(module)
|
||||
- 命名空间(namespace)
|
||||
- 命名空间(namespace)
|
||||
|
||||
declare 关键字的重要特点是,它只是通知编译器某个类型是存在的,不用给出具体实现。
|
||||
declare 关键字的重要特点是,它只是通知编译器某个类型是存在的,不用给出具体实现。比如,只描述函数的类型,不给出函数的实现,如果不使用`declare`,这是做不到的。
|
||||
|
||||
declare 只能用来描述已经存在的变量和数据结构,不能用来声明新的变量和数据结构。另外,所有 declare 语句都不会出现在编译后的文件里面。
|
||||
|
||||
@@ -51,15 +51,15 @@ x = 1;
|
||||
下面的例子是脚本使用浏览器全局对象`document`。
|
||||
|
||||
```typescript
|
||||
declare var document;
|
||||
document.title = "Hello";
|
||||
declare var document;
|
||||
document.title = 'Hello';
|
||||
```
|
||||
|
||||
上面示例中,declare 告诉编译器,变量`document`的类型是外部定义的(具体定义在 TypeScript 内置文件`lib.d.ts`)。
|
||||
|
||||
如果 TypeScript 没有找到`document`的外部定义,这里就会假定它的类型是`any`。
|
||||
|
||||
注意,declare 关键字只用来给出类型描述,是纯的类型代码,不允许设置变量的初始值,即不涉及值。
|
||||
注意,declare 关键字只用来给出类型描述,是纯的类型代码,不允许设置变量的初始值,即不能涉及值。
|
||||
|
||||
```typescript
|
||||
// 报错
|
||||
@@ -80,11 +80,11 @@ declare function sayHello(
|
||||
):void;
|
||||
|
||||
sayHello('张三');
|
||||
```
|
||||
```
|
||||
|
||||
上面示例中,declare 命令给出了`sayHello()`的类型描述,因此可以直接使用它。
|
||||
|
||||
注意,这种单独的函数类型声明语句,只能用于`declare`命令后面。一方面,TypeScript 不支持单独的函数类型声明语句;另一方面, declare 关键字后面也不能带有函数的具体实现。
|
||||
注意,这种单独的函数类型声明语句,只能用于`declare`命令后面。一方面,TypeScript 不支持单独的函数类型声明语句;另一方面,declare 关键字后面也不能带有函数的具体实现。
|
||||
|
||||
```typescript
|
||||
// 报错
|
||||
@@ -203,7 +203,7 @@ declare 关键字的另一个用途,是为外部模块添加属性和方法时
|
||||
|
||||
```typescript
|
||||
import { Foo as Bar } from 'moduleA';
|
||||
|
||||
|
||||
declare module 'moduleA' {
|
||||
interface Bar extends Foo {
|
||||
custom: {
|
||||
@@ -213,141 +213,9 @@ declare module 'moduleA' {
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,从模块`moduleA`导入了一个`Foo`,将其重命名为`Bar`,并用 declare 关键字增加一个属性`custom`。
|
||||
上面示例中,从模块`moduleA`导入了`Foo`接口,将其重命名为`Bar`,并用 declare 关键字为`Bar`增加一个属性`custom`。
|
||||
|
||||
declare module 后面的模块名可以使用通配符。
|
||||
|
||||
```typescript
|
||||
declare module 'my-plugin-*' {
|
||||
interface PluginOptions {
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
function initialize(options: PluginOptions): void;
|
||||
export = initialize;
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,模块名`my-plugin-*`表示适配所有以`my-plugin-`开头的模块名(比如`my-plugin-logger`)。
|
||||
|
||||
## declare global
|
||||
|
||||
如果要为 JavaScript 引擎的原生对象添加属性和方法,可以使用`declare global {}`语法。
|
||||
|
||||
```typescript
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface String {
|
||||
toSmallString(): string;
|
||||
}
|
||||
}
|
||||
|
||||
String.prototype.toSmallString = ():string => {
|
||||
// 具体实现
|
||||
return '';
|
||||
};
|
||||
```
|
||||
|
||||
上面示例中,为 JavaScript 原生的`String`对象添加了`toSmallString()`方法。declare global 给出这个新增方法的类型描述。
|
||||
|
||||
这个示例第一行的空导出语句`export {}`,表示当前脚本是一个模块。这是因为 declare global必须用在模块里面。
|
||||
|
||||
下面的示例是为 window 对象添加一个属性`myAppConfig`。
|
||||
|
||||
```typescript
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface window {
|
||||
myAppConfig:object;
|
||||
}
|
||||
}
|
||||
|
||||
const config = window.myAppConfig;
|
||||
```
|
||||
|
||||
declare global 只能扩充现有对象的类型描述,不能增加新的顶层类型。
|
||||
|
||||
## declare enum
|
||||
|
||||
declare 关键字给出 enum 类型描述的例子如下,后面的写法都是允许的。
|
||||
|
||||
```typescript
|
||||
declare enum E1 {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
declare enum E2 {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
declare const enum E3 {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
declare const enum E3 {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
```
|
||||
|
||||
## declare module 命令
|
||||
|
||||
我们可以为每个模块文件,定义一个`.d.ts`文件。但是,更方便的做法是为整个项目,定义一个大的`.d.ts`文件,在这个文件里面使用`declare module`定义每个模块文件的类型。
|
||||
|
||||
下面的示例是`node.d.ts`文件的一部分。
|
||||
|
||||
```typescript
|
||||
declare module "url" {
|
||||
export interface Url {
|
||||
protocol?: string;
|
||||
hostname?: string;
|
||||
pathname?: string;
|
||||
}
|
||||
|
||||
export function parse(
|
||||
urlStr: string,
|
||||
parseQueryString?,
|
||||
slashesDenoteHost?
|
||||
): Url;
|
||||
}
|
||||
|
||||
declare module "path" {
|
||||
export function normalize(p: string): string;
|
||||
export function join(...paths: any[]): string;
|
||||
export var sep: string;
|
||||
}
|
||||
```
|
||||
|
||||
使用时,脚本使用三斜杠命令,加载这个类型声明文件。
|
||||
|
||||
```typescript
|
||||
/// <reference path="node.d.ts"/>
|
||||
```
|
||||
|
||||
如果不加载上面的`reference`标签,脚本文件使用外部模块时,就需要在脚本里面使用 declare 命令单独给出外部模块的类型。
|
||||
|
||||
但是,对于某些第三方模块,原始作者没有提供接口类型,这时可以在脚本顶部加上下面一行命令。
|
||||
|
||||
```typescript
|
||||
declare module "模块名";
|
||||
|
||||
// 例子
|
||||
declare module "hot-new-module";
|
||||
```
|
||||
|
||||
加上上面的命令以后,外部模块即使没有类型,也可以通过编译。但是,从该模块输入的所有接口都将为`any`类型。
|
||||
|
||||
## 扩展模块类型
|
||||
|
||||
declare 可以用来对一个现有模块进行类型扩充,主要适合需要扩充外部模块的情况。
|
||||
|
||||
如果一个项目有多个模块文件,可以对一个文件中声明的类型,在另一个文件中进行扩展。
|
||||
下面是另一个例子。一个项目有多个模块,可以在一个模型中,对另一个模块的接口进行类型扩展。
|
||||
|
||||
```typescript
|
||||
// a.ts
|
||||
@@ -377,6 +245,137 @@ const a:A = { x: 0, y: 0 };
|
||||
|
||||
(3)不能对默认的`default`接口进行扩展,只能对 export 命令输出的命名接口进行扩充。这是因为在进行类型扩展时,需要依赖输出的接口名。
|
||||
|
||||
某些第三方模块,原始作者没有提供接口类型,这时可以在自己的脚本顶部加上下面一行命令。
|
||||
|
||||
```typescript
|
||||
declare module "模块名";
|
||||
|
||||
// 例子
|
||||
declare module "hot-new-module";
|
||||
```
|
||||
|
||||
加上上面的命令以后,外部模块即使没有类型,也可以通过编译。但是,从该模块输入的所有接口都将为`any`类型。
|
||||
|
||||
declare module 描述的模块名可以使用通配符。
|
||||
|
||||
```typescript
|
||||
declare module 'my-plugin-*' {
|
||||
interface PluginOptions {
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
function initialize(options: PluginOptions): void;
|
||||
export = initialize;
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,模块名`my-plugin-*`表示适配所有以`my-plugin-`开头的模块名(比如`my-plugin-logger`)。
|
||||
|
||||
## declare global
|
||||
|
||||
如果要为 JavaScript 引擎的原生对象添加属性和方法,可以使用`declare global {}`语法。
|
||||
|
||||
```typescript
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface String {
|
||||
toSmallString(): string;
|
||||
}
|
||||
}
|
||||
|
||||
String.prototype.toSmallString = ():string => {
|
||||
// 具体实现
|
||||
return '';
|
||||
};
|
||||
```
|
||||
|
||||
上面示例中,为 JavaScript 原生的`String`对象添加了`toSmallString()`方法。declare global 给出这个新增方法的类型描述。
|
||||
|
||||
这个示例第一行的空导出语句`export {}`,作用是强制编译器将这个脚本当作模块处理。这是因为`declare global`必须用在模块里面。
|
||||
|
||||
下面的示例是为 window 对象添加一个属性`myAppConfig`。
|
||||
|
||||
```typescript
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface window {
|
||||
myAppConfig:object;
|
||||
}
|
||||
}
|
||||
|
||||
const config = window.myAppConfig;
|
||||
```
|
||||
|
||||
declare global 只能扩充现有对象的类型描述,不能增加新的顶层类型。
|
||||
|
||||
## declare enum
|
||||
|
||||
declare 关键字给出 enum 类型描述的例子如下,下面的写法都是允许的。
|
||||
|
||||
```typescript
|
||||
declare enum E1 {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
declare enum E2 {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
declare const enum E3 {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
declare const enum E4 {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
```
|
||||
|
||||
## declare module 用于类型声明文件
|
||||
|
||||
我们可以为每个模块脚本,定义一个`.d.ts`文件,把该脚本用到的类型定义都放在这个文件里面。但是,更方便的做法是为整个项目,定义一个大的`.d.ts`文件,在这个文件里面使用`declare module`定义每个模块脚本的类型。
|
||||
|
||||
下面的示例是`node.d.ts`文件的一部分。
|
||||
|
||||
```typescript
|
||||
declare module "url" {
|
||||
export interface Url {
|
||||
protocol?: string;
|
||||
hostname?: string;
|
||||
pathname?: string;
|
||||
}
|
||||
|
||||
export function parse(
|
||||
urlStr: string,
|
||||
parseQueryString?,
|
||||
slashesDenoteHost?
|
||||
): Url;
|
||||
}
|
||||
|
||||
declare module "path" {
|
||||
export function normalize(p: string): string;
|
||||
export function join(...paths: any[]): string;
|
||||
export var sep: string;
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,`url`和`path`都是单独的模块脚本,但是它们的类型都定义在`node.d.ts`这个文件里面。
|
||||
|
||||
使用时,自己的脚本使用三斜杠命令,加载这个类型声明文件。
|
||||
|
||||
```typescript
|
||||
/// <reference path="node.d.ts"/>
|
||||
```
|
||||
|
||||
如果没有上面这一行命令,自己的脚本使用外部模块时,就需要在脚本里面使用 declare 命令单独给出外部模块的类型。
|
||||
|
||||
## 参考链接
|
||||
|
||||
- [How Does The Declare Keyword Work In TypeScript?](https://timmousk.com/blog/typescript-declare/), Tim Mouskhelichvili
|
||||
- [How Does The Declare Keyword Work In TypeScript?](https://timmousk.com/blog/typescript-declare/), Tim Mouskhelichvili
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# 装饰器的传统语法
|
||||
# 装饰器(旧语法)
|
||||
|
||||
本章介绍装饰器的传统语法。
|
||||
上一章介绍了装饰器的标准语法,那是在2022年通过成为标准的。但是在此之前,TypeScript 早在2014年就支持装饰器,不过使用的是旧语法。
|
||||
|
||||
## --experimentalDecorators 编译参数
|
||||
装饰器的旧语法与标准语法,有相当大的差异。旧语法以后会被淘汰,但是目前大量现有项目依然在使用它,本章就介绍旧语法下的装饰器。
|
||||
|
||||
使用装饰器的传统语法,需要打开`--experimentalDecorators`编译参数。
|
||||
## experimentalDecorators 编译选项
|
||||
|
||||
使用装饰器的旧语法,需要打开`--experimentalDecorators`编译选项。
|
||||
|
||||
```bash
|
||||
$ tsc --target ES5 --experimentalDecorators
|
||||
```
|
||||
|
||||
除了`--experimentalDecorators`这个配置项目用来打开装饰器支持,还有另外一个配置项`--emitDecoratorMetadata`,用来产生一些元数据,供其他工具(比如 reflect-metadata )使用。
|
||||
此外,还有另外一个编译选项`--emitDecoratorMetadata`,用来产生一些装饰器的元数据,供其他工具或某些模块(比如 reflect-metadata )使用。
|
||||
|
||||
这两个配置项可以在命令行设置,也可以在`tsconfig.json`文件里面进行设置。
|
||||
这两个编译选项可以在命令行设置,也可以在`tsconfig.json`文件里面进行设置。
|
||||
|
||||
```javascript
|
||||
{
|
||||
@@ -63,9 +65,9 @@ class A {
|
||||
|
||||
上面示例中,A 是类装饰器,B 是属性装饰器,C 是方法装饰器,D 是参数装饰器,E 是存取器装饰器。
|
||||
|
||||
注意,构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是用来装饰构造方法。
|
||||
注意,构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
|
||||
|
||||
另外,装饰器只能用于类,要么应用于一个类,要么应用于一个类的内部成员,不能用于独立的函数。
|
||||
另外,装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数。
|
||||
|
||||
```typescript
|
||||
function Decorator() {
|
||||
@@ -287,9 +289,13 @@ class C {
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
||||
(new C()).add(1, 2)
|
||||
// params: 1 2
|
||||
// result: 3
|
||||
```
|
||||
|
||||
上面示例中,方法装饰器`@logger`用来装饰`add()`方法,它的作用是将该方法的结果输出一条日志。每当`add()`调用一次,控制台就会打印出一条日志“result: ...”。
|
||||
上面示例中,方法装饰器`@logger`用来装饰`add()`方法,它的作用是让该方法输出日志。每当`add()`调用一次,控制台就会打印出参数和运行结果。
|
||||
|
||||
## 属性装饰器
|
||||
|
||||
@@ -356,13 +362,11 @@ class PropertyExample {
|
||||
// 输出 Property name undefined
|
||||
```
|
||||
|
||||
上面示例中,属性装饰器`@logProperty`内部想要获取实例属性`name`的属性描述对象,结果拿到的是`undefined`。
|
||||
|
||||
因为上例的`target`是类的原型对象,不是实例对象,所以拿不到`name`属性,也就是说`target.name`是不存在的,所以拿到的是`undefined`。只有通过`this.name`才能拿到`name`属性,但是这时`this`还不存在。
|
||||
上面示例中,属性装饰器`@logProperty`内部想要获取实例属性`name`的属性描述对象,结果拿到的是`undefined`。因为上例的`target`是类的原型对象,不是实例对象,所以拿不到`name`属性,也就是说`target.name`是不存在的,所以拿到的是`undefined`。只有通过`this.name`才能拿到`name`属性,但是这时`this`还不存在。
|
||||
|
||||
属性装饰器不仅无法获得实例属性的值,也不能初始化或修改实例属性,而且它的返回值也会被忽略。因此,它的作用很有限。
|
||||
|
||||
不过,如果属性装饰器设置了当前属性的取值器(setter),然后在构造函数里面为实例属性赋值,这时可以拿到属性的值。
|
||||
不过,如果属性装饰器设置了当前属性的存取器(getter/setter),然后在构造函数里面就可以对实例属性进行读写。
|
||||
|
||||
```typescript
|
||||
function Min(limit:number) {
|
||||
@@ -466,7 +470,7 @@ class Point {
|
||||
|
||||
上面示例中,装饰器`@configurable(false)`关闭了所装饰属性(`x`和`y`)的属性描述对象的`configurable`键(即关闭了属性的可配置性)。
|
||||
|
||||
下面的示例是将装饰器用来验证属性,如果赋值不满足条件就报错。
|
||||
下面的示例是将装饰器用来验证属性值,如果赋值不满足条件就报错。
|
||||
|
||||
```typescript
|
||||
function validator(
|
||||
@@ -507,7 +511,7 @@ c.foo = 150;
|
||||
|
||||
上面示例中,装饰器用自己定义的存值器,取代了原来的存值器,加入了验证条件。
|
||||
|
||||
TypeScript 不允许对同一个属性的存取器(getter 和 setter)使用同一个装饰器,也就是说只能装饰两个存取器里面的一个,且必须排在前面的那一个,否则报错。
|
||||
TypeScript 不允许对同一个属性的存取器(getter 和 setter)使用同一个装饰器,也就是说只能装饰两个存取器里面的一个,且必须是排在前面的那一个,否则报错。
|
||||
|
||||
```typescript
|
||||
// 报错
|
||||
@@ -823,4 +827,5 @@ x
|
||||
- [Deep introduction to using and implementing TypeScript decorators](https://techsparx.com/nodejs/typescript/decorators/introduction.html), by David Herron
|
||||
- [Deep introduction to property decorators in TypeScript](https://techsparx.com/nodejs/typescript/decorators/properties.html), by David Herron
|
||||
- [Deep introduction to accessor decorators in TypeScript](https://techsparx.com/nodejs/typescript/decorators/accessors.html), by David Herron
|
||||
- [Using Property Decorators in Typescript with a real example](https://dev.to/danywalls/using-property-decorators-in-typescript-with-a-real-example-44e), by Dany Paredes
|
||||
- [Using Property Decorators in Typescript with a real example](https://dev.to/danywalls/using-property-decorators-in-typescript-with-a-real-example-44e), by Dany Paredes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user