声明详解
声明文件
.ts 和 .d.ts 区别
- *.d.ts对于typescript而言,是类型声明文件,且在*.d.ts文件中的顶级声明必须以declare或export修饰符开头。同时在项目编译过后,*.d.ts文件是不会生成任何代码的。
- 补充:默认使用tsc —init会开启skipLibCheck跳过声明文件检查,可以关闭它。
- 而.ts则没有那么多限制,任何在.d.ts中的内容,均可以在*.ts中使用。
一个文件有扩展名 .d.ts,这意味着每个根级别的声明都必须以 declare 关键字作为前缀。这有利于让开发者清楚的知道,在这里 TypeScript 将不会把它编译成任何代码,同时开发者需要确保这些在编译时存在。
声明作用域
declare与declare global它们功能是一样的。在d.ts中,使用declare与declare global两个作用是相等的。 因此,在d.ts进行declare,它默认是全局的,在使用declare global显得有点画蛇添足了。
在模块文件中定义declare,如果想要用作全局就可以使用declare global完成该需求。
拆分声明并导入-三斜杠指令
三斜线的path & types,和es6的import语义相似,同时三斜线指令必须放在文件的最顶端
当我们的声明文件过于庞大,一般都会采用三斜线指令,将我们的声明文件拆分成若干个,然后由一个入口文件引入。
// node_modules/@types/jquery/index.d.ts
/// <reference types="sizzle" />
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />
export = jQuery;
- 当我们在书写一个全局变量的声明文件时
- 当我们需要依赖一个全局变量的声明文件时
// types/jquery-plugin/index.d.ts
/// <reference types="jquery" />
declare function foo(options: JQuery.AjaxSettings): string;
/// 后面使用 xml 的格式添加了对 jquery 类型的依赖,这样就可以在声明文件中使用 JQuery.AjaxSettings 类型了
declare 声明语法
在使用declare声明类型的时候,并不能去定义具体的实现过程。declare代码只在编译阶段生效 https://www.tslang.cn/docs/handbook/modules.html https://juejin.cn/post/6898710177969602574#comment https://ts.xcatliu.com/basics/declaration-files.html#npm-%E5%8C%85
全局声明
在声明文件中,最常看见的语法之一。用来全局声明变量、常量、类、全局对象等等,前提是该文件不是模块声明文件 全局变量的声明文件主要有以下几种语法:
- declare var 声明全局变量
- declare function 声明全局方法
- declare class 声明全局类
- declare enum 声明全局枚举类型
- declare namespace 声明(含有子属性的)全局对象
- interface 和 type 声明全局类型(不用加declare 猜测js里没有的关键字不需要特殊说明,只存在与编译阶段)
declare const Jye1: string;
declare let Jye2: string;
declare class Jye3 {}
declare namespace Jye4 {}
非全局声明
这个文件包含import export,那么这个文件中包含的declare & interface & type就会变成局部声明,除却export出来的类型,其他declare的类型,则无法被使用。 但可以重新加上global修饰,让某一个声明提升为全局
// @types/react/index.d.ts
export = React; // 兼容CMD
export as namespace React; // UMD 库声明全局变量 把此文件模块当成React并导出到全局使用
declare namespace React {
type ElementType<P = any> =
{
[K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
}[keyof JSX.IntrinsicElements] |
ComponentType<P>;
}
// 上面出现了export,这个声明文件变成模块声明文件,而不是一个全局声明文件。
declare namespace Jye {
interface Info {
name: string;
age: number;
}
function getAge(): number;
}
export =对应的像是import xxx = require。其实使用都是类似的,只是为了兼容AMD和commonJS才有的语法
// src/b.ts
React.ElementType; // ok 能找到 因为被导出到全局了
let settings: Jye.Info = { // 找不到命名空间“Jye”。ts(2503) 因为在模块声明里
name: 'jye',
age: 8,
};
混用declare 和 export
与全局变量的声明文件类似,interface 前是不需要 declare 的。
declare const name: string;
declare function getName(): string;
declare class Animal {
constructor(name: string);
sayHi(): string;
}
declare enum Directions {
Up,
Down,
Left,
Right
}
interface Options {
data: any;
}
export { name, getName, Animal, Directions, Options };
模块声明与拓展
带不带引号
声明模块,比如lodash模块默认不能使用的情况,可以自己来声明这个模块 在 TS 中指定模块有两种方式:
declare module "buffer" {} // with quotes
和 declare module buffer {} // without quotes
前者(带引号)表示外部模块(ES6 模块),目前用于 .d.ts将多个 ES6 模块放在一个文件中的文件:
```ts
declare module "buffer" {}
declare module "fs" {}
后者(不带引号)被用作命名空间,现在替换为:
declare namespace buffer {}
还可以声明文件模块
// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
定义外部模块
要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在.d.ts文件里定义的
这里使用module关键字并且把名字用引号括起来,方便之后import
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 let sep: string;
}
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
拓展类型
环境声明允许你安全地使用现有的 JavaScript 库,并且能让你的 JavaScript、CoffeeScript 或者其他需要编译成 JavaScript 的语言逐步迁移至 TypeScript。
可以通过 declare 关键字来告诉 TypeScript,你正在试图表述一个其他地方已经存在的代码
拓展基本类型
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script>
标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'
ts用这个在js声明的jQuery时,提前declare下
declare var jQuery: (selector: string) => any;
jQuery('#foo');
一般选择把这些声明放入 .ts 或者 .d.ts 里。在你实际的项目里,我们强烈建议你应该把声明放入独立的 .d.ts 里(可以从一个命名为 global.d.ts 或者 vendor.d.ts 文件开始)。
声明合并可拓展复杂类型
有的第三方库扩展了一个全局变量,可是此全局变量的类型却没有相应的更新过来,就会导致 ts 编译错误,此时就需要扩展全局变量的类型。 比如扩展 String 类型的接口(通过声明合并,使用 interface String 即可给 String 添加属性或方法)
interface String {
prependHello(): string;
}
'foo'.prependHello();
也可以使用 declare namespace 给已有的命名空间添加类型声明
// types/jquery-plugin/index.d.ts
declare namespace JQuery {
interface CustomOptions {
bar: string;
}
}
拓展原有模块
如果是需要扩展原有模块的话,需要在类型声明文件中先引用原有模块,再使用 declare module 扩展原有模块
旧的补充
// global.d.ts
// axios的实例类型
import { AxiosInstance } from 'axios'
// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module '@vue/runtime-core' {
// 给`this.$http`提供类型
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
新的导出
- 导出增加"aaa"变量, 是string类型.
- 类的实例增加"bbb"属性, 是number类型.
- 类增加静态属性"ccc", 是个函数.
// global.d.ts
// AnyTouch一定要导入, 因为只有导入才是扩充, 不导入就会变成覆盖.
import AnyTouch from 'any-touch'
declare module 'any-touch' {
// 导出增加"aaa"变量, 是个字符串.
export const aaa: string;
export default class {
// 类增加静态属性"ccc", 是个函数.
static ccc:()=>void
// 类的实例增加"bbb"属性, 是number类型.
bbb: number
}
}
外部模块简写:假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。
declare module "hot-new-module";
对非ts/js文件模块进行类型扩充
ts只支持模块的导入导出, 但是有些时候你可能需要引入css/html等文件, 这时候就需要用通配符让ts把他们当做模块, 下面是对".vue"文件的导入支持(来自vue官方):
// global.d.ts
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// App.vue
// 可以识别vue文件
import X1 from './X1.vue';
export default defineComponent({
components:{X1}
})
命名空间的原理
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说 module X { 相当于现在推荐的写法 namespace X {)。
还是通过函数来隔离外部环境的
namespace Tools {
const TIMEOUT = 100; // 外部无法访问
export class Ftp {
constructor() {
setTimeout(() => {
console.log('Ftp');
}, TIMEOUT)
}
}
export class Http {
constructor() {
console.log('Http');
}
}
export function parseURL(){
console.log('parseURL');
}
}
// 外部访问
Tools.TIMEOUT // 报错, Tools上没有这个属性
Tools.parseURL() // 'parseURL'
编译成JS
"use strict";
var Tools;
(function (Tools) {
const TIMEOUT = 100;
class Ftp {
constructor() {
setTimeout(() => {
console.log('Ftp');
}, TIMEOUT);
}
}
Tools.Ftp = Ftp;
class Http {
constructor() {
console.log('Http');
}
}
Tools.Http = Http;
function parseURL() {
console.log('parseURL');
}
Tools.parseURL = parseURL;
})(Tools || (Tools = {}));