使用ES2015模塊,您能夠將應用程序代碼分紅可重用的、封裝的、專一於單一任務的模塊。javascript
這很好,可是如何構造模塊呢?一個模塊應該有多少個函數和類?java
這篇文章介紹了有關如何更好地組織JavaScript模塊的4種最佳實踐。git
當我開始使用JavaScript模塊時,我使用默認的語法來導出模塊定義的單個塊,不論是類仍是函數。github
例如,這是一個將模塊 Greeter
導出爲默認值的模塊程序:瀏覽器
// greeter.js export default class Greeter { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } }
隨着時間的推移,我注意到了重構默認導出的類(或函數)的困難。在重命名原始類時,使用者模塊中的類名沒有改變。性能優化
更糟糕的是,編輯器沒有提供有關要導入的類名的自動完成建議。babel
個人結論是,默認的導出並無帶來明顯的好處。而後我轉向了命名導出。編輯器
讓咱們將 Greeter
命名爲出口,而後看看好處:函數
// greeter.js export class Greeter { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } }
使用命名導出,編輯器能夠更好地進行重命名:每次更改原始類名時,全部使用者模塊也會更改類名。性能
自動完成功能還會建議導入的類:
因此,這是個人建議:
"支持命名模塊導出,以受益於重命名重構和代碼自動完成功能。"
注意:使用 React,Lodash 等第三方模塊時,默認導入一般是能夠的。默認的導入名稱是一個不變的常量:React
,_
。
模塊級別範圍定義了函數、類、對象和變量。該模塊能夠導出其中一些組件。就這樣。
// Module-level scope export function myFunction() { // myFunction Scope }
模塊級範圍不該該進行繁重的計算,好比解析JSON、發出HTTP請求、讀取本地存儲等等。
例如,下面的模塊配置解析來自全局變量bigJsonString
的配置:
// configuration.js export const configuration = { // Bad data: JSON.parse(bigJsonString) };
這是一個問題,由於bigJsonString
的解析是在模塊級範圍內完成的。bigJsonString
的解析其實是在導入配置模塊時發生的:
// Bad: 導入模塊時進行解析 import { configuration } from 'configuration'; export function AboutUs() { return <p>{configuration.data.siteName}</p>; }
在更高的級別上,模塊級範圍的做用是定義模塊組件、導入依賴項和導出公共組件:這是依賴項解析過程。它應該與運行時分離:解析JSON、發出請求、處理事件。
讓咱們重構配置模塊來執行延遲解析:
// configuration.js let parsedData = null; export const configuration = { // Good get data() { if (parsedData === null) { parsedData = JSON.parse(bigJsonString); } return parsedData; } };
由於data
屬性被定義爲一個getter
,因此只有在使用者訪問configuration.data
時才解析bigJsonString
。
// Good: 導入模塊時不進行JSON解析 import { configuration } from 'configuration'; export function AboutUs() { // 調用時才進行JSON解析 return <p>{configuration.data.companyDescription}</p>; }
消費者更清楚何時進行大的操做,使用者可能決定在瀏覽器空閒時執行該操做。或者,使用者可能會導入模塊,可是出於某種緣由不使用它。
這爲更深層的性能優化提供了機會:減小交互時間,最大程度地減小主線程工做。
導入時,模塊不該該執行任何繁重的工做。相反,使用者應該決定什麼時候執行運行時操做。
內聚性描述了模塊內部各個組件在一塊兒的程度。
高內聚模塊的函數、類或變量是密切相關的。他們專一於單個任務。
formatDate
模塊具備很高的內聚性,由於它的功能密切相關,而且側重於日期格式化:
// formatDate.js const MONTHS = [ 'January', 'February', 'March','April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; function ensureDateInstance(date) { if (typeof date === 'string') { return new Date(date); } return date; } export function formatDate(date) { date = ensureDateInstance(date); const monthName = MONTHS[date.getMonth())]; return `${monthName} ${date.getDate()}, ${date.getFullYear()}`; }
formatDate()
,ensureDateInstance()
和MONTHS
彼此密切相關。
刪除MONTHS
或ensureDateInstance()
會破壞formatDate()
:這是高內聚的標誌。
我發現很難理解一個模塊的路徑包含一個,甚至更多的父文件夾:
import { compareDates } from '../../date/compare'; import { formatDate } from '../../date/format'; // Use compareDates and formatDate
而有一個父選擇器../
一般不是問題,擁有2個或更多一般很難掌握。
這就是爲何我建議避免使用父文件夾,而使用絕對路徑:
import { compareDates } from 'utils/date/compare'; import { formatDate } from 'utils/date/format'; // Use compareDates and formatDate
儘管有時寫入絕對路徑的時間更長,可是使用絕對路徑可使導入的模塊的位置清晰明瞭。
爲了減小冗長的絕對路徑,能夠引入新的根目錄。例如,這可使用babel-plugin-module-resolver實現。
使用絕對路徑而不是較長的相對路徑。
JavaScript模塊很是適合將您的應用程序邏輯拆分爲多個獨立的小塊。
經過使用命名的導出而不是默認的導出,能夠在導入命名組件時更輕鬆地重命名重構和編輯器自動完成幫助。
使用 import {myFunc} from 'myModule'
的惟一目的就是導入myFunc組件,僅此而已。myModule
的模塊級範圍應該只定義包含少許內容的類、函數或變量。
一個組件應該有多少個函數或類,這些組件應該如何與每一個組件相關聯?支持高內聚的模塊:它的組件應該緊密相關並執行一個共同的任務。
包含許多父文件夾../
的長相對路徑很難理解。將它們重構爲絕對路徑。
你使用哪些JavaScript模塊最佳作法?
原文:https://dmitripavlutin.com/ja...
做者:Dmitri Pavlutin
譯者:作工程師不作碼農
關注公衆號,第一時間接收最新文章。若是對你有一點點幫助,能夠點喜歡點贊點收藏,還能夠小額打賞做者,以鼓勵做者寫出更多更好的文章。