import * as React from 'react'; 与 import React from 'react'; 的区别在哪里?

125

我注意到 React 可以这样导入:

import * as React from 'react';

...或者像这样:

import React from 'react';

第一个使用 react 模块的全部内容进行导入(参见:导入整个模块的所有内容)。

第二个只导入了default模块导出(参见:导入默认值)。


这两种方法似乎是不同的,基本上是不兼容的。

为什么它们都能工作?


请参考源代码并解释机制...我对了解这是如何工作感兴趣。


更新

这不是“import * as react from 'react' vs import react from 'react'”问题的重复

那个问题是用通用 ES6 模块信息回答的。

我正在询问使 react 模块像这样工作的机制。它似乎与“hacky”导出机制在这里的源代码有关,但不清楚它是如何使导入整个模块和仅导入默认导出到 React 都能够与转换 JSX 等一起工作。



1
你问:“为什么它们都能工作?”而我问你:“为什么它们不能工作呢?” - Vencovsky
如果仅导入默认设置和所有内容会给您相同的结果,那么您认为“所有内容”包括什么?您是否查看了您正在导入的源代码? - Kevin B
我猜你是指包括 default,所以应该是:import * as React from 'react'; - Alex
3
在 TypeScript 中,您可以在 tsconfig.json 中指定 allowSyntheticDefaultImports。这将允许您使用 import React from 'react'。JavaScript / Babel 在这里“作弊”,即使实际上不存在合成默认导入,也会让您这样做。React 导出的正确语法应该是 import * as React from 'react' - John Ruddell
1
@KevinB 我已经查看了源代码,似乎与这个“hacky”行有关,但我不清楚它是如何工作的。 - Brian Adams
显示剩余3条评论
2个回答

79

简述

事实上,ES的import语句中,import defaultimport *并不相同。它们在这种情况下表现相似是React作者选择发布库以及TypeScript(使用esModuleInterop)或Babel和您的打包工具使用兼容层,使它们“正常工作”的组合结果。根据ES6规范,它可能不应该起作用,但今天我们仍然处于JS模块一团糟的时代,因此像Babel、TypeScript、Webpack等工具尝试规范化其行为。

更多细节:

React不是一个ES6库。如果你看一下源代码,你会在index.js中看到这个:

const React = require('./src/React');

// TODO: decide on the top-level export form.
// This is hacky but makes it work with both Rollup and Jest.
module.exports = React.default || React;

(注意注释,即使在React源代码中,他们也在努力解决ES6默认导出兼容性问题。)

module.exports =语法是CommonJS(NodeJS)的语法,浏览器不支持。这就是为什么我们使用像Webpack、Rollup或Parcel这样的打包工具的原因。它们可以理解各种模块语法并生成应在浏览器中正常工作的打包文件。

尽管React不是ES库,但TypeScript和Babel都可以让你像导入ES库一样导入它(使用import语法而非require()等),但CJS和ES之间存在差异必须加以解决。其中一个问题是export = 可能会给你一些ES没有规范兼容的方式导入的东西,例如函数或类作为模块。为了解决这些不兼容性,Babel允许你将CJS模块导入为默认导出项,并且可以作为命名空间导入已有一段时间。TypeScript过去没有这样做,但最近添加了一个选项esModuleInterop。所以现在Babel和TypeScript都可以通过默认或命名空间ES导入相当一致地导入CJS模块。

对于TypeScript还要提到的是,它还取决于库的类型定义如何实际定义。我不会深入讲解,但你可以想象情况,借助转译器和打包工具某个导入在运行时可用,但TypeScript编译时出现错误。

值得一提的另一件事是,如果你查看React的构建代码,你会发现有一个UMD模块版本以及CJS版本。UMD版本包括一些令人头疼的运行时代码,尝试使其在任何模块环境中(包括浏览器)正常工作。主要用于在运行时仅包含React的情况下使用(即你不使用打包工具)。示例

令人困惑吗?是啊,我也这么认为。


3
这里提到的代码块已不在 index.js 文件中。@Aaron,你能否提供更新的信息? - Joe Lloyd
1
代码在"将React导出为命名导出而不是CommonJS"拉取请求中进行了更改。 - c0m1t

36
您可能已经在您的 tsconfig.json 文件中设置了 "allowSyntheticDefaultImports": true,这实际上使编译器不会警告它认为无效的默认导入。TypeScript 添加了 esModuleInterop,它基本上与 babel 在模块加载方面做的一样。
这允许您使用 ES6 默认导入,即使您要导入的源代码没有任何默认导出。
当涉及到这个问题时,TypeScript 是严格的(遵循规则),这就是为什么他们要求您使用 import * as React from 'react' 或者在其基础配置中告诉它允许合成默认导入。
更多相关信息请参考:这里

1
有趣的是,这可能可以解释TypeScript的问题,但它同样适用于JavaScript,所以肯定还有其他因素在起作用。你在上面的评论中提到了 Babel ... Babel JSX转译器会做一些巧妙的事情吗? - Brian Adams
1
@brian-lives-outdoors 是的,Babel 基本上默认就是这样做的。我相信它在这里。此外,这是一篇非常好的文章 - John Ruddell
3
实际上是 esModuleInterop 使这个工作起来。当处理模块时,Babel 会执行类似的操作。根据 ES6 规范,模块有点混乱,import defaultimport * 不应该是同一件事,但通常在转译器和捆绑器中它们是可以互换的,以避免混淆或不兼容性。 - Aaron Beall
2
优秀的文章 约翰,谢谢。在我接受之前,我会先阅读一下这篇文章,但看起来你和@Aaron是正确的...是转译器使其工作。 - Brian Adams
1
@Aaron 是的,我提到了。在 esModuleInterop 出现之前,allowSyntheticDefaultImports 是解决方案。 - John Ruddell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接