編寫高品質JavaScript模組的4個最佳實踐

使用ES2015模組,您可以將應用程式程式碼分成可重用的、封裝的、專註於單一任務的模組。

這很好,但是如何構造模組呢?一個模組應該有多少個函數和類?

這篇文章介紹了有關如何更好地組織JavaScript模組的4種最佳實踐。

1.優先使用命名導出

當我開始使用JavaScript模組時,我使用默認的語法來導出模組定義的單個塊,不管是類還是函數。

例如,這是一個將模組 Greeter 導出為默認值的模組程式:

// greeter.js  export default class Greeter {    constructor(name) {      this.name = name;    }      greet() {      return `Hello, ${this.name}!`;    }  }

隨著時間的推移,我注意到了重構默認導出的類(或函數)的困難。在重命名原始類時,使用者模組中的類名沒有改變。

更糟糕的是,編輯器沒有提供有關要導入的類名的自動完成建議。

我的結論是,默認的導出並沒有帶來明顯的好處。然後我轉向了命名導出

讓我們將 Greeter 命名為出口,然後看看好處:

// greeter.js  export class Greeter {    constructor(name) {      this.name = name;    }      greet() {      return `Hello, ${this.name}!`;    }  }

使用命名導出,編輯器可以更好地進行重命名:每次更改原始類名時,所有使用者模組也會更改類名。

自動完成功能還會建議導入的類:

JavaScript Named Import Autocomplete

所以,這是我的建議:

「支援命名模組導出,以受益於重命名重構和程式碼自動完成功能。」

注意:使用 React,Lodash 等第三方模組時,默認導入通常是可以的。默認的導入名稱是一個不變的常量:React_

2.導入期間不進行繁重的計算工作

模組級別範圍定義了函數、類、對象和變數。該模組可以導出其中一些組件。就這樣。

// Module-level scope    export function myFunction() {    // myFunction Scope  }

模組級範圍不應該進行繁重的計算,比如解析JSON、發出HTTP請求、讀取本地存儲等等。

例如,下面的模組配置解析來自全局變數bigJsonString的配置:

// configuration.js  export const configuration = {    // Bad    data: JSON.parse(bigJsonString)  };

這是一個問題,因為bigJsonString的解析是在模組級範圍內完成的。bigJsonString的解析實際上是在導入配置模組時發生的:

// Bad: parsing happens when the module is imported  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 parsing doesn't happen when the module is imported  import { configuration } from 'configuration';    export function AboutUs() {    // JSON parsing happens now    return <p>{configuration.data.companyDescription}</p>;  }

消費者更清楚什麼時候進行大的操作,使用者可能決定在瀏覽器空閑時執行該操作。或者,使用者可能會導入模組,但是出於某種原因不使用它。

這為更深層的性能優化提供了機會:減少交互時間,最大程度地減少主執行緒工作。

導入時,模組不應該執行任何繁重的工作。相反,使用者應該決定何時執行運行時操作。

3.儘可能的使用高內聚模組

內聚性描述了模組內部各個組件在一起的程度。

高內聚模組的函數、類或變數是密切相關的。他們專註於單個任務。

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彼此密切相關。

刪除MONTHSensureDateInstance()會破壞formatDate():這是高內聚的標誌。

4.避免較長的相對路徑

我發現很難理解一個模組的路徑包含一個,甚至更多的父文件夾:

import { compareDates } from '../../date/compare';  import { formatDate }   from '../../date/format';    // Use compareDates and formatDate

而有一個父選擇器../通常不是問題,擁有2個或更多通常很難掌握。

這就是為什麼我建議避免使用父文件夾,而使用絕對路徑:

儘管有時寫入絕對路徑的時間更長,但是使用絕對路徑可以使導入的模組的位置清晰明了。

為了減少冗長的絕對路徑,可以引入新的根目錄。例如,這可以使用babel-plugin-module-resolver實現。

使用絕對路徑而不是較長的相對路徑。

5.結論

JavaScript模組非常適合將您的應用程式邏輯拆分為多個獨立的小塊。

通過使用命名的導出而不是默認的導出,可以在導入命名組件時更輕鬆地重命名重構和編輯器自動完成幫助。

使用 import {myFunc} from 'myModule' 的唯一目的就是導入myFunc組件,僅此而已。myModule的模組級範圍應該只定義包含少量內容的類、函數或變數。

一個組件應該有多少個函數或類,這些組件應該如何與每個組件相關聯?支援高內聚的模組:它的組件應該緊密相關並執行一個共同的任務。

包含許多父文件夾../的長相對路徑很難理解。將它們重構為絕對路徑。

你使用哪些JavaScript模組最佳做法?


原文:https://dmitripavlutin.com/javascript-modules-best-practices/ 作者:Dmitri Pavlutin 譯者:做工程師不做碼農

全文完。