• Typescript的declare 关键字


    前言

            declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。

            它的主要作用,就是让当前文件可以使用其他文件声明的类型。举例来说,自己的脚本使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用declare关键字,告诉编译器外部函数的类型。这样的话,编译单个脚本就不会因为使用了外部类型而报错。

    作用范围

            declare 关键字可以描述以下类型:

    • 变量(const、let、var 命令声明)
    • type 或者 interface 命令声明的类型
    • class
    • enum
    • 函数(function)
    • 模块(module)
    • 命名空间(namespace)

            declare 关键字的重要特点是,它只是通知编译器某个类型是存在的,不用给出具体实现。比如,只描述函数的类型,不给出函数的实现,如果不使用declare,这是做不到的。

            declare 只能用来描述已经存在的变量和数据结构,不能用来声明新的变量和数据结构。另外,所有 declare 语句都不会出现在编译后的文件里面。

    外部变量

            declare 关键字可以给出外部变量的类型描述。

            举例来说,当前脚本使用了其他脚本定义的全局变量x。

    x = 123; // 报错

            上面示例中,变量x是其他脚本定义的,当前脚本不知道它的类型,编译器就会报错。

            这时使用 declare 命令给出它的类型,就不会报错了。

    1. declare let x:number;
    2. x = 1;

            如果 declare 关键字没有给出变量的具体类型,那么变量类型就是any

    1. declare let x;
    2. x = 1;

            上面示例中,变量x的类型为any。

            注意,declare 关键字只用来给出类型描述,是纯的类型代码,不允许设置变量的初始值,即不能涉及值。

    1. // 报错
    2. declare let x:number = 1;

            declare 设置了变量的初始值,结果就报错了。 

            例子:

            脚本使用浏览器全局对象document

    1. declare var document;
    2. document.title = 'Hello';

            上面示例中,declare 告诉编译器,变量document的类型是外部定义的(具体定义在 TypeScript 内置文件lib.d.ts)。

            如果 TypeScript 没有找到document的外部定义,这里就会假定它的类型是any

    外部函数

    1. declare function sayHello(
    2. name:string
    3. ):void;
    4. sayHello('张三');

            上面示例中,declare 命令给出了sayHello()的类型描述,因此可以直接使用它。

            注意,这种单独的函数类型声明语句,只能用于declare命令后面。一方面,TypeScript 不支持单独的函数类型声明语句;另一方面,declare 关键字后面也不能带有函数的具体实现。

    外部类

    1. declare class Animal {
    2. constructor(name:string);
    3. eat():void;
    4. sleep():void;
    5. }

            类内部函数不能书写函数体

    1. declare class C {
    2. // 静态成员
    3. public static s0():string;
    4. private static s1:string;
    5. // 属性
    6. public a:number;
    7. private b:number;
    8. // 构造函数
    9. constructor(arg:number);
    10. // 方法
    11. m(x:number, y:number):number;
    12. // 存取器
    13. get c():number;
    14. set c(value:number);
    15. // 索引签名
    16. [index:string]:any;
    17. }

            同样的,declare 后面不能给出 Class 的具体实现或初始值

    外部模块(外部空间)

            如果想把变量、函数、类组织在一起,可以将 declare 与 module 或 namespace 一起使用。

    1. declare namespace AnimalLib {
    2. class Animal {
    3. constructor(name:string);
    4. eat():void;
    5. sleep():void;
    6. }
    7. type Animals = 'Fish' | 'Dog';
    8. }
    9. // 或者
    10. declare module AnimalLib {
    11. class Animal {
    12. constructor(name:string);
    13. eat(): void;
    14. sleep(): void;
    15. }
    16. type Animals = 'Fish' | 'Dog';
    17. }

            上面示例中,declare 关键字给出了 module 或 namespace 的类型描述。

            declare module 和 declare namespace 里面,加不加 export 关键字都可以。

    1. declare namespace Foo {
    2. export var a: boolean;
    3. }
    4. declare module 'io' {
    5. export function readFile(filename:string):string;
    6. }

            来看看实际的使用场景:

            当前脚本使用了myLib这个外部库,它有方法greetingFn()和属性greetings

    1. let result = myLib.greetingFn('你好');
    2. console.log('欢迎词:' + result);
    3. let count = myLib.greetings;

            myLib的类型描述就可以这样写:

    1. declare namespace myLib {
    2. function makeGreeting(s:string):string;
    3. let numberOfGreetings:number;
    4. }

    declare 关键字为外部模块新增属性和方法

    1. import { Foo as Bar } from 'moduleA';
    2. declare module 'moduleA' {
    3. interface Bar extends Foo {
    4. custom: {
    5. prop1:string;
    6. }
    7. }
    8. }

            上面示例中,从模块moduleA导入了Foo接口,将其重命名为Bar,并用 declare 关键字为Bar增加一个属性custom。

            另外一种场景:

            一个项目有多个模块,可以在一个模型中,对另一个模块的接口进行类型扩展。

    1. // a.ts
    2. export interface A {
    3. x: number;
    4. }
    5. // b.ts
    6. import { A } from './a';
    7. declare module './a' {
    8. interface A {
    9. y: number;
    10. }
    11. }
    12. const a:A = { x: 0, y: 0 };

    上面示例中,脚本a.ts定义了一个接口A,脚本b.ts为这个接口添加了属性y。declare module './a' {}表示对a.ts里面的模块,进行类型声明,而同名 interface 会自动合并,所以等同于扩展类型。

    使用这种语法进行模块的类型扩展时,有两点需要注意:

    (1)declare module NAME语法里面的模块名NAME,跟 import 和 export 的模块名规则是一样的,且必须跟当前文件加载该模块的语句写法(上例import { A } from './a')保持一致。

    (2)不能创建新的顶层类型。也就是说,只能对a.ts模块中已经存在的类型进行扩展,不允许增加新的顶层类型,比如新定义一个接口B。

    (3)不能对默认的default接口进行扩展,只能对 export 命令输出的命名接口进行扩充。这是因为在进行类型扩展时,需要依赖输出的接口名。

            某些第三方模块,原始作者没有提供接口类型,这时可以在自己的脚本顶部加上下面一行命令。

    declare module "模块名";

            加上上面的命令以后,外部模块即使没有类型,也可以通过编译。但是,从该模块输入的所有接口都将为any类型。

            declare module 描述的模块名可以使用通配符。

    1. declare module 'my-plugin-*' {
    2. interface PluginOptions {
    3. enabled: boolean;
    4. priority: number;
    5. }
    6. function initialize(options: PluginOptions): void;
    7. export = initialize;
    8. }

            上面示例中,模块名my-plugin-*表示适配所有以my-plugin-开头的模块名(比如my-plugin-logger)

         

    declare global

            如果要为 JavaScript 引擎的原生对象添加属性和方法,可以使用declare global {}语法。

    1. export {};
    2. declare global {
    3. interface String {
    4. toSmallString(): string;
    5. }
    6. }
    7. String.prototype.toSmallString = ():string => {
    8. // 具体实现
    9. return '';
    10. };

            上面示例中,为 JavaScript 原生的String对象添加了toSmallString()方法。declare global 给出这个新增方法的类型描述。

            这个示例第一行的空导出语句export {},作用是强制编译器将这个脚本当作模块处理。这是因为declare global必须用在模块里面。

    1. export {};
    2. declare global {
    3. interface window {
    4. myAppConfig:object;
    5. }
    6. }
    7. const config = window.myAppConfig;

             declare global 只能扩充现有对象的类型描述,不能增加新的顶层类型。

    declare enum

    declare 关键字给出 enum 类型描述的例子如下,下面的写法都是允许的。

    1. declare enum E1 {
    2. A,
    3. B,
    4. }
    5. declare enum E2 {
    6. A = 0,
    7. B = 1,
    8. }
    9. declare const enum E3 {
    10. A,
    11. B,
    12. }
    13. declare const enum E4 {
    14. A = 0,
    15. B = 1,
    16. }

    declare module 用于类型声明文件

            我们可以为每个模块脚本,定义一个.d.ts文件,把该脚本用到的类型定义都放在这个文件里面。但是,更方便的做法是为整个项目,定义一个大的.d.ts文件,在这个文件里面使用declare module定义每个模块脚本的类型

    1. // node.d.ts
    2. declare module "url" {
    3. export interface Url {
    4. protocol?: string;
    5. hostname?: string;
    6. pathname?: string;
    7. }
    8. export function parse(
    9. urlStr: string,
    10. parseQueryString?,
    11. slashesDenoteHost?
    12. ): Url;
    13. }
    14. declare module "path" {
    15. export function normalize(p: string): string;
    16. export function join(...paths: any[]): string;
    17. export var sep: string;
    18. }

            上面示例中,url和path都是单独的模块脚本,但是它们的类型都定义在node.d.ts这个文件里面。

            使用时,自己的脚本使用三斜杠命令,加载这个类型声明文件。

    /// 

            如果没有上面这一行命令,自己的脚本使用外部模块时,就需要在脚本里面使用 declare 命令单独给出外部模块的类型。

  • 相关阅读:
    docker打包发版
    红黑树及其应用介绍(万字长文)
    《实现领域驱动设计》笔记——架构
    Libevent库的学习
    OceanMind海睿思数据中台迎来重磅更新,使用体验全面提升!
    c++11~c++20 -06-字节对齐alignof、alignas
    asp.net core服务限制堆内存大小
    【大数据】-- dataworks 创建odps 的 hudi 外表
    数字IC/FPGA面试题目合集解析(一)
    ShardingSphere|shardingJDBC - 在使用数据分片功能情况下无法配置读写分离
  • 原文地址:https://blog.csdn.net/weixin_42274805/article/details/132871653