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
posted @ 2023-11-24 22:24  kongshu  阅读(18)  评论(0编辑  收藏  举报