javascript
JavaScript 和 typeScript 中的 import、from
From:https://segmentfault.com/a/1190000018249137?utm_source=tag-newest
- Github - allowSyntheticDefaultImports should be the default?
- exports、module.exports和export、export deault到底是咋回事
JavaScript 中有多種 export 的方式,而 TypeScript 中針對(duì)這種情況做了多種 import 語(yǔ)法,最常見的就是 import * as path from 'path' 這種。下面來(lái)講解 TypeScript 中不同的 import 具有什么意義。
1、從 export 說(shuō)起
關(guān)于 TypeScript 中不同?import?的含義,最典型的就是下面的?import?語(yǔ)法:
import * as path from 'path'不少人疑問(wèn)這句代碼究竟是什么意思,這里要先從 js 的?export?開始說(shuō)。
首先,JavaScript 的模塊化方案,在歷史的演進(jìn)中,有多種導(dǎo)出模塊的方式:
- exports
- module.exports
- export
- export default
在 nodejs 中內(nèi)置的模塊遵循的都是?CommonJS?規(guī)范,語(yǔ)法為?module.exports?和?exports。
// 模塊中的 exports 變量指向 module.exports // 這篇文章不會(huì)深入講解 module.exports 和 exports 的關(guān)系module.exports = function () { } exports.site = 'https://tasaid.com' module.exports.name = 'linkFly'例如?nodejs?內(nèi)置的?events?模塊的源碼:
在?ECMAScript 6?中又新增了語(yǔ)法?export?和?export default:
export default function () { } export const site = 'https://tasaid.com' export const name = 'linkFly'到這里畫風(fēng)還比較正常,而大名鼎鼎的 JavaScript 轉(zhuǎn)碼編譯器 babel 針對(duì) ECMAScript 6 新增的?export default?語(yǔ)法,搞了個(gè)?babel-plugin-transform-es2015-modules-commonjs?的轉(zhuǎn)換插件,用于將 ECMAScript 6 轉(zhuǎn)碼為 CommonJs 規(guī)范的語(yǔ)法:
源碼:export default 42;
編譯后:
Object.defineProperty(exports, "__esModule", {value: true });exports.default = 42;到這里,我們看到有三種?export?默認(rèn)值的語(yǔ)法:
module.exports = function () {} // commonjs exports.default = function () {} // babel 轉(zhuǎn)碼 export default function () {} // es62、JavaScript 中的 import
:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import
靜態(tài)的import?語(yǔ)句用于導(dǎo)入由另一個(gè)模塊導(dǎo)出的綁定。無(wú)論是否聲明了?strict mode?,導(dǎo)入的模塊都運(yùn)行在嚴(yán)格模式下。在瀏覽器中,import?語(yǔ)句只能在聲明了?type="module"?的?script?的標(biāo)簽中使用。
此外,還有一個(gè)類似函數(shù)的動(dòng)態(tài)?import(),它不需要依賴?type="module"?的script標(biāo)簽。
在?script?標(biāo)簽中使用?nomodule?屬性,可以確保向后兼容。
在您希望按照一定的條件或者按需加載模塊的時(shí)候,動(dòng)態(tài)import()?是非常有用的。而靜態(tài)型的?import?是初始化加載依賴項(xiàng)的最優(yōu)選擇,使用靜態(tài)?import?更容易從代碼靜態(tài)分析工具和?tree shaking?中受益。
語(yǔ)法
import defaultExport from "module-name"; import * as name from "module-name"; import { export } from "module-name"; import { export as alias } from "module-name"; import { export1 , export2 } from "module-name"; import { foo , bar } from "module-name/path/to/specific/un-exported/file"; import { export1 , export2 as alias2 , [...] } from "module-name"; import defaultExport, { export [ , [...] ] } from "module-name"; import defaultExport, * as name from "module-name"; import "module-name";var promise = import("module-name"); //這是一個(gè)處于第三階段的提案。- defaultExport? ? 導(dǎo)入模塊的默認(rèn)導(dǎo)出接口的引用名。
- module-name? ? 要導(dǎo)入的模塊。通常是包含目標(biāo)模塊的.js文件的相對(duì)或絕對(duì)路徑名,可以不包括.js擴(kuò)展名。某些特定的打包工具可能允許或需要使用擴(kuò)展或依賴文件,它會(huì)檢查比對(duì)你的運(yùn)行環(huán)境。只允許單引號(hào)和雙引號(hào)的字符串。
- name? ? 導(dǎo)入模塊對(duì)象整體的別名,在引用導(dǎo)入模塊時(shí),它將作為一個(gè)命名空間來(lái)使用。
- export, exportN? ? 被導(dǎo)入模塊的導(dǎo)出接口的名稱。
- alias, aliasN? ? 將引用指定的導(dǎo)入的名稱。
2.1 描述
name 參數(shù)是 "導(dǎo)入模塊對(duì)象" 的名稱,它將用一種名稱空間來(lái)引用導(dǎo)入模塊的接口。export 參數(shù)指定單個(gè)的命名導(dǎo)出,而 import * as name 語(yǔ)法導(dǎo)入所有導(dǎo)出接口,即導(dǎo)入模塊整體。
導(dǎo)入整個(gè)模塊的內(nèi)容 (?import * as myModule from 'xxx.js' )
這將 myModule 插入當(dāng)前作用域,其中包含來(lái)自位于 /modules/my-module.js 文件中導(dǎo)出的所有接口。
import * as myModule from '/modules/my-module.js';在這里,訪問(wèn)導(dǎo)出接口意味著使用模塊名稱(在本例為 "myModule" )作為命名空間。例如,如果上面導(dǎo)入的模塊包含一個(gè)接口 doAllTheAmazingThings(),你可以這樣調(diào)用:
myModule.doAllTheAmazingThings();導(dǎo)入單個(gè)接口 (?import {myExport} from 'xxx.js'; )
給定一個(gè)名為 myExport 的對(duì)象或值,它已經(jīng)從模塊 my-module 導(dǎo)出(因?yàn)檎麄€(gè)模塊被導(dǎo)出)或顯式地導(dǎo)出(使用?export?語(yǔ)句),將 myExport 插入當(dāng)前作用域。
import {myExport} from '/modules/my-module.js';導(dǎo)入多個(gè)接口 (?import {foo, bar} from 'xxx.js'; )
這將 foo 和 bar 插入當(dāng)前作用域。
import {foo, bar} from '/modules/my-module.js';導(dǎo)入帶有別名的接口 (?import {x?as y} from 'zzz.js'; )
可以在導(dǎo)入時(shí)重命名接口。例如,將 shortName 插入當(dāng)前作用域。
import {reallyLongModuleExportName as shortName} from '/modules/my-module.js';導(dǎo)入時(shí)重命名多個(gè)接口
使用別名導(dǎo)入模塊的多個(gè)接口。
import {reallyReallyLongModuleMemberName as shortName,anotherLongModuleName as short } from '/modules/my-module.js';僅為副作用而導(dǎo)入一個(gè)模塊 (?import 'xxx.js'; )
整個(gè)模塊僅為副作用(中性詞,無(wú)貶義含義)而導(dǎo)入,而不導(dǎo)入模塊中的任何內(nèi)容(接口)。 這將運(yùn)行模塊中的全局代碼, 但實(shí)際上不導(dǎo)入任何值。
import '/modules/my-module.js';導(dǎo)入默認(rèn)值
引入模塊可能有一個(gè) default export(無(wú)論它是對(duì)象,函數(shù),類等)可用。然后可以使用 import 語(yǔ)句來(lái)導(dǎo)入這樣的默認(rèn)接口。最簡(jiǎn)單的用法是直接導(dǎo)入默認(rèn)值:
import myDefault from '/modules/my-module.js';也可以同時(shí)將 default 語(yǔ)法與上述用法(命名空間導(dǎo)入或命名導(dǎo)入)一起使用。在這種情況下,default 導(dǎo)入必須首先聲明。 例如:
import myDefault, * as myModule from '/modules/my-module.js'; // myModule used as a namespace或者
import myDefault, {foo, bar} from '/modules/my-module.js'; // specific, named imports當(dāng)用動(dòng)態(tài)導(dǎo)入的方式導(dǎo)入默認(rèn)導(dǎo)出時(shí),其工作方式有所不同。你需要從返回的對(duì)象中解構(gòu)并重命名 "default" 鍵。
(async () => {if (somethingIsTrue) {const { default: myDefault, foo, bar } = await import('/modules/my-module.js');} })();動(dòng)態(tài) import
標(biāo)準(zhǔn)用法的 import 導(dǎo)入的模塊是靜態(tài)的,會(huì)使所有被導(dǎo)入的模塊,在加載時(shí)就被編譯(無(wú)法做到按需編譯,降低首頁(yè)加載速度)。有些場(chǎng)景中,你可能希望根據(jù)條件導(dǎo)入模塊或者按需導(dǎo)入模塊,這時(shí)你可以使用動(dòng)態(tài)導(dǎo)入代替靜態(tài)導(dǎo)入。下面的是你可能會(huì)需要?jiǎng)討B(tài)導(dǎo)入的場(chǎng)景:
- 當(dāng)靜態(tài)導(dǎo)入的模塊很明顯的降低了代碼的加載速度且被使用的可能性很低,或者并不需要馬上使用它。
- 當(dāng)靜態(tài)導(dǎo)入的模塊很明顯的占用了大量系統(tǒng)內(nèi)存且被使用的可能性很低。
- 當(dāng)被導(dǎo)入的模塊,在加載時(shí)并不存在,需要異步獲取
- 當(dāng)導(dǎo)入模塊的說(shuō)明符,需要?jiǎng)討B(tài)構(gòu)建。(靜態(tài)導(dǎo)入只能使用靜態(tài)說(shuō)明符)
- 當(dāng)被導(dǎo)入的模塊有副作用(這里說(shuō)的副作用,可以理解為模塊中會(huì)直接運(yùn)行的代碼),這些副作用只有在觸發(fā)了某些條件才被需要時(shí)。(原則上來(lái)說(shuō),模塊不能有副作用,但是很多時(shí)候,你無(wú)法控制你所依賴的模塊的內(nèi)容)
請(qǐng)不要濫用動(dòng)態(tài)導(dǎo)入(只有在必要情況下采用)。靜態(tài)框架能更好的初始化依賴,而且更有利于靜態(tài)分析工具和?tree shaking?發(fā)揮作用
關(guān)鍵字 import 可以像調(diào)用函數(shù)一樣來(lái)動(dòng)態(tài)的導(dǎo)入模塊。以這種方式調(diào)用,將返回一個(gè) promise
import('/modules/my-module.js').then((module) => {// Do something with the module.});這種使用方式也支持?await?關(guān)鍵字。
let module = await import('/modules/my-module.js');2.2 示例
標(biāo)準(zhǔn)導(dǎo)入
下面的代碼將會(huì)演示如何從輔助模塊導(dǎo)入以協(xié)助處理 AJAX JSON 請(qǐng)求。
模塊:file.js
function getJSON(url, callback) {let xhr = new XMLHttpRequest();xhr.onload = function () {callback(this.responseText)};xhr.open('GET', url, true);xhr.send(); }export function getUsefulContents(url, callback) {getJSON(url, data => callback(JSON.parse(data))); }主程序:main.js
import { getUsefulContents } from '/modules/file.js';getUsefulContents('http://www.example.com',data => { doSomethingUseful(data); });動(dòng)態(tài)導(dǎo)入
此示例展示了如何基于用戶操作去加載功能模塊到頁(yè)面上,在例子中通過(guò)點(diǎn)擊按鈕,然后會(huì)調(diào)用模塊內(nèi)的函數(shù)。當(dāng)然這不是能實(shí)現(xiàn)這個(gè)功能的唯一方式,import()函數(shù)也可以支持await。
const main = document.querySelector("main"); for (const link of document.querySelectorAll("nav > a")) {link.addEventListener("click", e => {e.preventDefault();import('/modules/my-module.js').then(module => {module.loadPageInto(main);}).catch(err => {main.textContent = err.message;});}); }2.3 規(guī)范
| "function-like" dynamic?import()?proposal |
| ECMAScript (ECMA-262)Imports |
Report problems with this compatibility data on GitHub
3、TypeScript 中的 import
在 TypeScript 中,也有多種?import?的方式。
import * as xx from 'xx' // commonjs 模塊 import xx from 'xx' // es6 模塊 import xx = require('xx') // commonjs 模塊,類型聲明為 export = xx const xx = require('xx') // 沒有類型聲明,默認(rèn)導(dǎo)入 any 類型在?tsconfig.json?中,allowSyntheticDefaultImports?會(huì)影響到 import 語(yǔ)法的類型檢查規(guī)則,這個(gè)下面再說(shuō)。
import * as xx from 'xx'
import * as xx from 'xx'?的語(yǔ)法來(lái)一般都是用來(lái)導(dǎo)入使用?module.exports?導(dǎo)出的模塊。
import * as path from 'path'因?yàn)?nodejs 中的模塊大部分都是通過(guò)?module.exports、exports.xx?語(yǔ)法進(jìn)行導(dǎo)出的。
import xx from 'xx'
默認(rèn)情況下,import xx from 'xx'?的語(yǔ)法只適用于 ECMAScript 6 的?export default?導(dǎo)出:
模塊 foo:
export default function () { console.log('https://tasaid.com') }ES6 模塊的導(dǎo)入:
import foo from './foo' foo()而前面我們說(shuō)了,babel?會(huì)將 es6 的模塊的?export default?語(yǔ)法編譯為?exports.default?語(yǔ)法。
而 TypeScript 默認(rèn)是不識(shí)別這種語(yǔ)法的,如果一個(gè)模塊的導(dǎo)出是?exports.default?導(dǎo)出,如果使用?import xx from 'xx'?的語(yǔ)法導(dǎo)入是會(huì)報(bào)錯(cuò)的。
所以在?tsconfig.json?中,有個(gè)?allowSyntheticDefaultImports?選項(xiàng),就是針對(duì)這種語(yǔ)法做兼容。
如果設(shè)定?allowSyntheticDefaultImports?為?true,則檢測(cè)導(dǎo)入的模塊是否是 ES6 模塊,如果不是,則查找模塊中是否有?exports.default?導(dǎo)出。
從而達(dá)到針對(duì)?exports.default?的兼容。
?
效果參見這個(gè)動(dòng)畫:
allowSyntheticDefaultImports?選項(xiàng)的,一般情況下我采取的方式是將 deafult 重新命名:
import { default as foo } from 'foo'import xx = require('xx')
import xx = require('xx')?是用來(lái)導(dǎo)入 commonjs 模塊的庫(kù),特殊的地方在于這個(gè)庫(kù)的類型聲明是?export = xx?這種方式導(dǎo)出的:
foo.js 源碼:
module.exports = () => { console.log('https://tasaid.com') }foo.d.ts 類型聲明文件源碼:
declare function foo(): void; export = foobar.ts 引用:
import foo = require('./foo')foo()在 《[JavaScript 和 TypeScript 交叉口 —— 類型定義文件(*.d.ts)
](https://tasaid.com/blog/20171...》中講述過(guò) TypeScript 類型聲明文件對(duì)導(dǎo)入導(dǎo)出的影響?
const xx = require('xx')
當(dāng)一個(gè)模塊沒有類型聲明文件的時(shí)候,可以使用 commonjs 原始的?require()?方式來(lái)導(dǎo)入模塊,這樣會(huì)默認(rèn)該模塊為 any。
總? 結(jié)
最后我們整體總結(jié)下,在 TypeScript 中,有多種 import 的方式,分別對(duì)應(yīng)了 JavaScript 中不同的 export。
import * as xx from 'xx' // commonjs 模塊 import xx from 'xx' // 標(biāo)準(zhǔn) es6 模塊 import xx = require('xx') // commonjs 模塊,類型聲明為 export = xx const xx = require('xx') // 沒有類型聲明,默認(rèn)導(dǎo)入 any 類型針對(duì)?babel?編譯出來(lái)的?exports.default?語(yǔ)法,ts 提供了?allowSyntheticDefaultImports?選項(xiàng)可以支持,只不過(guò)個(gè)人不太推薦。
個(gè)人建議將?default?重命名。
import { default as foo } from 'foo'關(guān)于 TypeScript 中類型聲明文件(*.d.ts) 對(duì) import 和 export 的影響,參考 《[JavaScript 和 TypeScript 交叉口 ---?類型定義文件](https://tasaid.com/blog/20171...》。
總結(jié)
以上是生活随笔為你收集整理的JavaScript 和 typeScript 中的 import、from的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux二进制实用工具Binutils
- 下一篇: 网络层知识架构图