关于ts中declare和.d.ts的使用
前言
TypeScript作为一门强类型的语言,其中变量的类型,对象的属性都受到了严格的约束,对于属性的读或者写都需要在事先声明的情况下进行操作。
同时,TypeScript最后都会被编译成JavaScript来执行,所以本文的特性都是在不会被编译成JavaScript代码的前提下去使用,也就是ts自己的游戏,和JavaScript无关,这句话后面就可以理解了
在本文中你可以学到declare, namespace, .d.ts的基本用法,如果有错误希望大佬指出
declare定义
declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。
它的主要作用,就是让当前文件可以使用其他文件声明的类型。举例来说,自己的脚本使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用
declare
关键字,告诉编译器外部函数的类型。这样的话,编译单个脚本就不会因为使用了外部类型而报错。
总而言之有两个要点:
- 在当前文件中使用
- 只能定义,而不能有具体实现(例如你可以declare一个变量,一个方法,但你不可以在declare的同时对其赋值,或者定义的操作)
用法:
ini
declare let x:number;
x = 1;
如果 declare 关键字没有给出变量的具体类型,那么变量类型就是
any
。
php
declare function sayHello(
name:string
):void;
sayHello('张三');
.d.ts文件
与ts文件的区别:
其中语句的开头必须是export或者declare
- 其声明的内容在所有ts文件中都可以直接使用,无需import
例如我们声明一个接口后在另一个文件使用,大概是这样的操作:
typescript
// interfaceA.ts
export interface A{
name:string;
}
// main.ts
import {A} from "./interfaceA.ts"
let instanceA:A;
你可能会说,这样也可以啊,挺常规的操作啊,但如果我有几十个文件都用到了这个接口的类型,那我就需要import几十次,这就不太善咯,于是出现了.d.ts文件,.d.ts文件中的内容可以在所有文件中直接使用,并且我们不需要在任何地方对.d.ts文件进行引用,只需要其存在我们ts编译时能检查到的路径内即可,所以我们就可以出现下面的操作
typescript
// interfaceA.d.ts
declare interface A{
name:string;
}
// main.ts
let instanceA:A;
// test1.ts
let instanceA:A;
// test2.ts
let instanceA:A;
// test3.ts
let instanceA:A;
......
这样都不会报错了,这还有什么好处呢,在我们使用一些库,这些库可能并不是用ts编写的,所以我们无法获取到其中的类型声明,那么我们在使用其中属性的时候,编译就会出现报错的情况,假设我们使用JQuery(假设这个库没使用ts编写),那么我们按照如下操作的时候可能会出现错误
javascript
$(".element")
那么我们可以通过类似于安装JQuery的类型包
例如:(应该是这样的操作,之前看另外一篇文章有提到过,明白我的意思就好)
npm install @types/jquery
那么我们就可以这么声明:
typescript
// index.d.ts
import JQuery from "@types/jquery"
declare const $:JQuery;
当多个.d.ts文件变量污染的时候怎么办?
namespace的使用
namespace(命名空间)相当于一个独立的作用域,在其中我们可以正常的编写TypeScript代码,就像我们直接在文件里面编写一样,例如:
csharp
namespace nsA {
const name: string = "nsA";
function sayHello(): void{
console.log("Hello from " + name);
}
}
namespace nsB {
const name: string = "nsB";
function sayHello(): void{
console.log("Hello from " + name);
}
}
但是外部怎么使用呢?
所以我们需要将外部可能用到的命名空间内部的变量export
出来,这样就保证了内部的细节不会被修改,例如:
typescript
namespace nsA {
const name: string = "nsA";
export function sayHello(): void{
console.log("Hello from " + name);
}
}
namespace nsB {
const name: string = "nsB";
export function sayHello(): void {
console.log("Hello from " + name);
}
}
nsA.sayHello(); // Hello from nsA
nsB.sayHello(); // Hello from nsB
同样,namespace也被export出去,在别的文件中通过import引入,然后当作对象使用就好,这里就不作赘述了,可以自行了解namespace的用法。
在.d.ts文件中的namespace
typescript
// index.d.ts
declare namespace nsB {
interface B{
name:string;
}
}
// main.ts
// 无需引入命名空间nsB
const instanceB:nsB.B = {
name:"haha"
}
console.log(instanceB.name); // 输出:haha
注意,其中不能有具体实现,只能有声明
而且在.d.ts中声明的namespace是不需要export的,其默认都会export出来,所以我们可以直接使用
解决办法
所以通过上述解释,相信如何解决.d.ts变量污染的问题就得到解决了,我们只需要通过使用命名空间包裹起来的方式,就可以防止上述问题
回到开始的话(这一段非常重要!!是很多文章都没有提到的点)
还记得我们开头说过,ts的特性大多数都是只能用在ts身上,这是ts自己的游戏吗?
为什么在上述例子我没有像前面在nsB中声明一个sayHello函数,虽然我们在main.ts文件中使用
nsB.sayHello()
也不会报错,但是这是不被允许的,因为在编译成JavaScript代码后,这段代码依然会保留,但是我们并没有定义或者引入已经定义的nsB,nsB是undefined,在严格模式下是不允许访问的,而上述例子,instanceB后面的类型声明是不会被编译成JavaScript代码的,所以可以直接使用
declare的其它用法
我们还可以通过declare module的方式来声明模块,这样在引入一些js库的时候,就不会报相关属性可能不存在的错误,例如:
javascript
// haha.d.ts
declare module haha{
export function xiaoxin():void;
}
// main.d.ts
import {xiaoxin} from 'haha'
但注意,回到最开始的话,这是ts自己的游戏,我们要保证引入的haha
是存在的,只不过其中导出的声明不够清晰而已。
最后的话
花了几个小时才弄懂这个declare,namespace,.d.ts之间的关系,在学ts的过程中,我觉得最难的不是什么语法啥的,更多的是要明白ts所做的许多工作都是为了编译阶段进行的检查,而不是执行时,ts中自己的特性,我们大多数都只能用在ts自己身上,有些代码不会被编译成js文件,例如.d.ts文件,那么我们用到的关于其中的内容,就不应该被编译,不知道能不能理解这句话,但这确实是我最大的感悟了。