typescirpt 的一些问答
如何看待d.ts
- 这个是typescript的类型文件,tsc 编译器会产生这个文件,我们也可以手动的编写这个文件,这样可以在不重新编写js的前提下,还能享受到ts带来的好处
- vscode 编译器的智能提示也是依靠这个文件
vscode 的只能提示是如何找到类型信息的
- 首先ts工程下的所有d.ts都自动被检测到,并加载到智能提示的范围。ts工程指的是tsconfig中的include, files所指定的。如果不指定那默认就是./src/xxx
- 第三方包的类型信息来自于哪里
- 包本身自带的。可以参考包本身的package.json中的types配置段。一般情况下d.ts文件跟对应的x.js在同一目录,并且同名。这个好处是得益于,ts的import xx from 'package',这个package是不带后缀的。
- 如果第三方的包并没有提供类型文件怎么办。安装约定,我们可以在node_modules/@types/xxx,在这个目录下面放置包的类型文件。注意,这个目录下面,类型文件必须以包的名称作为文件夹来组织。同时得有index.d.ts,这个是入口文件
- 第三方包的类型文件还可以放到哪里,如何让vscode 找到。我们可以通过tsconfig中的typeRoots这个配置项来配置。它的默认值就是node_modules/@types.同样,所有第三方包的文件都必须采用包名为文件夹来存放。
- 我们注意到,vscode 中有很多类型是全局的,也就是不需要采用import, 就自然有智能提示。这个是如何做到的。
- tsconfig中有个types的配置项,这个里面是包的名称,它其实指向的是node_modules/下面的文件夹。这些指定的文件夹会默认加到全局的类型中。默认是找文件夹下面的index.d.ts
如何看到///<reference path=""/>
,///<reference type=""/>
- 为什么需要它?
这个是用来告诉编译器,在编译当前文件时,先引入这些申明的文件。有人说这个跟c 语言中的头文件类似。 - 哪里可以使用这些文件
d.ts, ts 文件中都可以使用 - path vs type
path 则是标记引用的文件名称
type 则是标记引用的类型名称,这个类型名称,指的是包的名称,与import 同样的解析规则。除此之外,它还会去解析node_modules/@types/下面的文件夹。注意,这里的包名,其实就是文件夹的名称。
typescript 是如何依据类型去寻找js 文件呢。
- 首先ts 假设,d.ts 文件跟js 文件同名且在同一目录。而且ts 中的import,并没有指定后缀名
- 其次,node_modules/@types/下面的同名文件夹跟node_modules下面的同名文件夹也是关联信息。
如何看待tsconfig 中的esModuleInterop咱们代码来说明
// moduleA cjs
'use strict';
const prop='I am a string';
module.exports.prop=prop;
// index.mjs esmodule 的行为
import {prop} from './moduleA.cjs';
import defaultModuleA from './moduleA.cjs';
import * NSA from './moduleA.cjs';
console.log(prop); // 'I am a string'
console.log(defaultModuleA); // {prop:'I am a string'}
console.log(NSA); // {default:{prop:'I am a string'},prop:'I am a string'}
总结一下,在esModule的解析中
- 如果是默认导入,
import xx from ''
,esmodule 会取到commonjs中的module.exports。在上面的例子里也就是defaultModuleA={prop} - 如果是命名导出,
import {xx} from ''
,这个相对于对于默认导出的一个解构赋值。 - 如果是命名空间导出,namespace import,也就是我们提到的
import * as ns from ''
,esmodule 做了两件事情- 第一,默认导出,也就是
ns.default={prop}
, - 第二,它会遍历默认导出里面的属性,将其平铺到ns中,也就是相当于
Object.assign(ns,{prop})
。但是这里有个前提就是typeof(defaultModuleA)==='object',否则会跳过这一步。
- 第一,默认导出,也就是
明白了上面这一步,我们来看看另一个例子
//moduleA.cjs
'use strict';
function FA(){
console.log('Iam functionA');
}
FA.prop='I am prop';
module.exports=FA;
// index.mjs esmodule 的行为
import {prop} from './moduleA.cjs'; // SyntaxError: Named export 'propa' not found. The requested module './moduleA.cjs' is a CommonJS module, which may not support all module.exports as named exports
// 如果导出的是函数,不是纯粹的对象,则不可以解构赋值导入,或者命名导入
import defaultModuleA from './moduleA.cjs';
import * NSA from './moduleA.cjs';
//console.log(prop); // syntax error 针对第一条的import,会报错。
const {prop:otherprop}=defaultModuleA; // 可以通过解构赋值的方式取值。
console.log(otherprop); // I am prop
console.log(defaultModuleA); // [Function: ModuleA] { prop: 'I am prop' }
console.log(NSA); // {default:[Function: ModuleA] { prop: 'I am prop' }} 跟上面例子对比,你会发现命名的导出部分消失了。
- 总结一下,如果cjs导出的也就是module.exports里面的是方法,而不是对象,那么它命名空间下面的其他属性就丢失了,也就是defaultModuleA.prop===undefined,我们只能使用defaultModuleA()了。这样的代码运行时会报错,命名空间的使用就失去了意义,因为属性丢失,只剩下方法本身了。
- 在不打开esmoduleInterop的情况下,ts 会将下面的代码转义成这样
import * as moduleA from 'moduleA';
moduleA();
moduleA.prop;
import * as moduleA from 'moduleA';
import _ from 'moduleA';
moduleA();
moduleA.prop; // 这个会报错。因为这个对象不存在了
_.default();
_.default.prop;
- 由于es6规范中指出,import 导入的namespace必须是一个对象。但是,在这个规范出来之前,typescript 已经默认这样是合法的,并且做了合适的转义,也就是,如果default export 是方法,就直接调用,如果方法里面的还有其他属性,我们也可以使用。所以我们得告诉ts, 让他转义成标准得格式。这就是为什么我们需要打开esmoduleInterop