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