【Vuejs】1842- 一文揭秘Vue3組件庫(kù)的優(yōu)雅打包與細(xì)節(jié)
我認(rèn)為,前端工程化更像是個(gè)命題作文,沒有標(biāo)準(zhǔn)答案,能解決問題即可。所以,本文旨在以我解決問題的實(shí)戰(zhàn)經(jīng)過來分享一種組件庫(kù)的打包方式,希望大家都會(huì)有所啟發(fā)、有所收獲!廢話少說,直接開始吧。
現(xiàn)狀&目標(biāo)
看過我文章的同學(xué)應(yīng)該知道很久之前我搞了個(gè)組件庫(kù),還寫了些文章——快上車!從零開始搭建一個(gè)屬于自己的組件庫(kù)![1]...之類的分享給大家。但其實(shí)組件庫(kù)一直處于一個(gè)發(fā)展階段,還有很多東西都不成熟,就好比今天的主角——組件庫(kù)打包。
這里先簡(jiǎn)單回顧一下之前的組件庫(kù)打包:
-
直接配置 vite.config文件。只配置了lib模式打包 -
運(yùn)行 pnpm run build,其實(shí)就是執(zhí)行vite build簡(jiǎn)單粗暴完成組件庫(kù)打包 -
輸出 umd、iife、es、cjs模式的產(chǎn)物文件 -
輸出一個(gè) style.css樣式文件
這是最終打包完的 dist 包下的目錄結(jié)構(gòu):
簡(jiǎn)單點(diǎn)開看一下 es 包的產(chǎn)物代碼:
好了,以上就是之前的組件庫(kù)打包的方案和結(jié)果,簡(jiǎn)單又好用,簡(jiǎn)直了。但是為什么現(xiàn)在的我要選擇升級(jí)打包方式呢?是基于什么樣的痛點(diǎn)?這一個(gè)我會(huì)在后文慢慢道來。這里,大家暫且先跟我一起看看本文的實(shí)戰(zhàn)目標(biāo)。
講到實(shí)戰(zhàn)目標(biāo),不得不以優(yōu)秀的開源項(xiàng)目為標(biāo)桿。這里我就直接按照 element-plus 的產(chǎn)物格式作為目標(biāo)了。跟大家簡(jiǎn)單的看看它的打包產(chǎn)物的結(jié)構(gòu),如下圖所示:
其中再點(diǎn)開它的 es 目錄看看究竟:
非常工整的結(jié)構(gòu),也就是打包前的原項(xiàng)目結(jié)構(gòu)。感興趣的可以去 unpkg[2]或者自己裝一個(gè)在node_modules中查看。這里我們直接分析產(chǎn)物結(jié)構(gòu)如下:
-
dist。放整包的,簡(jiǎn)單理解為一個(gè)打包所有代碼并壓縮的.min.js -
es。es包,產(chǎn)物按項(xiàng)目的目錄結(jié)構(gòu)生成。簡(jiǎn)單理解為只將ts、.vue文件編譯成js -
lib。跟上面的 es 的一樣,只是這里是 cjs 模式的 -
theme-chalk。樣式代碼,各組件的css文件和一個(gè)整體的index文件 -
package.json。emmm...這個(gè)大家自己翻譯吧 -
*.d.ts??膳浜?vscode 的 voloar 插件實(shí)現(xiàn)代碼提示 -
*.json。用于 webstorm 的代碼提示
所以,上述就是本文的目標(biāo)了,我也要通過對(duì)組件庫(kù)打包的升級(jí)改造,讓組件庫(kù)的打包產(chǎn)物跟上述結(jié)構(gòu)、功能相似。不過,本文只著重分享組件庫(kù)的打包,代碼提示相關(guān)的實(shí)踐并不會(huì)涉及,并且我打算另外寫一篇文章來分享組件庫(kù)的代碼提示實(shí)戰(zhàn)。
分離CSS
這一小節(jié),我將為大家解開上一小節(jié)我遺留下來的一個(gè)問題:我是基于什么樣的痛點(diǎn)才要升級(jí)組件庫(kù)的打包?畢竟這種大佬都不愿意投入資源的非kpi項(xiàng)目,我有那時(shí)間摸摸魚不香嗎是吧~
回到本小節(jié)的主題,為什么要分離css?那就得看之前是怎么開發(fā)的了。
如上圖所示,這是最基本的 vue 開發(fā)模式了:template + script + css。這樣寫法的組件,通過 vite 的 lib 模式直接打包,可以得到產(chǎn)物:**.js 和一個(gè) style.css,這一點(diǎn)沒問題吧,前文已經(jīng)講過了。
這樣有什么樣的問題?這樣會(huì)導(dǎo)致所有組件的樣式都被打包進(jìn)了同一個(gè) css 文件里,按需引用對(duì)于樣式文件來說就不存在了。當(dāng)然,這也只是其中一個(gè)問題,而且還是個(gè)小問題而已,我肯定不會(huì)因此而升級(jí)打包方式和改變項(xiàng)目組成結(jié)構(gòu)的,所以我們接著往下看先。
如果之前有看過我另一篇組件庫(kù)實(shí)戰(zhàn)文章:組件庫(kù)實(shí)戰(zhàn)——按需加載工程化[3] 的同學(xué)應(yīng)該知道,那時(shí)候的解決方案其實(shí)也是有缺陷的,這也是當(dāng)前遇到痛點(diǎn)必須解決的地方。當(dāng)然,沒看過的也沒關(guān)系,這里我簡(jiǎn)單的說明一下:
在使用element-plus按需導(dǎo)入[4]的項(xiàng)目中,如果直接使用自己組件庫(kù)的組件(我是vc-前綴的),會(huì)丟掉原本el-組件的樣式。比如說我直接在代碼中使用 vc-button,而他又依賴el-button的樣式,此時(shí)就會(huì)丟失el-button的樣式。原因就是按需引入的插件匹配 vc-button 的組件時(shí)并不知道需要引入 el-button 的樣式,于是我的自己寫一個(gè)插件工具來實(shí)現(xiàn)這一點(diǎn)。
如上圖所示,這個(gè)插件工具——resolver當(dāng)解析到 template 中有 vc-button 的時(shí)候,會(huì)去 import vcButton 的組件代碼和 import el-button 的樣式代碼。(此時(shí) vc-button 的樣式文件是全局引入的。因?yàn)榇虬笾挥幸粋€(gè) style.css)
雖然這個(gè)自動(dòng)導(dǎo)入的問題是解決了,但是又遺留了另一個(gè)問題就是組合組件的樣式問題。比如說此時(shí)我的 vc-button 不僅用了 el-button,還組合了 el-tag、el-select 等組件使用,那上述插件工具就沒用了,因?yàn)槿鄙?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">import另外兩個(gè)組件的樣式。
如需在使用組合組件的時(shí)候樣式能正常引入,那你得告訴插件這個(gè)組件用了哪些組件的樣式是吧?那這個(gè)要怎么做呢?其實(shí)我們可以參考老大哥——element-plus 的實(shí)現(xiàn)。因?yàn)樗陀羞@樣的案例,比如說他的 select 組件,就是多個(gè) el- 組件組合成的。我們?nèi)タ匆幌滤脑创a:
可以看到 select 目錄下有一個(gè) style 目錄,點(diǎn)開里面的文件可以看到其做了一個(gè)樣式的集成。引用了 input、tag、popper 等組件的樣式。雖然之前參與這個(gè)項(xiàng)目的時(shí)候沒怎么留意過這個(gè) style 目錄(也不知道是干嘛的),但是現(xiàn)在大概可以猜出他就是一個(gè)樣式關(guān)系表,做整合的。結(jié)合上述說明,我們重新畫個(gè)圖來看看就很清晰了:
這里,我們可以看出 import 樣式那一塊上不再是直接用 el-button 的樣式了,而是用了 vc-button的樣式索引文件(是個(gè)js文件),而這個(gè)索引文件呢,引用了各種它所需要的樣式文件。如此一來,之前遺留下來的缺陷問題也就引刃而解了。
這也是將 css 拆分出來的好處,讓他形成一個(gè) 原子css 的概念。將每一個(gè)組件的樣式都單獨(dú)抽出來寫,每一個(gè)組件的樣式就是一個(gè)原子css,這樣組合使用的時(shí)候也就很方便了。
然后,這里再順便提一下為什么這次的更新打包,要保留原本的目錄結(jié)構(gòu)來打包?;蛘哒f為什么 element-plus 產(chǎn)物的 es、lib 目錄下是保留了原項(xiàng)目結(jié)構(gòu)的。我們來看看它官網(wǎng)的其中一個(gè)介紹:
我個(gè)人猜測(cè),如果需要手動(dòng)引入樣式的話,還是要知道去哪里引用的對(duì)吧?總不能打包完代碼就亂成一團(tuán),然后用戶需要手動(dòng)引入的時(shí)候不知道去哪里引入了...
好了,這一小節(jié)說得有點(diǎn)長(zhǎng),我簡(jiǎn)單總結(jié)一下:
-
解釋了自建組件庫(kù)使用 unplugin之類插件[5] 實(shí)現(xiàn)自動(dòng)按需導(dǎo)入的樣式引入問題。 -
解釋了為什么要分離 css?核心解決組合組件的樣式問題,順便解決按需加載的體量問題。 -
順便分析了為什么組件庫(kù)打包完要保留原項(xiàng)目結(jié)構(gòu)。
編寫打包腳本
前文鋪墊了這么多,終于輪到本文的重頭戲了。那這一小節(jié),我們主要實(shí)現(xiàn)幾個(gè)目標(biāo)點(diǎn):
-
使用 gulp 串并聯(lián)工作流完成打包任務(wù) -
打包出 全局dist、es、lib 的產(chǎn)物。其中es、lib目錄要保持原目錄結(jié)構(gòu) -
抽離css,并且編譯打包c(diǎn)ss(這里我用的是scss)
這里因?yàn)槲覀円獙?duì)不同格式根本打包,再配置成 vite.config 并直接通過 vite build 來打包肯定是不夠方便的了,所以我借助 gulp 來完成一個(gè)簡(jiǎn)單的打包腳本。正式進(jìn)入打包環(huán)節(jié)前,先來解決工具選擇的問題。目前我個(gè)人意向的是 vite 和 rollup,我是如何選擇的呢?在此,我先擼了個(gè)圖:
從圖中大概可以看出來,vite 雖然生產(chǎn)環(huán)境默認(rèn)使用 rollup 來構(gòu)建,但相比于 rollup,它是更為上層的。它會(huì)有更多的集成,比如說集成了對(duì) ts 的支持,對(duì) scss、less 等支持,還有各種基礎(chǔ)的插件集成(如支持直接 require 模塊),當(dāng)然,他還預(yù)置了一些通用的 rollup 配置。
講這么多,簡(jiǎn)單來說就是 vite 更上層,使用方便,適合懶人;**rollup 更底層,使用靈活**,適合有激情愛折騰的大佬!我當(dāng)然是選擇了前者~如果說想使用 rollup 的話,建議大家直接參考 element-plus 的打包吧,它就是基于 rollup 寫的打包腳本。
我們直接看基于 vite 寫打包腳本的基本格式:
import { build } from 'vite'
function buildScript () {
build({
plugins: [],
build: {
outDir: 'xxx',
lib: {
entry: 'xxx'
}
},
})
}
其實(shí)也很簡(jiǎn)單,安裝 vite,然后 import 它暴露出來的 build 函數(shù),并對(duì)其中做一些配置。這些配置就跟我們平時(shí)配置 vite.config 文件是一樣的,一把梭哈,基本沒什么難度。接下來我們看看其中每一步的一些核心點(diǎn)吧。
1. 打包 es、lib 包并保留原結(jié)構(gòu)
關(guān)于 es、lib 包,我們依舊是使用 vite 的 lib 模式去構(gòu)建[6](詳細(xì)可點(diǎn)擊鏈接去了解)。這里簡(jiǎn)單的列一下基礎(chǔ)的配置:
{
plugins: [
vue(),
vueJsx()
],
build: {
outDir: join(vcElementPlusRoot, 'dist', 'es'),
lib: {
entry: files,
formats: ['es'],
},
rollupOptions: {
external: ['element-plus', 'vue', 'vue-router', '@element-plus/icons-vue']
}
}
}
其中的 plugins 配置中,因?yàn)槲覀兇虬氖?vue3 組件庫(kù),所以會(huì)用到 vitejs/plugin-vue[7] 等相關(guān)的 vue 插件。
build 配置中,我們主要看 lib.entry 即可。這里的入口是可以傳數(shù)組(多個(gè))的,如下圖的說明:
這里可以通過一個(gè)工具——fast-glob[8]拿到所有的入口,包括 style 目錄中的索引文件的入口(這一點(diǎn)后面會(huì)提到)。
如以下寫法,就能拿到組件庫(kù)的全部入口了:
const files = await glob('**/*.{js,ts,vue}', {
cwd: vcElementPlusComponentsRoot,
absolute: true,
onlyFiles: true,
})
獲取的結(jié)果如下截圖(大家可以自己試著玩玩):
剩下的就是 rollupOptions 配置,當(dāng)我們想實(shí)現(xiàn)打包后保留原項(xiàng)目結(jié)構(gòu),必須配置:
-
preserveModules[9]。此模式將使用原始模塊名稱作為文件名為所有模塊創(chuàng)建單獨(dú)的塊,而不是創(chuàng)建盡可能少的塊。(感興趣的點(diǎn)擊進(jìn)去看看吧,這里我隨便找個(gè)翻譯軟件翻譯的) -
preserveModulesRoot[10]。當(dāng)output.preserveModules為true時(shí),應(yīng)該從output.dir路徑中剝離的輸入模塊的目錄路徑(同上,感興趣點(diǎn)鏈接了解吧)
ok,基本上有了這些之后,打包 es、lib 的任務(wù)就完成了。最終腳本代碼如下:
await build({
resolve: {
alias: VcElementAlias()
},
plugins: [
vue(),
vueJsx()
],
build: {
outDir: join(vcElementPlusRoot, 'dist', 'es'),
lib: {
entry: files,
formats: ['es'],
},
rollupOptions: {
external: ['element-plus', 'vue', 'vue-router', '@element-plus/icons-vue'],
output: {
preserveModules: true,
preserveModulesRoot: vcElementPlusComponentsRoot,
exports: 'named'
}
}
}
})
接著,我運(yùn)行一下打包看看打包后的效果:
為了方便大家看產(chǎn)物結(jié)構(gòu),我用不同顏色的框框劃分了打包后的產(chǎn)物結(jié)構(gòu),可以看到基本符合我們的預(yù)期了。(cjs模式的打包跟es類似的,所以我就不展開lib目錄的打包過程和產(chǎn)物了)
2. 打包 dist 代碼
這一點(diǎn)相比上一點(diǎn)來說要更加的簡(jiǎn)單,其實(shí)就是我們改版前的那種。無腦配置個(gè)輸出模式為 umd 和 iife 就完成了。簡(jiǎn)單看看相比前文的差異的配置:
build: {
outDir: join(vcElementPlusRoot, 'dist'),
lib: {
entry: file,
formats: ['umd', 'iife'],
name: VC_ELEMENT_PLUS_CAMELCASE_NAME,
fileName: format => `${VC_ELEMENT_PLUS}.${format}.js`
}
...
}
其余的基本沒什么不同,不過注意這里要配置 name 和 fileName。當(dāng)然,這些你不配置的話,打包也不會(huì)成功,并且會(huì)有報(bào)錯(cuò)提示,只要根據(jù)報(bào)錯(cuò)提示完成對(duì)應(yīng)的配置后,問題也不大。我打算把這兩個(gè)文件放在 dist 的根目錄中,跟 es、lib 目錄同級(jí)。
最終打包出來的結(jié)果如下:
也是符合預(yù)期的,我們接著往下走。
3. 編譯&打包CSS
這一步,我借助了gulp-sass[11]插件。它的作用是:用于將 Sass 代碼編譯成 CSS 代碼。在正式講打包之前,我給大家看看我抽離的樣式文件大概成什么樣:
我將原本都各自寫在組件內(nèi)的樣式抽離出來,放在一個(gè) theme 的目錄下的。每一個(gè) scss 文件以組件名命名,他們就是一個(gè)個(gè)組件的原子css。然后我會(huì)在組件對(duì)應(yīng)的 style 索引文件中這樣引用樣式(直接引用 scss 文件):
大家也可能注意到了,在 theme 目錄下,也有一個(gè) index.scss 文件,它的代碼是這樣的:
沒錯(cuò),它其實(shí)就是一個(gè)總的樣式文件。緊接著,我們直接看看關(guān)于 scss 的編譯、打包腳本如何實(shí)現(xiàn)吧:
import gulpSass from 'gulp-sass'
import gulp from 'gulp'
import dartSass from 'sass'
import { vcElementPlusRoot } from '../../utils/path';
export async function sassCompiler () {
const sass = gulpSass(dartSass)
return await gulp.src(`${vcElementPlusRoot}/theme/*.scss`) // 入口
.pipe(sass.sync()) // 編譯
.pipe(gulp.dest(`${vcElementPlusRoot}/dist/theme`)) // 輸出目錄
}
對(duì)于上述代碼,他的作用就是編譯所有的 scss 文件,并且將他們都打包進(jìn) index 文件中。因?yàn)殛P(guān)于 gulp-sass 、 gulp 等這些工具,我也是要用的時(shí)候才去寫,平時(shí)也了解不多,所以我就不多展開他們的一些用法、寫法了,大家感興趣可以自己去研究一下。
最后也是來看看打包后的效果:
可以看到,所有的 scss 文件都被編譯成了 css 文件,此時(shí),我們打開一下 index.css 文件大概看看成什么樣:
我沒有做代碼壓縮,所以大家可以一目了然,應(yīng)該是所有組件的 css 都被打進(jìn)來了,沒問題!
4. 通過 gulp 編排任務(wù)
其實(shí)這就是一個(gè)工作流工具,有了解過 CI/CD 的同學(xué)應(yīng)該很清楚它是干嘛的了。當(dāng)然,還是那句話,我并不是常年使用 gulp 的,所以了解得也并不多,這里使用也就是為了解決問題,達(dá)成目的,所以我也不會(huì)過多的展開對(duì) gulp 的講解。大家感興趣的可以去他的官網(wǎng)[12]詳細(xì)看看。
因?yàn)榍懊嫖覀円呀?jīng)把打包任務(wù)都分別實(shí)現(xiàn)了,最后通過 gulp 做一個(gè)串聯(lián)而已。所以我還是直接上代碼吧:
import { series, parallel } from 'gulp'
import { cleanDist, elpBuildModules, elpBuildBundle, sassCompiler } from './tasks'
export default series(
cleanDist, // 刪除上次的dist
parallel(
elpBuildModules, // 并行執(zhí)行 es、lib 打包
elpBuildBundle, // 并行執(zhí)行 全局dist 打包
sassCompiler // 并行執(zhí)行 scss 的編譯打包
)
)
整個(gè) gulpfile 就這么點(diǎn),簡(jiǎn)單來說它做的事情就是刪除上次的 dist,然后進(jìn)行 es、lib、dist、scss 的編譯打包工作。
彩蛋——rollup插件改動(dòng)樣式索引文件
不知道大家發(fā)現(xiàn)沒,前文兩個(gè)地方我都埋了點(diǎn)伏筆:
-
入口為什么要包含 style目錄中的索引文件 -
組件 style目錄中的索引文件直接引用scss文件:
相信已經(jīng)很明顯了,因?yàn)?strong>直接引用 scss 文件作為樣式文件在瀏覽器中無法直接使用!所以我們需要對(duì)其做一些改動(dòng)。**開發(fā)環(huán)境中,因?yàn)?vite 天然支持 scss**(只要安裝了sass的包就行,不用任何插件配置),所以我們?cè)陂_發(fā)環(huán)境使用樣式時(shí),直接 import 我們的索引文件(再次提醒索引文件是個(gè)js)是沒問題的,比如:
import {vcButton} from '@xxx'
import '@xxx/button/style/index.js'
但是如果此時(shí)到了瀏覽器環(huán)境直接使用的話就不行了,因?yàn)?index.js 中 import 的是一個(gè) scss 的文件。所以我們還需要自己寫一個(gè) rollup 插件,在打包的時(shí)候?qū)⑺饕募玫穆窂阶鲆稽c(diǎn)改動(dòng)。
這個(gè)插件的目標(biāo)就是**將 scss 替換成 css**,如:
-
import '@lizhife/vc-element-plus/theme/back.scss' -
import '@lizhife/vc-element-plus/theme/back.css'
當(dāng)然,對(duì)應(yīng)的import路徑配置那些也要配置好,不然可能在路徑上也要有所改動(dòng)。這里我就基于 rollup 的 resolveId[13] 鉤子。當(dāng)然,這一段在 vite 官網(wǎng)[14]也能看到。
簡(jiǎn)單說說 resolveId 鉤子的作用,他能拿到你所有 import 的包名、路徑,并在參數(shù)中提供給你。所以基于此,我們可以這樣來寫這個(gè)插件:
export function rollupPluginCompileStyleEntry (): Plugin {
const themeEntryPrefix = `${PREFIX}/${VC_ELEMENT_PLUS}/${THEME}`
return {
name: 'rollup-plugin-compile-style-entry',
resolveId (id) {
// 匹配是否滿足 @xxx/vc-el.. 開頭的字符
if (!id.startsWith(themeEntryPrefix)) return
return {
// 將 scss 字符替換成 css
id: id.replaceAll('.scss', '.css'),
external: 'absolute',
}
}
}
}
這個(gè)插件的核心就如注釋那樣了,匹配一個(gè)固定開頭的字符(比如這里是 @lizhife/vc-element-plus),**將這個(gè)字符串的 .scss 替換成 .css**。我們直接看結(jié)果,看看使用了這個(gè)插件后的效果如何:
可以看到,import 的最終結(jié)果變成了 xx.css,這也符合我們的期望,完美~當(dāng)然啦,記得把插件配置上,不然就白搞了:
plugins: [
rollupPluginCompileStyleEntry(),
vue(),
vueJsx()
],
彩蛋——alias配置
當(dāng)在項(xiàng)目中使用自身依賴時(shí),需要注意配置alias。
Error: [vite]: Rollup failed to resolve import "@lizhife/vc-element-plus" from '...'
當(dāng)遇到上述的一些因?yàn)榘鴮?dǎo)致的無法解析的問題,可以通過配置 alias 來解決,特別是一些開發(fā)環(huán)境和打包完之后有所變動(dòng)的。相關(guān)的我也在這里說太多了,之前的文章也有提到這一點(diǎn)。
涉及的插件簡(jiǎn)介
這里我會(huì)介紹本次實(shí)戰(zhàn)中會(huì)用到的各種插件、工具和他們的作用簡(jiǎn)介,希望可以幫助大家更清晰地了解本文的內(nèi)容。另外我會(huì)附上每個(gè)插件的gayhub地址,感興趣的同學(xué)可以戳進(jìn)去詳細(xì)了解。
-
@esbuild-kit/cjs-loader[15]:
-
支持在 gulpfile 使用 esm(import、export)的模塊化寫法 -
支持在 gulpfile 使用 ts
-
fast-glob[16]
-
提供了一種快速、靈活的方式來匹配文件和目錄。
-
@vitejs/plugin-vue[17]:
-
支持 vite 解析 .vue后綴的單文件組件(SFC),類似 webpack 中我們用的vue-loader
-
@vitejs/plugin-vue-jsx[18]:
-
支持 vite 解析 jsx/tsx。同第3點(diǎn),并且二者是放在同一個(gè)倉(cāng)庫(kù)中的
-
gulp-sass[19]
-
一個(gè) gulp 插件,用于將 Sass 代碼編譯成 CSS 代碼
寫在最后
文章內(nèi)容有點(diǎn)長(zhǎng),大家點(diǎn)贊關(guān)注慢慢看~關(guān)于組件庫(kù)打包的內(nèi)容輸出,之前就有小伙伴催更了,但是因?yàn)橹皼]啥使用上的問題,并且這一塊投入也麻煩,所以一直沒搞。組件庫(kù)慢慢發(fā)展到現(xiàn)在,組件數(shù)量慢慢上升,發(fā)展遇到瓶頸了所以需要升級(jí)一下組件庫(kù)的架構(gòu)和打包。當(dāng)然,后續(xù)有相關(guān)的組件庫(kù)實(shí)戰(zhàn)我會(huì)持續(xù)的輸出文章分享。
最后,如果本文有哪些寫得不對(duì)的地方,大家盡管指出。希望這篇文章在你的工程化道路上有所啟發(fā)。再重申一下,工程化是開放性作文,思路、方案有很多,能解決問題的就是可行的,并沒有標(biāo)準(zhǔn)答案。
作者:
回復(fù)“加群”,一起學(xué)習(xí)進(jìn)步
