TS学习笔记十一:声明文件

  本节介绍声明文件相关内容,包括声明文件的规范、原理、结构、模板、发布及使用等相关内容。

  1. 讲解视频

    TS学习笔记二十一:声明文件结构介绍


    TS学习笔记二十四:声明文件示例


    TS学习笔记二十二:声明文件规范

    0005.jpg?auth_key=4859226230-0-0-7259dd5b43ff5dfdacb1d54c3dc8efb4)(title-TS学习笔记二十三:声明文件原理)]

    TS学习笔记二十三:声明文件原理

  2. B站视频

    TS学习笔记二十一:声明文件结构介绍

  3. 西瓜视频
    https://www.ixigua.com/7325237297077715497

TS学习笔记十一:声明文件_第1张图片

一、结构

  编写声明文件之前需要识别库的类型,包括全局库、模块化库和UMD库。

1. 全局库:

  全局库是指能在全局命名空间下访问的,不需要任何形式的import,如Jquery等,只简单的暴露出一个或多个全局变量。全局库直接使用script标签引用。

识别全局库时,通常会看到:

  1. 顶级的var语句或function声明。
  2. 一个或多个赋值语句到window上。
  3. 源码中会假设dom原始值对象document或window是存在的,进行了直接调用。

在全局库的源码中无法看到:

  1. 任何类型的模块加载器,如require或define。
  2. Commonjs/Node.js风格的导入,如:var a = require(‘a’);
  3. define(…)的调用。
  4. 库文档中说明了如果require或导入这个库是的提示。

  全局库可以很轻易的转换为UMD库,所以目前大多数库都是UMD类型的,全局库的声明模板文件是global.d.ts,具体内容在后面章节。

2. 模块化库:

  模块化库只能工作在有模块加载器的环境中,如expres只能在node.js中工作,使用时必须使用require函数加载。
模块库至少会包含下列条件之一:

  1. 去条件的调用require或define。
  2. 像import * as a from ‘b’;或export c的声明。
  3. 赋值给exports或module.exports;

  极少包含window或global对象的赋值。

3. UMD库:

  UMD模块是既可以作为模块使用,也可以作为全局使用的模块。UMD模块会自动检查是否存在模块加载器环境,如果在库中看到了typeof define,typeof window或typeof module等时几乎就可以确定是一个UMD库了。
  模块有三个可用模板:module.d.ts, module-class.d.ts, module-function.d.ts。
  如果模块可以被当做函数调用,则使用module-function.d.ts;如果模块能够使用new来构建,则使用module-class.d.ts;如果不能当做函数调用也不能用new来构建,则使用module.d.ts。

4. 模块插件:

  一个模块插件可以改变一个模块的机构,对于声明文件的目标,可以写相同的代码不论被改变的模块是一个纯粹的模块还是UMD模块,模板为module-plugin-d-ts。

全局插件:

  一个全局插件是全局代码,会改变全局对象的结构,对于全局修改的模块,在运行时存在冲突的可能。可以从文档中看出是否是一个全局插件。使用global-plugin-d-ts模板。

全局修改的模块:

  当一个全局修改的模块被导入的时候,会改变全局作用域里的值,可能造成运行时的冲突。可以从文档中识别出来,与全局插件相似,使用global-modifying-module-d-ts模板。

如果库依赖其它的库,可能会有以下几种依赖:

1. 依赖全局库

/// 指令:

/// 

function getThing(): someLib.thing;

2. 依赖模块
使用import引入:

import * as moment from "moment";
function getThing(): moment;

3. 依赖UMD库
如果全局库依赖于某个UMD模块,使用///

/// 
function getThing(): moment;

如果模块或UMD依赖于一个UMD库,使用import语句:

import * as someLib from 'someLib';

  不能使用///指令声明UMD库的依赖。
  书写全局声明文件时,可能会导致无法处理的命名冲突,可以使用库定义的全局变量名来声明命名空间类型。
  一个插件添加或修改已存在的顶层模块的导出部分,在Commonjs或其他加载器里是允许的,ES模块被当做时不可改变的,因此此种模式就不可行,因为ts是能不预知加载器类的,所以没在编译时保证。
  在ES6模块加载器中,顶层的对象只能具有属性,顶层的模块对象永远不能被调用。

二、规范

1. 普通类型

  对于普通类型number/string/boolean在写声明文件时不要使用对应的装盒对象,即Number、String、和Boolean,注意使用首字母小写的原始类型,如果要用Object,使用any代替。

2. 泛型

  不要定义一个从来没有使用过其类型参数的泛型类型。

3. 回调函数类型

回调函数返回值类型:
  不要为返回值忽略的回调函数设置一个any返回值类型,应该给返回值类型被忽略的回调函数设置void类型的返回值类型:

/* 错误 */

function fn(x: () => any) {

    x();

}

/* OK */

function fn(x: () => void) {

    x();

}

因为使用void类型相对安全,可以防止不小心使用了x的返回值:

function fn(x: () => void) {

    var k = x(); // oops! meant to do something else

    k.doSomething(); //此处使用了x的返回值,有可能报错。

}

回调函数里的可选参数:

不要在回调函数里使用可选参数:

/* 错误 */

interface Fetcher {

    getObject(done: (data: any, elapsedTime?: number) => void): void;

}

  上述实例中的done回调函数可能以1个参数或2个参数进行调用,不需要把这个参数当成可选参数来处理,允许提供一个接收较少参数的回调函数:

/* OK */

interface Fetcher {

    getObject(done: (data: any, elapsedTime: number) => void): void;

}

重载与回调函数:
  不要因为回调函数参数个数不同而写不同的重载,应该只使用最大参数个数写一个重载,因为回调函数总是可以忽略某个参数的,因此没有必要为参数少的情况写重载,参数少的回调函数首先允许错误类型的函数被传入,因为他们匹配第一个重载:

/* 错误 */

declare function beforeAll(action: () => void, timeout?: number): void;

declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

/* OK */

declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

4. 函数重载

顺序:
  不要把一般的重载放在精确的重载前面,应该排序重载令精确的排在一般的签名,因为当解析函数调用的时候ts会选择第一个匹配到的重载,当前面的重载比后面的普通,则后面的将会被隐藏了不会被调用:

/* 错误 */

declare function fn(x: any): any;

declare function fn(x: HTMLElement): number;

declare function fn(x: HTMLDivElement): string;

 

var myElem: HTMLDivElement;

var x = fn(myElem); // x: any, wat?

/* OK */

declare function fn(x: HTMLDivElement): string;

declare function fn(x: HTMLElement): number;

declare function fn(x: any): any;

 

var myElem: HTMLDivElement;

var x = fn(myElem); // x: string, :)

使用可选参数:

  不要仅在末尾参数不同时写不同的重载,应该尽可能使用可选参数:

/* 错误 */

interface Example {

    diff(one: string): number;

    diff(one: string, two: string): number;

    diff(one: string, two: string, three: boolean): number;

}

/* OK */

interface Example {

    diff(one: string, two?: string, three?: boolean): number;

}

  在所有重载都有相同的返回值时会不好用,因为ts解析签名兼容性时会查看是否某个目标签名能够使用源的参数调用,且允许外来参数:

function fn(x: (a: string, b: number, c: number) => void) { }

var x: Example;

// When written with overloads, OK -- used first overload

// When written with optionals, correctly an error

fn(x.diff);

  当使用了ts严格检查null特性时,因为没有指定的参数在js里标识undefined,通常显示的为可选参数传入一个undefined:

var x: Example;

// When written with overloads, incorrectly an error because of passing 'undefined' to 'string'

// When written with optionals, correctly OK

x.diff("something", true ? undefined : "hour");

使用联合类型:

  不要仅在某个位置上的参数类型不同的情况下定义重载,应该尽可能使用联合类型:

/* WRONG */

interface Moment {

    utcOffset(): number;

    utcOffset(b: number): Moment;

    utcOffset(b: string): Moment;

}

/* OK */

interface Moment {

    utcOffset(): number;

    utcOffset(b: number|string): Moment;

}

function fn(x: string): void;

function fn(x: number): void;

function fn(x: number|string) {

    // When written with separate overloads, incorrectly an error

    // When written with union types, correctly OK

    return moment().utcOffset(x);

}

三、原理

1. 类型

  类型通过以下方式引入,每种声明形式都会创建一个新的类型名称:

  1. 类型别名声明(type sn = number | string;)
  2. 接口声明(interface I { x: number[]; })
  3. 类声明(class C { })
  4. 枚举声明(enum E { A, B, C })
  5. 指向某个类型的import声明

2. 值

  与类型相比,值是运行时的名字,可以在表达式里引用,创建方式如下:

  1. let/const/var等声明
  2. 包含值的namespace或module声明
  3. enum声明
  4. class声明
  5. 指向值import声明
  6. function声明

3. 命名空间

  类型可以存在于命名空间中,如:let a : A.B.C,此时C类型来自A.B的命名空间。
  一个给定的名字A,可以找出三种不同的意义,一个类型、一个值或一个命名空间,如let a : A.A = A,A首先被当做命名空间,然后作为类型名,最后是值。

4. 内置组合

  声明class时会声明两个东西,如class A{},此时声明了一个类型A指向类的实例机构,值C指向类构造函数,枚举声明也有类似的行为。
  可以使用组合通过相同的名字标识两种不同的对象:

export var Bar: { a: Bar };

export interface Bar {

  count: number;

}

5. 高级组合

  有一些声明可以通过多个声明进行组合,如class C{}和interface C{}可以同时存在并且都可以作为C类型的属性。值总是会和同名的其它值产生冲突,除非他们在不同的命名空间中,类型冲突则发生在使用类型别名声明的情况下,命名空间永远不会发生冲突。
利用interface添加:
  可以使用一个interface给别的一个interface声明里添加额外的成员,但不能使用别名给类型别名中添加成员:

interface Foo {

  x: number;

}

// ... elsewhere ...

interface Foo {

  y: number;

}

let a: Foo = ...;

console.log(a.x + a.y); // OK

也可以作用于类:

class Foo {

  x: number;

}

// ... elsewhere ...

interface Foo {

  y: number;

}

let a: Foo = ...;

console.log(a.x + a.y); // OK

使用namespace添加:

  命名空间声明可以用来添加新类型,值和命名空间,可以添加一个静态成员到一个类:

class C {

}

// ... elsewhere ...

namespace C {

  export let x: number;

}

let y = C.x; // OK

也可以给类添加一个命名空间类型:

class C {

}

// ... elsewhere ...

namespace C {

  export interface D { }

}

let y: C.D; // OK

  上述实例中作为命名空间的C不会和类创建的值或类型C互相冲突。也可以合同namespace声明:

namespace X {

  export interface Y { }

  export class Z { }

}

 

// ... elsewhere ...

namespace X {

  export var Y: number;

  export namespace Z {

    export class C { }

  }

}

type X = string;

  上述示例中,第一段代码创建了以下名字于含义:

  • 一个值X(因为namespace声明包含一个值,Z)

  • 一个命名空间X(因为namespace声明包含一个值,Z)

  • 在命名空间X里的类型Y

  • 在命名空间X里的类型Z(类的实例结构)

  • 值X的一个属性值Z(类的构造函数)

第二段代码创建了以下名字和含义:

  • 值Y(number类型),它是值X的一个属性

  • 一个命名空间Z

  • 值Z,它是值X的一个属性

  • 在X.Z命名空间下的类型C

  • 值X.Z的一个属性值C

  • 类型X

使用export=或import:

  export和import声明会导出或导入目标的所有含义。

四、举例

1. 全局变量

  使用declare var声明变量,如果变量是只读的,可以使用declare const,也可以使用declare let声明拥有块级作用域的变了。

2. 全局函数

  使用declare function声明函数。

3. 带属性的对象

  使用declare namespace描述用点表示法访问的类型或值。

4. 函数重载

  使用declare function进行声明函数,重载时可以如下进行声明:

declare function getWidget(n: number): Widget;

declare function getWidget(s: string): Widget[];

5. 可重用类型(接口)

  使用interface定义一个带有属性的类型;

6. 可重用类型(类型别名)

  可以使用类型别名来定义类型的短名,

7. 组织类型

  使用命名空间组织类型,也可以在一个声明中创建嵌套的命名空间。

8. 类

  使用declare class描述一个类或像类一样的对象,类可以有属性和方法,就像构造函数一样。

五、模板

1. global-modifying-module.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This is the global-modifying module template file. You should rename it to index.d.ts

 *~ and place it in a folder with the same name as the module.

 *~ For example, if you were writing a file for "super-greeter", this

 *~ file should be 'super-greeter/index.d.ts'

 */

 

/*~ Note: If your global-modifying module is callable or constructable, you'll

 *~ need to combine the patterns here with those in the module-class or module-function

 *~ template files

 */

declare global {

    /*~ Here, declare things that go in the global namespace, or augment

     *~ existing declarations in the global namespace

     */

    interface String {

        fancyFormat(opts: StringFormatOptions): string;

    }

}

 

/*~ If your module exports types or values, write them as usual */

export interface StringFormatOptions {

    fancinessLevel: number;

}

 

/*~ For example, declaring a method on the module (in addition to its global side effects) */

export function doSomething(): void;

 

/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */

export { };

2. global-plugin.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This template shows how to write a global plugin. */

 

/*~ Write a declaration for the original type and add new members.

 *~ For example, this adds a 'toBinaryString' method with to overloads to

 *~ the built-in number type.

 */

interface Number {

    toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;

    toBinaryString(callback: MyLibrary.BinaryFormatCallback, opts?: MyLibrary.BinaryFormatOptions): string;

}

 

/*~ If you need to declare several types, place them inside a namespace

 *~ to avoid adding too many things to the global namespace.

 */

declare namespace MyLibrary {

    type BinaryFormatCallback = (n: number) => string;

    interface BinaryFormatOptions {

        prefix?: string;

        padding: number;

    }

}

3. global.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ If this library is callable (e.g. can be invoked as myLib(3)),

 *~ include those call signatures here.

 *~ Otherwise, delete this section.

 */

declare function myLib(a: string): string;

declare function myLib(a: number): number;

 

/*~ If you want the name of this library to be a valid type name,

 *~ you can do so here.

 *~

 *~ For example, this allows us to write 'var x: myLib';

 *~ Be sure this actually makes sense! If it doesn't, just

 *~ delete this declaration and add types inside the namespace below.

 */

interface myLib {

    name: string;

    length: number;

    extras?: string[];

}

 

/*~ If your library has properties exposed on a global variable,

 *~ place them here.

 *~ You should also place types (interfaces and type alias) here.

 */

declare namespace myLib {

    //~ We can write 'myLib.timeout = 50;'

    let timeout: number;

 

    //~ We can access 'myLib.version', but not change it

    const version: string;

 

    //~ There's some class we can create via 'let c = new myLib.Cat(42)'

    //~ Or reference e.g. 'function f(c: myLib.Cat) { ... }

    class Cat {

        constructor(n: number);

 

        //~ We can read 'c.age' from a 'Cat' instance

        readonly age: number;

 

        //~ We can invoke 'c.purr()' from a 'Cat' instance

        purr(): void;

    }

 

    //~ We can declare a variable as

    //~   'var s: myLib.CatSettings = { weight: 5, name: "Maru" };'

    interface CatSettings {

        weight: number;

        name: string;

        tailLength?: number;

    }

 

    //~ We can write 'const v: myLib.VetID = 42;'

    //~  or 'const v: myLib.VetID = "bob";'

    type VetID = string | number;

 

    //~ We can invoke 'myLib.checkCat(c)' or 'myLib.checkCat(c, v);'

    function checkCat(c: Cat, s?: VetID);

}

4. module-class.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This is the module template file for class modules.

 *~ You should rename it to index.d.ts and place it in a folder with the same name as the module.

 *~ For example, if you were writing a file for "super-greeter", this

 *~ file should be 'super-greeter/index.d.ts'

 */

 

/*~ Note that ES6 modules cannot directly export class objects.

 *~ This file should be imported using the CommonJS-style:

 *~   import x = require('someLibrary');

 *~

 *~ Refer to the documentation to understand common

 *~ workarounds for this limitation of ES6 modules.

 */

 

/*~ If this module is a UMD module that exposes a global variable 'myClassLib' when

 *~ loaded outside a module loader environment, declare that global here.

 *~ Otherwise, delete this declaration.

 */

export as namespace myClassLib;

 

/*~ This declaration specifies that the class constructor function

 *~ is the exported object from the file

 */

export = MyClass;

 

/*~ Write your module's methods and properties in this class */

declare class MyClass {

    constructor(someParam?: string);

 

    someProperty: string[];

 

    myMethod(opts: MyClass.MyClassMethodOptions): number;

}

 

/*~ If you want to expose types from your module as well, you can

 *~ place them in this block.

 */

declare namespace MyClass {

    export interface MyClassMethodOptions {

        width?: number;

        height?: number;

    }

}

5. module-function.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This is the module template file for function modules.

 *~ You should rename it to index.d.ts and place it in a folder with the same name as the module.

 *~ For example, if you were writing a file for "super-greeter", this

 *~ file should be 'super-greeter/index.d.ts'

 */

 

/*~ Note that ES6 modules cannot directly export callable functions.

 *~ This file should be imported using the CommonJS-style:

 *~   import x = require('someLibrary');

 *~

 *~ Refer to the documentation to understand common

 *~ workarounds for this limitation of ES6 modules.

 */

 

/*~ If this module is a UMD module that exposes a global variable 'myFuncLib' when

 *~ loaded outside a module loader environment, declare that global here.

 *~ Otherwise, delete this declaration.

 */

export as namespace myFuncLib;

 

/*~ This declaration specifies that the function

 *~ is the exported object from the file

 */

export = MyFunction;

 

/*~ This example shows how to have multiple overloads for your function */

declare function MyFunction(name: string): MyFunction.NamedReturnType;

declare function MyFunction(length: number): MyFunction.LengthReturnType;

 

/*~ If you want to expose types from your module as well, you can

 *~ place them in this block. Often you will want to describe the

 *~ shape of the return type of the function; that type should

 *~ be declared in here, as this example shows.

 */

declare namespace MyFunction {

    export interface LengthReturnType {

        width: number;

        height: number;

    }

    export interface NamedReturnType {

        firstName: string;

        lastName: string;

    }

 

    /*~ If the module also has properties, declare them here. For example,

     *~ this declaration says that this code is legal:

     *~   import f = require('myFuncLibrary');

     *~   console.log(f.defaultName);

     */

    export const defaultName: string;

    export let defaultLength: number;

}

6. module-plugin.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This is the module plugin template file. You should rename it to index.d.ts

 *~ and place it in a folder with the same name as the module.

 *~ For example, if you were writing a file for "super-greeter", this

 *~ file should be 'super-greeter/index.d.ts'

 */

 

/*~ On this line, import the module which this module adds to */

import * as m from 'someModule';

 

/*~ You can also import other modules if needed */

import * as other from 'anotherModule';

 

/*~ Here, declare the same module as the one you imported above */

declare module 'someModule' {

    /*~ Inside, add new function, classes, or variables. You can use

     *~ unexported types from the original module if needed. */

    export function theNewMethod(x: m.foo): other.bar;

 

    /*~ You can also add new properties to existing interfaces from

     *~ the original module by writing interface augmentations */

    export interface SomeModuleOptions {

        someModuleSetting?: string;

    }

 

    /*~ New types can also be declared and will appear as if they

     *~ are in the original module */

    export interface MyModulePluginOptions {

        size: number;

    }

}

7. module.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]

// Project: [~THE PROJECT NAME~]

// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

 

/*~ This is the module template file. You should rename it to index.d.ts

 *~ and place it in a folder with the same name as the module.

 *~ For example, if you were writing a file for "super-greeter", this

 *~ file should be 'super-greeter/index.d.ts'

 */

 

/*~ If this module is a UMD module that exposes a global variable 'myLib' when

 *~ loaded outside a module loader environment, declare that global here.

 *~ Otherwise, delete this declaration.

 */

export as namespace myLib;

 

/*~ If this module has methods, declare them as functions like so.

 */

export function myMethod(a: string): string;

export function myOtherMethod(a: number): number;

 

/*~ You can declare types that are available via importing the module */

export interface someType {

    name: string;

    length: number;

    extras?: string[];

}

 

/*~ You can declare properties of the module using const, let, or var */

export const myField: number;

 

/*~ If there are types, properties, or methods inside dotted names

 *~ of the module, declare them inside a 'namespace'.

 */

export namespace subProp {

    /*~ For example, given this definition, someone could write:

     *~   import { subProp } from 'yourModule';

     *~   subProp.foo();

     *~ or

     *~   import * as yourMod from 'yourModule';

     *~   yourMod.subProp.foo();

     */

    export function foo(): void;

}

六、发布

  对应编写好的声明文件,可以发布到npm上,发布时有两种方式:

  1. 与npm包捆绑到一起
  2. 发布到npm上

  如果包有一个.js文件,还需要在package.json里指定主声明文件,设置types属性指向捆绑在一起的声明文件,typings和types具有相同的意义,也可以使用。如果声明文件名为index.d.ts并位置在包的根目录里,就不需要使用types属性指定了。

  npm会管理所有的依赖,在package.json的dependencies指明了即可。不能再声明文件中使用/// ,应该使用/// 代替。

  如果类型依赖于另一个包,不要把依赖包放进自己的包中,保持他们在各自的文件里;不要将声明拷贝到你的包里;如果依赖包没有包含它自己的声明的话,应该依赖于npm类型声明包。

七、使用

  发布到npm库之后,可以直接使用npm install xx进行安装,下载安装之后就可以在代码中引入使用了。多数情况下,类型声明包的名字总是和他们在npm上的包的名字相同,但是有@types/前缀。

你可能感兴趣的:(#,Ts基础知识及面试题汇总,学习,笔记,ts,typescript,javascript,前端框架,前端)