向滿屏的 Import 語句說再見!
密集的導入語句不僅對視覺造成沖擊,也是對代碼組織結構的一次考驗。
如何優(yōu)雅地管理這些導入語句,避免“全屏占用”?本文將探討生成大量導入語句的原因,可能帶來的問題,以及如何從多個角度優(yōu)化和管理導入語句。
拒絕使用模塊重新導出
模塊重新導出是一種常見技術,廣泛應用于Twitter、字節(jié)跳動和谷歌等大公司的組件庫中。
例如,在字節(jié)跳動的arco-design組件庫中:https://github.com/arco-design/arco-design/blob/main/components/index.tsx
通過在components/index.tsx文件中重新導出所有組件,你可以只用一條導入語句來使用多個組件。
// 不要使用命名導入
import Modal from '@arco-design/web-react/es/Modal'
import Checkbox from '@arco-design/web-react/es/Checkbox'
import Message from '@arco-design/web-react/es/Message'
...
// 使用命名導入
import { Modal, Checkbox, Message } from '@arco-design/web-react'
圖片
重新導出通常用于整合同類型的模塊,通常按文件夾組織,如 components、routes、utils、hooks、stories 等,都通過各自的index.tsx文件進行暴露。這極大簡化了導入路徑,提高了代碼的可讀性和可維護性。
重新導出的幾種形式:
直接重新導出:直接從另一個模塊重新導出特定成員。
export { foo, bar } from './moduleA';
重命名和重新導出(包括默認導出):從另一個模塊導入成員,可能重命名后再導出。默認導出也可以重命名和重新導出。
// 通過export導出
export { foo as newFoo, bar as newBar } from './moduleA';
// 通過export default導出
export { default as ModuleDDefault } from './moduleD';
重新導出整個模塊(不包括默認導出):將另一個模塊的所有導出成員重新導出為一個對象。(注意:重新導出不包括默認導出)
export * from './moduleA';
合并導入和重新導出:先導入模塊中的成員,然后使用它們,最后重新導出它們。
import { foo, bar } from './moduleA';
export { foo, bar };
通過這些形式,我們可以靈活地組織和管理代碼模塊。每種形式都有其適用場景,選擇合適的方式可以幫助我們構建更清晰和高效的代碼結構。
使用 require.context
require.context是一個非常有用的功能,可以讓我們在不顯式地一個個導入的情況下動態(tài)導入一組模塊。
只需一段代碼,當你需要添加文件或組件時,它會自動收集并重新導入。
在固定場景如項目路由和狀態(tài)管理中效果極佳(提高效率,避免添加一個配置需要修改多個文件的情況)。
尤其是在配置路由時,當需要生成大量導入時(你有多少頁面就得導入多少頁面 ??),require.context非常有用。
// 不要使用require.context
import A from '@/pages/A'
import B from '@/pages/B'
...
// 統(tǒng)一處理routes/index.ts文件
// 創(chuàng)建一個上下文來導入routes目錄下的所有.ts文件
const routesContext = require.context('./routes', false, /.ts$/);
const routes = [];
// 遍歷上下文中的每個模塊
routesContext.keys().forEach(modulePath => {
// 獲取模塊的導出
const route = routesContext(modulePath);
// 獲取組件名稱 [如果需要],例如:從"./Header.ts"中提取"Header"
// const routeName = modulePath.replace(/^./(.*).\w+$/, '$1');
// 將組件存儲在組件對象中
routes.push(route.default || route);
});
export default routes;
在擁有多個路由的大型項目中,使用require.context可以很好地處理路由導入。
使用動態(tài)導入
動態(tài)導入也可以實現(xiàn)與require.context類似的功能,動態(tài)打包模塊。
對ProvidePlugin不感興趣
webpack.ProvidePlugin是個好東西,但不應濫用。一旦配置好,項目中使用的變量/函數(shù)/庫或工具可以在任何地方使用。
相信我——看完這個例子,如果你以前沒用過,你會迫不及待地想試試 ??
const webpack = require('webpack');
module.exports = {
// 其他配置...
plugins: [
new webpack.ProvidePlugin({
React: 'react',
_: 'lodash',
dayjs: 'dayjs',
// 假設項目src目錄中的自定義utils.js
Utils: path.resolve(__dirname, 'src/utils.js')
})
]
// 其他配置...
};
在你可以在任何地方使用dayjs、lodash、Utils等,而無需導入它們。
webpack.ProvidePlugin是一個強大的工具,可以幫助我們減少重復的導入語句,使代碼更簡潔。然而,它并不能減少構建大小,因為這些庫仍然會被包含在最終的捆綁文件中。正確使用這個插件可以提高開發(fā)效率,但應謹慎使用,以避免隱藏依賴,導致代碼難以理解和維護。 對于需要按需加載的模塊或組件,考慮使用動態(tài)import()語法,更有效地控制代碼何時加載并減少捆綁大小。 謹慎使用ProvidePlugin,僅對在多個地方需要全局變量配置的模塊使用,避免不必要的代碼捆綁。 此外,如果是Vite項目,你可以使用vite-plugin-inject代替ProvidePlugin功能。
// 配置
import inject from 'vite-plugin-inject'; // 未提供測試,可更新為替代方案
...
plugins: [
inject({
// 鍵是你想提供的全局變量,值是你想提供的模塊
dayjs: 'dayjs', // 例如,這將全局提供'dayjs',可通過dayjs訪問
// 你可以繼續(xù)添加需要全局提供的其他模塊
}),
]
...
如果使用TS,記得配置類型。
// globals.d.ts文件處理全局類型
import dayjs from 'dayjs';
declare global {
const dayjs: typeof dayjs;
}
// 還要配置tsconfig.json文件
{
"compilerOptions": {
// 編譯選項...
},
"include": ["src/**/*", "globals.d.ts" // 確保TypeScript包含此文件]
}
大量的TypeScript類型導入
在TS項目中,屏幕上會有大量的TypeScript導入。然而,通過適當?shù)呐渲茫梢燥@著減少導入數(shù)量。
這里介紹我在項目中最常用的方法:TS命名空間。使用它,不僅可以模塊化類型,更重要的是可以直接使用類型而無需導入它們 ??。
類似于ProvidePlugin,它可以直接消除導入語句。
// accout.ts
declare namespace IAccount {
type IList<T = IItem> = {
count: number
list: T[]
}
interface IUser {
id: number;
name: string;
avatar: string;
}
}
// 直接在任何文件中使用,無需導入。
const [list, setList] = useState<IAccount.IList | undefined>();
const [user, setUser] = useState<IAccount.IUser | undefined>();
注意 ?? 可能需要配置eslint以啟用命名空間的使用 ??
充分利用Babel功能
React似乎也意識到了這個問題:在17版之前,由于JSX的特性,每個組件需要顯式地從'react'導入React。然而之后,編譯器自動轉換,不再需要導入React。如果你使用的是React 17之前的版本,可以通過修改Babel來實現(xiàn)這一點。更多細節(jié)請參考React官方文檔,提供了非常詳細的解釋。(還提供了自動刪除導入的腳本。)
其他技巧
設置webpack和TypeScript別名,可以縮短導入路徑,使其更具語義化。
resolve: {
alias: {
"@src": path.resolve(__dirname, 'src/'),
"@components": path.resolve(__dirname, 'src/components/'),
"@utils": path.resolve(__dirname, 'src/utils/')
}
}
// 使用別名前
import MyComponent from '../../../../components/MyComponent';
// 使用別名后
import MyComponent from '@components/MyComponent';
設置格式化的 prettier.printWidth
將值設置得太小可能會導致頻繁的換行,使其難以閱讀。120是一個更合適的值(基于團隊的實際使用)。
{
"printWidth": 120,
...
}
根據(jù)條件全局動態(tài)加載組件
在入口文件中導入全局組件,使用require.ensure或import根據(jù)條件動態(tài)加載組件,便于維護,減少引用也減少性能開銷。
// 異步加載全局彈窗以減少性能開銷
Vue.component('IMessage', function (resolve) {
// 在指定條件下全局加載,無需在具體頁面中引用。
if (/^\/pagea|pageb/.test(location.pathname)) {
require.ensure(['./components/message/index.vue'], function() {
resolve(require('./components/message/index.vue'));
});
}
});
使用babel-plugin-import
babel-plugin-import并不能直接減少導入數(shù)量,但通過優(yōu)化導入語句來減少包大小并提高項目加載性能。這是對使用大型第三方庫的項目非常有價值的優(yōu)化技術。
以arco-design為例:
// .bablerc配置
{
"plugins": [
["import", {
"libraryName": "@arco-design/web-react",
"libraryDirectory": "es", // 或"lib",取決于使用的具體模塊系統(tǒng)
"style": true // 加載CSS
}, "@arco-design/web-react"]
]
}
此配置告訴babel-plugin-import自動將類似import { Button } from '@arco-design/web-react'; 的導入語句轉換為按需導入,并加載相應的CSS文件。
結尾
有很多原因會導致屏幕充滿導入語句。然而,如果沒有諸如重新導入模塊、require.context、動態(tài)導入、webpack.ProvidePlugin等方法,我們將不得不寫滿屏幕的導入語句 ????????。