不要再像我這樣使用 React 導入了,試試 Wrapper 模式吧!
在之前的實際項目中發現了一種 React.js 中非常低效的導入策略。在本文中,將向你介紹這個問題,以及如何通過 Wrapper(封裝)模式 創建一種更靈活的方式,成功解決這一問題的。
問題所在
在一個項目中,我發現 lodash 和 Framer Motion 這樣的庫被以下方式導入:
import _ from 'lodash'; // 導入了整個 lodash(71.78KB)
import { motion } from 'framer-motion'; // 導入了整個 framer-motion(111.19KB)
這種方式的問題顯而易見:
- 你直接導入了整個庫,導致包的體積激增。
- 如果你的項目總體積是 1MB,那么僅這兩個庫就占了約 18%(~180KB)。
?? 更好的解決方案:直接導入特定方法
要降低包體積,應該具體導入所需方法:
// before:
import _ from 'lodash'; // 71.78KB
// after:
import debounce from 'lodash/debounce'; // 僅 3.41KB
import merge from 'lodash/merge'; // 僅 16KB
但,這樣就夠了嗎?
盡管這種導入方式節省了空間,但在實際開發中可能面臨以下問題:
- 重構成本:如果 lodash 被十幾個文件引用,你就需要修改每個文件中的導入語句,這不僅麻煩而且容易出錯。
- 容易漏掉修改:容易遺漏某些文件,導致代碼不一致或產生難以排查的問題。
- 合并沖突麻煩:當多個分支都修改了相同的導入方式,合并時可能出現繁瑣的沖突。
更靈活的方式:Wrapper 模式(封裝模式)
為了解決這些問題,可以使用一個簡單的封裝文件(Wrapper):
LodashWrapper.tsx:
import debounce from 'lodash/debounce';
const lodashWrapper = {
debounce,
};
export default lodashWrapper;
使用時直接導入封裝文件:
import lodashWrapper from './lodashWrapper';
const SearchInput = () => {
const [query, setQuery] = useState('');
const handleSearch = useCallback(
lodashWrapper.debounce((searchTerm) => {
console.log('Searching for:', searchTerm);
}, 500),
[]
);
return <input onChange={(e) => setQuery(e.target.value)} />;
};
?? 注意:Wrapper 本身并不能減少包的體積(需要具體導入才能減少體積),但能提高代碼的維護性和靈活性。
可視化結果(使用 vite-bundle-visualizer 生成)
圖片
優化后的導入方式:
- 原本大小:71.78KB
- 優化后:僅導入 debounce 為 3.41KB(gzip 壓縮后只有 1.2KB)
?? 為什么總是建議使用 Wrapper?
使用 Wrapper 的優點顯而易見:
- 開發者更容易管理導入:所有人使用預定義的優化方式導入庫。
- 避免冗余導入:統一管理導入方式,避免重復導入相同內容。
- 維護簡單:如果某個庫的導入方式改變,只需要修改封裝文件即可。
但也有一些缺點:
- 增加了抽象層:可能會略微增加代碼復雜性。
- 增加了文件數量:封裝多個庫可能導致項目中出現大量額外的封裝文件。
如何選擇合適的庫?
在選擇第三方庫時,也需要注意它們是否支持單獨導入:
- 比如 Recharts,目前不支持單獨導入,會導入整個庫。
import { BarChart } from 'recharts'; // 導入整個庫
- 而 Nivo 則支持樹搖(tree-shake)導入,可以只導入特定圖表組件。
import { ResponsiveBar } from '@nivo/bar'; // 只導入特定組件
這種差別,會極大影響應用的性能表現與最終打包體積。
如何快速查看導入大小?
推薦在 VS Code 中使用 Import Cost 插件,可以直觀看到每個導入語句帶來的體積增加。
優化后的 vite 配置文件
分享我的 vite.config.ts,內置了壓縮、手動分塊(manual chunk splitting)、路徑別名(alias)等優化方式:
// vite.config.ts 示例
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('lodash')) return 'lodash';
if (id.includes('framer-motion')) return 'framer-motion';
return 'vendor';
}
},
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
components: path.resolve(__dirname, './src/components'),
},
},
});
總結(Key Takeaways)
- 不要直接導入整個庫,使用具體方法導入減小體積。
- 使用 Wrapper 封裝庫,可以提高代碼維護性、降低修改成本。
- 選擇支持具體導入(樹搖優化)的庫,確保應用性能。
- 使用 VS Code 插件監控導入體積,及時優化。