深入了解 Export 和 import
深入了解 export 和 import
Export和import指令有几种语法变体。
在上一篇文章中,我们看到了一个简单的用法,现在让我们探讨更多的示例。
export 之前申明
我们可以将任何声明(无论是变量、函数还是类)置于export之前,从而将其标记为导出。
例如,这里所有的导出都是有效的:
// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
export class User {
constructor(name) {
this.name = name;
}
}
请注意,在类或函数之前导出并不会使其成为函数表达式。它仍然是一个函数声明,尽管已经导出。
大多数JavaScript风格指南不建议在函数和类声明后使用分号。
这就是为什么在导出类和导出函数的结尾不需要分号的原因:
export function sayHi(user) {
alert(`Hello, ${user}!`);
} // no ; at the end
除声明外的 export
此外,我们可以把出口分开。
这里我们首先声明,然后导出:
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // a list of exported variables
Import *
通常,我们把要导入的内容放在花括号import{…},像这样:
// 📁 main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
但如果有很多东西需要导入,我们可以使用import * as 将所有东西作为对象导入,例如:
// 📁 main.js
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
乍一看,“导入所有内容”似乎是一件很酷的事情,简单说来,为什么我们要明确地列出我们需要导入的内容呢?
嗯,有几个原因。
现代构建工具(webpack和其他工具)将模块捆绑在一起,并对它们进行优化,以加速加载和删除未使用的内容。
比如说,我们在我们的项目中添加了一个带有许多函数的第三方库say.js:
// 📁 say.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
现在,如果我们在项目中只使用say.js中的一个函数:
// 📁 main.js
import {sayHi} from './say.js';
然后优化器将看到这一点,并从捆绑代码中删除其他函数,从而使构建更小。这被称为“tree-shaking”。
显式列出要导入的内容会给出更短的名称:sayHi()而不是say.sayHi()。
显式的导入列表可以更好地概述代码结构:使用了什么和在哪里使用。它使代码支持和重构更容易。
Import “as”
我们还可以使用as在不同的名称下导入。
例如,为了简洁起见,让我们将sayHi导入到本地变量hi中,并将sayBye作为bye导入:
// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Hello, John!
bye('John'); // Bye, John!
Export “as”
导出也有类似的语法。
让我们导出hi和bye函数:
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
hi和bye是对外来者的正式称呼,用于进口产品:
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
Export default
在实践中,主要有两种模块。
包含库和函数包的模块,如上面的say.js
。
声明单个实体的模块,例如,模块User. js
只导出类User
。
大多数情况下,第二种方法是首选的,这样每个“东西”都驻留在自己的模块中。
当然,这需要很多文件,因为所有东西都需要自己的模块,但这根本不是问题。实际上,如果文件的名称很好,并且被结构化到文件夹中,代码导航就会变得更容易。
模块提供了一个特殊的export default (" the default export ")
语法,使"每个模块一件事"的方式看起来更好。
将export default
放在要导出的实体之前:
// 📁 user.js
export default class User { // just add "default"
constructor(name) {
this.name = name;
}
}
每个文件可能只有一个默认的导出。
然后不带大括号导入:
// 📁 main.js
import User from './user.js'; // not {User}, just User
new User('John');
不带大括号的导入看起来更好。开始使用模块时的一个常见错误是完全忘记花括号。记住,import对命名的导出需要花括号而默认的导出不需要花括号。
Named export Default export
export class User {...} export default class User {...}
import {User} from ... import User from ...
从技术上讲,我们可能在一个模块中同时有默认导出和命名导出,但在实践中,人们通常不会将它们混合在一起。模块有命名的exports或默认的exports。
由于每个文件最多可能有一个默认导出,因此导出的实体可能没有名称。
例如,这些都是完全有效的默认导出:
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export class { // Error! (non-default export needs a name)
constructor() {}
}
The “default” name
在某些情况下,default关键字用于引用默认的导出。
例如,将一个函数与它的定义分开导出:
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// same as if we added "export default" before the function
export {sayHi as default};
或者,另一种情况,让我们说一个模块user.js导出了一个主要的“默认”东西,以及一些命名的东西(很少有这种情况,但它发生了):
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
下面是如何导入一个指定的默认导出:
// 📁 main.js
import {default as User, sayHi} from './user.js';
new User('John');
最后,如果将所有东西*作为对象导入,那么默认属性就是默认的export:
// 📁 main.js
import * as user from './user.js';
let User = user.default; // the default export
new User('John');
A word against default exports
命名导出是显式的。它们精确地命名了它们导入的内容,所以我们从它们那里得到了这些信息;这是件好事。
命名导出强制我们在导入时使用正确的名称:
import {User} from './user.js';
// import {MyUser} won't work, the name must be {User}
而对于默认导出,我们总是在导入时选择名称:
import User from './user.js'; // works
import MyUser from './user.js'; // works too
// could be import Anything... and it'll still work
因此,团队成员可能会使用不同的名称来导入相同的东西,这不是很好。
通常,为了避免这种情况并保持代码的一致性,有一个规则,即导入的变量应该对应于文件名,
例如:
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
Re-export
" Re-export "语法从…允许导入并立即导出(可能是另一个名称),像这样:
export {sayHi} from './say.js'; // re-export sayHi
export {default as User} from './user.js'; // re-export default
为什么需要这样做?让我们来看一个实际的用例。
想象一下,我们正在编写一个“包”:一个包含大量模块的文件夹,其中一些功能被导出到外部(像NPM这样的工具允许我们发布和分发这些包,但我们不必使用它们),而许多模块只是“助手”,供其他包模块内部使用。
文件结构可能是这样的:
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
我们希望通过单个入口点公开包的功能。
换句话说,想要使用我们的包的人,应该只从“主文件”auth/index.js导入。
是这样的:
import {login, logout} from 'auth/index.js'
“主文件”auth/index.js导出了我们想在包中提供的所有功能。
其思想是,外部的人,也就是使用我们包的其他程序员,不应该干涉它的内部结构,搜索我们包文件夹中的文件。我们只导出auth/index.js中必要的部分,其余部分则不被窥探。
由于实际导出的功能分散在包中,我们可以将其导入auth/index.js并从中导出:
// 📁 auth/index.js
// import login/logout and immediately export them
import {login, logout} from './helpers.js';
export {login, logout};
// import default as User and export it
import User from './user.js';
export {User};
...
现在我们包的用户可以从“auth/index.js”中导入{login}。
语法export…从…只是这种进出口的缩写:
// 📁 auth/index.js
// re-export login/logout
export {login, logout} from './helpers.js';
// re-export the default export as User
export {default as User} from './user.js';
...
出口的显著差异是……与导入/导出相比,重新导出的模块在当前文件中不可用。所以在上面的auth/index.js示例中,我们不能使用重新导出的登录/注销函数。
Re-exporting the default export
155/5000 重新导出时,默认导出需要单独处理。
假设我们有User .js和导出默认类User,并想重新导出它:
// 📁 user.js
export default class User {
// ...
}
export * from './user.js'; // to re-export named exports
export {default} from './user.js'; // to re-export the default export
这种重新导出默认导出的奇怪现象是一些开发人员不喜欢默认导出而更喜欢命名导出的原因之一