【JavaScript】編寫高質(zhì)量JavaScript模塊的4個(gè)最佳實(shí)踐
在網(wǎng)上看到一篇不錯(cuò)的文章, 里面的最佳實(shí)踐筆者已經(jīng)在實(shí)際項(xiàng)目中實(shí)踐了很久. 確實(shí)香~分享給大家.
譯文地址[1]
這篇文章提供了關(guān)于如何更好地組織 JavaScript 模塊的 4 個(gè)最佳實(shí)踐。
盡可能使用 named exports
最開始使用 JavaScript 模塊時(shí),使用 export default 來導(dǎo)出模塊定義的單個(gè)塊,不管是類還是函數(shù)。例如:
// greeter.js
export default class Greeter {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
import x from './greeter.js'
隨著時(shí)間的推移,特別是在進(jìn)行大規(guī)模重構(gòu)的時(shí)候, 很難根據(jù) Greeter 去找到相關(guān)的引用.可維護(hù)性大大降低.
更糟糕的是,編輯器沒有提供建議, 我們需要自己寫導(dǎo)入的類名.
于是, 我們轉(zhuǎn)向了 named exports。讓我們來看看它的好處:
// greeter.js
export class Greeter {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
import {Greeter} from './greeter.js'
通過使用 named exports,編輯器可以更好地重命名:每次更改原始類名時(shí),所有使用者模塊都會(huì)自動(dòng)更改類名。
而且編輯器還會(huì)提供建議, 不再盲打代碼, 如下:
因此, 建議使用 named exports,以便在重命名重構(gòu)和代碼自動(dòng)完成中獲益。
注意: 使用 React,Lodash 等第三方模塊時(shí), default import 通常是可以的。因?yàn)樗麄儗?dǎo)入名稱是一個(gè)不變的常量:React,_。
惰性 import 對象
模塊級范圍不應(yīng)該進(jìn)行繁重的計(jì)算,比如解析 JSON、發(fā)出 HTTP 請求、讀取本地存儲(chǔ)等等。
例如,下面的模塊配置解析來自全局變量 bigJsonString 的配置
// configuration.js
export const configuration = {
// Bad
data: JSON.parse(bigJsonString)
};
這是一個(gè)問題,因?yàn)?bigJsonString 的解析是在模塊級范圍內(nèi)完成的。導(dǎo)入配置模塊時(shí),實(shí)際上將進(jìn)行 bigJsonString 的解析:
// Bad: parsing happens when the module is imported
import { configuration } from 'configuration';
export function AboutUs() {
return <p>{configuration.data.siteName}</p>;
}
在更高的級別上,模塊級 scope 的角色是定義模塊組件、導(dǎo)入依賴項(xiàng)和導(dǎo)出公共組件:這是依賴項(xiàng)解析過程。它應(yīng)該與運(yùn)行時(shí)分離:解析 JSON、發(fā)出請求、處理事件。
讓我們重構(gòu)配置模塊來執(zhí)行延遲解析
// configuration.js
let parsedData = null;
export const configuration = {
// Good
get data() {
if (parsedData === null) {
parsedData = JSON.parse(bigJsonString);
}
return parsedData;
}
};
因?yàn)?data 屬性被定義為一個(gè) getter,所以只有在使用者訪問 configuration.data 時(shí)才會(huì)解析 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>;
}
消費(fèi)者更清楚什么時(shí)候進(jìn)行大的操作。使用者可能決定在瀏覽器空閑時(shí)執(zhí)行該操作。或者,使用者可能會(huì)導(dǎo)入模塊,但是出于某種原因,不使用它。這為更深入的性能優(yōu)化提供了機(jī)會(huì):減少交互時(shí)間,最小化主線程工作。
導(dǎo)入時(shí),模塊不應(yīng)執(zhí)行任何繁重的工作。相反,使用者應(yīng)該決定何時(shí)執(zhí)行運(yùn)行時(shí)操作。
編寫高內(nèi)聚模塊
內(nèi)聚性描述了模塊內(nèi)的組件屬于整體。高內(nèi)聚模塊的函數(shù)、類或變量是密切相關(guān)的。他們專注于一項(xiàng)任務(wù)。formatDate 模塊具有很高的內(nèi)聚性,因?yàn)樗墓δ苊芮邢嚓P(guān).
// 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 彼此密切相關(guān)。刪除 MONTHS 或 ensureDateInstance()會(huì)破壞 formatDate():這是高內(nèi)聚的標(biāo)志。
低內(nèi)聚模塊的問題
另一方面,低內(nèi)聚模塊。是指那些包含彼此不相關(guān)的組件。下面的 utils 模塊有 3 個(gè)執(zhí)行不同任務(wù)的函數(shù).
// utils.js
import cookies from 'cookies';
export function getRandomInRange(start, end) {
return start + Math.floor((end - start) * Math.random());
}
export function pluralize(itemName, count) {
return count > 1 ? `${itemName}s` : itemName;
}
export function cookieExists(cookieName) {
const cookiesObject = cookie.parse(document.cookie);
return cookieName in cookiesObject;
}
getRandomInRange(), pluralize() and cookieExists() 刪除任一個(gè)函數(shù), 都不會(huì)影響整個(gè)模塊的功能.
低內(nèi)聚模塊迫使使用者依賴于它并不需要的模塊,這就創(chuàng)建了不需要的傳遞依賴。
例如,組件 ShoppingCartCount 從 utils 模塊導(dǎo)入 pluralize()函數(shù)
// ShoppingCartCount.jsx
import { pluralize } from 'utils';
export function ShoppingCartCount({ count }) {
return (
<div>
Shopping cart has {count} {pluralize('product', count)}
</div>
);
}
雖然 ShoppingCartCount 模塊只使用 utils 模塊之外的 pluralize() 函數(shù),但它對 cookie 模塊具有傳遞依賴關(guān)系(cookie 模塊導(dǎo)入到 utils 中)。
好的解決方案是將低內(nèi)聚性模塊 utils 分成幾個(gè)高內(nèi)聚性模塊:utils / random,utils / stringFormat 和 utils / cookies。
現(xiàn)在,如果 ShoppingCard 模塊導(dǎo)入 utils / stringFormat,那么它就不會(huì)對 Cookie 產(chǎn)生傳遞依賴:
// ShoppingCartCount.jsx
import { pluralize } from 'utils/stringFormat';
export function ShoppingCartCount({ count }) {
// ...
}
高內(nèi)聚模塊的最佳示例是 Node 內(nèi)置模塊,例如 fs,path,assert。
推薦編寫功能、類和變量緊密相關(guān)的高內(nèi)聚模塊。你可以通過將大的低內(nèi)聚模塊重構(gòu)為多個(gè)高內(nèi)聚模塊。
避免長的相對路徑
對于下面的代碼, 我們很難知道它的父級組件, 而且一旦文件發(fā)生變化, 也得一遍一遍地改寫路徑.
import { compareDates } from '../../../date/compare';
import { formatDate } from '../../../date/format';
// Use compareDates and formatDate
因此,我建議避免使用相對路徑,而使用絕對路徑:
import { compareDates } from 'utils/date/compare';
import { formatDate } from 'utils/date/format';
雖然絕對路徑有時(shí)寫起來比較長,但是使用它們可以清楚地顯示導(dǎo)入模塊的位置。我們可以使用 babel-plugin-module-resolver 來方便地定義絕對路徑.
使用絕對路徑而不是長的相對路徑。
參考資料
譯文地址: https://dmitripavlutin.com/javascript-modules-best-practices/
