我在Typescript方面遇到了奇怪的问题,并导入了moment
包。我已经在几个不同的地方看到过它,甚至根据我是否向类中添加了特定的静态方法,甚至在同一文件中看到了它。
问题是这样的:
当我使用import * as moment from 'moment';
导入时刻时,没有出现Typescript错误,但是测试确实出现了问题。具体来说,它们以TypeError: moment is not a function
失败。
当我更改导入import moment from 'moment';
时,代码有效,但我得到了"moment" has no default export
。
我不明白它们之间的区别。我看过其他问题(例如this one,它建议使用第一种语法)。对上面问题的公认答案的评论建议打开allowSyntheticDefaultImports
标志,确实可以纠正该问题,但是我有点担心该标志。 The documentation说:
允许从默认导入模块,而没有默认导出。这不会影响代码的发出,只影响类型检查。
这似乎意味着对于以这种方式导入的模块不进行类型检查。
因此,有两个问题:
allowSyntheticDefaultImports
标志的TS文档?是否关闭moment
程序包的类型检查?FWIW,我正在使用Typescript 3.4.5和2.24.0。
答案 0 :(得分:1)
allowSyntheticDefaultImports
,这不是您想要的。您需要esModuleInterop
。 TL; DR打开esModuleInterop
并将import * as foo from 'foo'
的每个实例替换为import foo from 'foo'
。
问题源于Node的require
太灵活的事实。您可以导出几乎所有内容。由于超出该问题范围的原因,这使得优化结果代码非常困难。
ES定义模块时,它们定义的结构稍微有些结构化。您必须导出一个普通对象。该对象可以具有名为default
的属性,但不需要。
不幸的是,许多JS软件包没有遵循此约定。如果我们看moment
...
> const moment = require('moment');
undefined
> typeof moment
'function'
> moment.default
undefined
您会看到moment
导出了一个函数,并且它没有名为default
的属性。对于TS来说,这是一个难题,其目的是坚持ES标准,但希望支持导入不遵循该标准的老式模块。
如果要编译到较旧的目标,则第一次尝试(也是默认行为)是破坏ES标准,并将import * as foo from 'foo'
替换为const foo = require('foo');
。当时的想法是没有人真正需要import * as foo from 'foo'
,因此这是一种劫持导入样式以破解支持较旧模块的方法。不过,它不符合ES6,因为import * as foo from 'foo'
的返回值应始终是一个对象,如果将其替换为require
,则可能无法获得对象。
不幸的是,这导致您的代码根据目标对象和构建方式的不同而表现不同。如果您将ES5作为目标,那么它将退回到require
的行为,并且您将得到function
的反馈。如果您以ES6为目标(使用某种捆绑器时经常会发生这种情况),那么它将放入import * as foo from 'foo'
中,捆绑器可能会对此做出不同的解释。他们通常要做的是为您提供一个具有名为default
的单个属性的对象。
因此,解决方案是劫持import moment from 'moment'
语法。在ES6中,这是导入任何导出内容的default
属性。令人高兴的是,这意味着导入的东西不必是对象,也可以是函数。因此,TS不仅仅是插入const moment = require('moment')
,而且做了一些不同的事情。它创建一个具有名为default
的单个属性的对象,并将其设置为等于require('moment')
,然后将其返回。
解决同一问题实际上只是一个不同的技巧,但它遵循ES6规范,其行为与Babel的行为更为相似。或者,引用此功能的TS release notes:
注意:新行为将添加到标志下,以避免不必要的行为 破坏现有的代码库。我们强烈建议将两者都应用于 新的和现有的项目。对于现有项目,名称空间导入 (从* express中导入*为express; express();)必须为 转换为默认导入(从“ express”导入快递; express();)。