目錄
- 一、項目結(jié)構(gòu)的核心思想
- 二谴麦、項目目錄結(jié)構(gòu)
- 三蠢沿、資源路徑編譯規(guī)則
- 四、index.html
- 五匾效、build目錄 和 config目錄
- 六舷蟀、public目錄
- 七、static 目錄
- 八面哼、src目錄結(jié)構(gòu)
- 九野宜、間接訪問
- 十、目錄的分類
- 十一魔策、應(yīng)用軟件用戶界面的結(jié)構(gòu)
- 十二匈子、業(yè)務(wù)代碼的目錄結(jié)構(gòu)
- 1.項目業(yè)務(wù)邏輯的根目錄
- 2.模塊的目錄
- 3.流程的目錄
- 4.頁面的目錄
- 5.組件的目錄
- 業(yè)務(wù)代碼的目錄結(jié)構(gòu)
- 十三、新概念的定義
- 十四闯袒、Vuex的拆分方案
- 分散式Vuex的拆分規(guī)則
- 相關(guān)工具函數(shù)的實現(xiàn)
- 十五虎敦、vue-router的拆分方案
- 十六、組件的vue-router和Vuex的配置對象的安放位置
- 方案1:集中在vue文件
- 方案2:分散到單獨的JS文件
- 方案3:分散到單獨的JS文件政敢,然后再匯總導(dǎo)出
- 方案4:匯總到單獨的JS文件
- 十七其徙、組件的分類
- 十八、組件結(jié)構(gòu)規(guī)范
- 容器組件結(jié)構(gòu)規(guī)范
- 展示組件結(jié)構(gòu)規(guī)范
前言
本規(guī)范是我依照 關(guān)注點分離 的思想喷户,針對 vue 項目制定的規(guī)范唾那,如果您在使用當中發(fā)現(xiàn)不當 或 需要完善的地方,您都可以通過以下方式聯(lián)系我摩骨,期待與您的交流:
- 郵箱:guobinyong@qq.com
- QQ:guobinyong@qq.com
- 微信:keyanzhe
內(nèi)容
一通贞、項目結(jié)構(gòu)的核心思想
因為代碼的相關(guān)性主要與業(yè)務(wù)功能有關(guān),而與文件類型的關(guān)系不大恼五,所以昌罩,為了便于 編寫、查閱灾馒、理解茎用、變更 代碼,項目結(jié)構(gòu)遵循以下核心宗指(宗指屬于思想):
- 以業(yè)務(wù)功能為單位組織項目結(jié)構(gòu);
- 以低耦合度為目標劃分模塊職責和邏輯轨功;
優(yōu)點:
- 業(yè)務(wù)功能模塊的相關(guān)代碼都集中在一塊旭斥,方便移動和刪除;
- 實現(xiàn)了關(guān)注點分離古涧,方便開發(fā)垂券、調(diào)試、維護羡滑、編寫菇爪、查閱、理解代碼柒昏;
二凳宙、項目目錄結(jié)構(gòu)
本項目規(guī)范的根級目錄與Vue腳手架的 webpack 模板的根級目錄一致,只是 src/
下的目錄結(jié)構(gòu)與 webpack 模板不一樣职祷;因為 src/
下的目錄結(jié)構(gòu)遵從本規(guī)范 項目結(jié)構(gòu)的核心思想
氏涩;
.
├── build/ # webpack 配置文件;
│ └── ...
├── config/ # 與項目構(gòu)建相關(guān)的常用的配置選項有梆;
│ ├── index.js # 主配置文件
│ ├── dev.env.js # 開發(fā)環(huán)境變量
│ ├── prod.env.js # 生產(chǎn)環(huán)境變量
│ └── test.env.js # 測試環(huán)境變量
│
├── src/
│ ├── main.js # webpack 的入口文件是尖;
│ ├── common/ # 存放項目共用的資源,如:常用的圖片淳梦、圖標析砸,共用的組件昔字、模塊爆袍、樣式,常量文件等等作郭;
│ │ ├── assets/ # 存放項目共用的代碼以外的資源陨囊,如:圖片、圖標夹攒、視頻 等蜘醋;
│ │ ├── components/ # 存放項目共用的組件,如:封裝的導(dǎo)航條咏尝、選項卡等等压语; 備注:這里的存放的組件應(yīng)該都是展示組件;
│ │ ├── network/ # 存放項目的網(wǎng)絡(luò)模塊编检,如:接口胎食;
│ │ ├── compatible/ # 存放項目的兼容模塊,如:適合App和微信各種接口的模塊允懂;
│ │ ├── extension/ # 存放已有類的擴展模塊厕怜,如:對 Array 類型進行擴展的模塊;
│ │ ├── libraries/ # 存放自己封裝的或者引用的庫;
│ │ ├── tools/ # 自己封裝的一些工具
│ │ ├── constant.js # 存放js的常量粥航;
│ │ ├── constant.scss # 存放scss的常量琅捏;
│ │ └── ...
│ └── app/ # 存放項目業(yè)務(wù)代碼;
│ ├── App.vue # app 的根組件递雀;
│ └── ...
│
├── static/ # 純靜態(tài)資源柄延,該目錄下的文件不會被webpack處理,該目錄會被拷貝到輸出目錄下缀程;
├── test/ # 測試
│ ├── unit/ # 單元測試
│ │ ├── specs/ # test spec files
│ │ ├── eslintrc # 專為單元測試配置的eslint配置文件
│ │ ├── index.js # 測試編譯的入口文件
│ │ ├── jest.conf.js # Jest的配置文件
│ │ └── karma.conf.js # Karma的配置文件
│ │ └── setup.js # 在Jest之前運行的啟動文件拦焚;
│ └── e2e/ # e2e 測試
│ ├── specs/ # test spec files
│ ├── custom-assertions/ # 自定義的斷言
│ ├── runner.js # test runner 腳本
│ └── nightwatch.conf.js # test runner 的配置文件
├── .babelrc # babel 的配置文件
├── .editorconfig # 編輯器的配置文件;可配置如縮進杠输、空格赎败、制表類似的參數(shù);
├── .eslintrc.js # eslint 的配置文件
├── .eslintignore # eslint 的忽略規(guī)則
├── .gitignore # git的忽略配置文件
├── .postcssrc.js # postcss 的配置文件
├── index.html # HTML模板
├── package.json # npm包配置文件蠢甲,里面定義了項目的npm腳本僵刮,依賴包等信息
└── README.md
三、資源路徑編譯規(guī)則
默認情況下鹦牛,vue-loader 使用 css-loader 和 Vue 模版編譯器自動處理樣式和模版文件搞糕。在編譯過程中,所有的資源路徑例如 <img src="...">
曼追、background: url(...)
和 @import
會作為模塊依賴窍仰。
路徑的編譯規(guī)則如下:
- 如果路徑是絕對路徑,會原樣保留礼殊;
- 如果路徑以 . 開頭驹吮,將會被看作相對的模塊依賴,并按照你的本地文件系統(tǒng)上的目錄結(jié)構(gòu)進行解析晶伦;
- 如果路徑以 ~ 開頭碟狞,其后的部分將會被看作模塊依賴。這意味著你可以用該特性來引用一個 node 依賴中的資源:
<img src="~some-npm-package/foo.png">
婚陪; - 如果路徑以 @ 開頭族沃,也會被看作模塊依賴。如果你的 webpack 配置中給 @ 配置了 alias泌参,這就很有用了脆淹。所有 vue-cli 創(chuàng)建的項目都默認配置了將 @ 指向
/src
;
四沽一、index.html
在webpack構(gòu)建項目期間盖溺,webpack插件 html-webpack-plugin
會將 /index.html
處理后并拷貝到輸出目錄中,并把 webpack 的構(gòu)建輸出資源(如:輸出的js锯玛、css文件咐柜,等等)鏈接自動插入到該 html 文件兼蜈。此外,Vue CLI還會自動注入資源提示(preload/prefetch)清單/圖標鏈接(當使用PWA插件時)拙友;
五为狸、build目錄 和 config目錄
- build 目錄下存放的是 webpack 的配置文件;
- config 目錄下存放的是與項目構(gòu)建相關(guān)的常用的配置選項遗契、變量辐棒;
通常情況下,除非要配置 webpack 的 loader 或者 插件牍蜂,否則漾根,你應(yīng)該優(yōu)先嘗試更改 config 目錄下的文件;
六鲫竞、public目錄
項目的 public 目錄由 webpackConfig.output.publicPath
參數(shù)決定辐怕;在用 vue 腳手架創(chuàng)建的基于 webpack 模板 的項目中,webpackConfig.output.publicPath
的默認配置是 /
从绘;
要引用 public 目錄下的資源寄疏,需要通過以下方式(為了方便描述,設(shè) public 目錄的路徑是 publicPath
):
- 在
publicPath/index.html
文件中引用 public 中的資源時僵井,需要在鏈接前加上<%= webpackConfig.output.publicPath %>
陕截;
示例:<link rel = "快捷方式圖標" href = "<%= webpackConfig.output.publicPath%>favicon.ico " >
- 在 JavaScript 中,需要通過
process.env.BASE_URL
引用 public 中的資源批什;
示例:- 先將
process.env.BASE_URL
傳遞給組件;data () { return { baseUrl: process.env.BASE_URL } }
- 然后农曲,在模板中引用:
<img :src="`${baseUrl}my-image.png`">
- 先將
七、static 目錄
- static 雖然目錄可以添加任何資源驻债,如:圖片乳规、代碼 等等,但是不建議把這些資源添加到 static static 目錄適合存放以下內(nèi)容:
- 與 webpack 不兼容的庫却汉;
- 您需要在構(gòu)建輸出中使用具有特定名稱的文件驯妄;
- 你有成千上萬的圖像荷并,需要動態(tài)引用它們的路徑合砂;
-
static/
目錄的資源不會被webpack處理,它們會被拷貝到輸出目錄下源织; - 要引用
static/
目錄下的資源翩伪,可以使用publicPath/static/...
;
八谈息、src目錄結(jié)構(gòu)
根據(jù)項目結(jié)構(gòu)的核心思想缘屹,src的目錄結(jié)構(gòu)將以業(yè)務(wù)功能劃分,具體如下 :
src/
├── main.js # webpack 的入口文件侠仇;
├── common/ # 存放項目共用的資源轻姿,如:常用的圖片犁珠、圖標,共用的組件互亮、模塊犁享、樣式,常量文件等等豹休;
│ ├── assets/ # 存放項目共用的代碼以外的資源炊昆,如:圖片、圖標威根、視頻 等凤巨;
│ ├── components/ # 存放項目共用的組件,如:封裝的導(dǎo)航條洛搀、選項卡等等敢茁; 備注:這里的存放的組件應(yīng)該都是展示組件;
│ ├── network/ # 存放項目的網(wǎng)絡(luò)模塊留美,如:接口卷要;
│ ├── compatible/ # 存放項目的兼容模塊,如:適合App和微信各種接口的模塊独榴;
│ ├── extension/ # 存放已有類的擴展模塊僧叉,如:對 Array 類型進行擴展的模塊;
│ ├── libraries/ # 存放自己封裝的或者引用的庫棺榔;
│ ├── tools/ # 自己封裝的一些工具
│ ├── constant.js # 存放js的常量瓶堕;
│ ├── constant.scss # 存放scss的常量;
│ └── ...
└── app/ # 存放項目業(yè)務(wù)代碼症歇;
注意:
- 項目的業(yè)務(wù)代碼應(yīng)該從
src/app/
目錄開始郎笆; -
src/common/
的子目錄中,在深度上的層級結(jié)構(gòu)應(yīng)該是盡量扁平的忘晤,不應(yīng)該有很深的層級結(jié)構(gòu)宛蚓;如果src/common/
中的目錄樹 與src/app/
中的目錄樹在深度上有十分相似的層級結(jié)構(gòu),則就表示你應(yīng)該重新考慮src/common/
中的資源是否是真的需要被共享的資源设塔,被共享的資源的目錄層級結(jié)構(gòu)應(yīng)該是盡可能扁平的凄吏;對于不必共享的資源,應(yīng)該放在src/app/
下相應(yīng)的目錄結(jié)構(gòu)中闰蛔;
九痕钢、間接訪問
要間接地訪問 常用 或者 易變更 的 目錄 或 模塊 ;
間接扡訪問方式有很多序六,如:
- 通過常量或變量任连;
- 通過 webpack 的配置字段
resolve.alias
;
示例:
假設(shè)我們會經(jīng)常訪問對于 src/common/assets/
目錄例诀,或者在將來随抠,可能會更改 src/common/assets/
目錄的位置裁着;
則我們可以如下間接地訪問 src/common/assets/
目錄:
- 在 webpack 的配置字段
resolve.alias
中給src/common/assets/
目錄設(shè)置別名:webpackConfig.resolve.alias = { 'c-assets': resolve('src/common/assets'), }
- 通過上面設(shè)置的別名
'c-assets/...'
訪問src/common/assets/
目錄:
優(yōu)點:
這樣做有以下好處:
- 訪問目錄更方便;
- 當目錄位置變更時拱她,只需要更改一處(別名的值 或者 變量的值)跨算,就可以使所有的訪問指向正確地的地址;
- 不易出錯椭懊,因為通過變量或者別名縮短了路徑的長度诸蚕;
十、目錄的分類
目錄的作用就是組織文件的氧猬,為了充分實現(xiàn) 項目結(jié)構(gòu)的核心思想
背犯,我把目錄分為以下幾類:
- 組件目錄 : 以組件為單位劃分的、用于組織單個組件相關(guān)文件的目錄盅抚;
- 容器目錄 : 用于組織某類東西的目錄漠魏;
示例:
假設(shè)有以下目錄:
components/ # 存放項目共用的組件,如:封裝的導(dǎo)航條妄均、選項卡柱锹、輪播圖等等;
├── navbar/ # 導(dǎo)航條組件的目錄丰包;
├── tabbar/ # 選項卡組件的目錄禁熏;
├── swiper/ # 輪播圖組件的目錄;
└── ...
其中邑彪,目錄 navbar/
瞧毙、 tabbar/
、 swiper/
中分別盛放的是 導(dǎo)航條寄症、選項卡宙彪、輪播圖 組件,它它們都是為單個組件專門分配的目錄有巧,所以释漆,這些目錄都是 組件目錄 ; 而目錄 components/
下面盛放了很多組件篮迎,所以它不是專門為單個組件而分配的目錄男图,它是用于盛放某類(組件)東西的,所以它是 容器目錄 柑潦;
十一享言、應(yīng)用軟件用戶界面的結(jié)構(gòu)
對于可交互性的應(yīng)用程序,人們所能直觀看到的就是圖形用戶界面渗鬼;為了方便描述 和 組織項目結(jié)構(gòu),我把應(yīng)用界面的構(gòu)成元素抽離成了若干個概念荧琼,并形象化地描述了它們之間的關(guān)系譬胎;
詳細文檔請見《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》
應(yīng)用軟件的界面由 模塊差牛、流程、頁面堰乔、組件 這幾元素組成偏化,它們的關(guān)系如下圖:
![應(yīng)用軟件界面結(jié)構(gòu)圖][]
我對這些構(gòu)成元素的概念作了非嚴謹?shù)亩x,如下:
- 軟件: 是一系列按照特定順序組織的計算機數(shù)據(jù)和指令的集合镐侯。如:微信侦讨、地圖、天氣 等等苟翻;
- 界面: 術(shù)語中韵卤,界面所表示的東西通常因場景而異;我簡單概括如下崇猫;
- 定義1: 應(yīng)用軟件的圖形顯示區(qū)域沈条;
- 定義2: 界面是應(yīng)用軟件中所有能被用戶看到的圖形的集合;
- 定義3: 界面是應(yīng)用軟件在任意時刻時所顯示的所有圖形的集合诅炉; 規(guī)定:在本文中指:
規(guī)定: 在本文中蜡歹,界面的定義取 定義2 ;
- 模塊: 軟件中某類 或 某個 功能的集合涕烧;如:微信中的月而、通訊錄、發(fā)現(xiàn)议纯、朋友圈 等等景鼠;
- 流程: 軟件中某個功能完整的操作序列;
- 頁面: 應(yīng)用軟件中共享同一界面的所有信息的集合痹扇;
- 組件: 頁面上的每個 獨立的 可視 或者 可交互區(qū)域铛漓;
十二、業(yè)務(wù)代碼的目錄結(jié)構(gòu)
根據(jù) 項目結(jié)構(gòu)的核心思想 和 應(yīng)用軟件用戶界面的結(jié)構(gòu) 鲫构,可知浓恶,業(yè)務(wù)代碼應(yīng)根據(jù) 應(yīng)用軟件用戶界面的結(jié)構(gòu) 來劃分;為了方便引用结笨,對于共用的東西包晰,應(yīng)該抽離出來,放在所有使用者最近的共同祖先目錄中炕吸;
1.項目業(yè)務(wù)邏輯的根目錄
一般伐憾,項目的業(yè)務(wù)代碼均放在 app/
目錄下,
根據(jù) 《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》 赫模,可知:
界面的構(gòu)成元素是:
- 模塊
- 流程
- 頁面
其中树肃,界面單由 頁面 構(gòu)成的情況不多,一般存在于非常簡單的應(yīng)用用瀑罗;所以胸嘴,app/
目錄下主要放置的是各個直接子模塊雏掠、直接流程的目錄;除此之外劣像, app
目錄下還可以放一此公共的 流程乡话、頁面、組件耳奕、資源 等等绑青;如下:
app/ # 應(yīng)用軟件業(yè)務(wù)代碼的根目錄;
├── module1/ # 模塊1的目錄屋群;
├── module2/ # 模塊2的目錄闸婴;
:
├── moduleN/ # 模塊N的目錄;
│
├── flow1/ # 流程1的目錄谓晌;
├── flow2/ # 流程2的目錄掠拳;
:
├── flowN/ # 流程N的目錄;
│
├── publicModule/ # 公共模塊的目錄纸肉;
├── publicFlow/ # 公共流程的目錄溺欧;
├── publicPage/ # 公共頁面的目錄;
├── publicComponent/ # 公共組件的目錄柏肪;
│
├── assets/ # 私有資源文件的目錄姐刁;
└── ...
所以:app目錄是容器目錄 ;
2.模塊的目錄
根據(jù) 《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》 烦味,可知:
模塊的構(gòu)成元素:
- 模塊
- 流程
- 頁面
所以聂使,模塊目錄下主要放置的是各個直接子模塊、直接流程谬俄、直接頁面的目錄柏靶;除此之外, 模塊目錄下還可以放一些公共的 子模塊溃论、流程屎蜓、頁面、組件钥勋、資源 等等炬转;如下:
module/ # 模塊的目錄;
├── subModule1/ # 子模塊1的目錄算灸;
├── subModule2/ # 子模塊2的目錄扼劈;
:
├── subModuleN/ # 子模塊N的目錄;
│
├── flow1/ # 流程1的目錄菲驴;
├── flow2/ # 流程2的目錄荐吵;
:
├── flowN/ # 流程N的目錄;
│
├── publicSubModule/ # 公共子模塊的目錄;
├── publicFlow/ # 公共流程的目錄捍靠;
├── publicPage/ # 公共頁面的目錄沐旨;
├── publicComponent/ # 公共組件的目錄森逮;
│
├── assets/ # 私有資源文件的目錄榨婆;
└── ...
所以:模塊目錄是容器目錄 ;
3.流程的目錄
根據(jù) 《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》 褒侧,可知:
流程的構(gòu)成元素:
- 流程
- 頁面
所以良风,流程目錄下主要放置的是各個直接子流程、頁面的目錄闷供;除此之外烟央, 流程目錄下還可以放一些公共的 子流程、組件歪脏、資源 等等疑俭;如下:
flow/ # 流程的目錄;
├── page1/ # 頁面1的目錄婿失;
├── page2/ # 頁面2的目錄钞艇;
:
├── pageN/ # 頁面N的目錄;
│
├── subFlow1/ # 子流程1的目錄豪硅;
├── subFlow2/ # 子流程2的目錄哩照;
:
├── subFlowN/ # 子流程N的目錄;
│
├── publicSubFlow/ # 公共子流程的目錄懒浮;
├── publicComponent/ # 公共組件的目錄飘弧;
│
├── assets/ # 私有資源文件的目錄;
└── ...
所以:流程目錄是容器目錄 砚著;
4.頁面的目錄
根據(jù) 《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》 次伶,可知:
頁面的構(gòu)成元素:
- 組件
所以,頁面目錄下主要放置的是各個組件的目錄稽穆;除此之外冠王, 頁面目錄下還可以放一些 資源 等等;如下:
page/ # 頁面的目錄秧骑;
├── component1/ # 組件1的目錄版确;
├── component2/ # 組件2的目錄;
:
├── componentN/ # 組件N的目錄乎折;
│
├── assets/ # 私有資源文件的目錄绒疗;
│
├── file1 # 文件1;
├── file2 # 文件2骂澄;
:
├── fileN # 文件N吓蘑;
└── ...
因為,通常頁面本身也是一個組件,所以:頁面目錄是組件目錄 磨镶;且溃蔫,頁面目錄中也可以放一些組件目錄中會有的文件;
5.組件的目錄
根據(jù) 《應(yīng)用軟件界面結(jié)構(gòu)和源碼目錄結(jié)構(gòu)》 琳猫,可知:
組件的構(gòu)成元素:
- 組件
所以伟叛,組件目錄下主要放置的是各個子組件的目錄;除此之外脐嫂, 組件目錄下還可以放一些 文件统刮、資源 等等;如下:
component/ # 組件的目錄账千;
├── subComponent1/ # 子組件1的目錄侥蒙;
├── subComponent2/ # 子組件2的目錄;
:
├── subComponentN/ # 子組件N的目錄匀奏;
│
├── assets/ # 私有資源文件的目錄鞭衩;
│
├── file1 # 文件1;
├── file2 # 文件2娃善;
:
├── fileN # 文件N论衍;
└── ...
很顯然:組件的目錄就是組件目錄 ;
業(yè)務(wù)代碼的目錄結(jié)構(gòu)
綜上所述会放,對于項目的業(yè)務(wù)代碼大致可以規(guī)劃出如下的源碼目錄結(jié)構(gòu):
app/ # 應(yīng)用軟件業(yè)務(wù)代碼的根目錄饲齐;
├── module1/ # 模塊的目錄;
│ ├── subModule1/ # 子模塊1的目錄咧最;
│ ├── subModule2/ # 子模塊2的目錄捂人;
│ :
│ ├── subModuleN/ # 子模塊N的目錄;
│ │
│ ├── flow1/ # 流程的目錄矢沿;
│ │ ├── page1/ # 頁面的目錄滥搭;
│ │ │ ├── component1/ # 組件的目錄;
│ │ │ │ ├── file1 # 文件捣鲸;
│ │ │ │ ├── file2 # 文件瑟匆;
│ │ │ │ └── ...
│ │ │ ├── component2/ # 組件的目錄;
│ │ │ └── ...
│ │ ├── page2/ # 頁面的目錄栽惶;
│ │ │ └── ...
│ │ │
│ │ ├── subFlow1/ # 子流程1的目錄愁溜;
│ │ ├── subFlow2/ # 子流程2的目錄;
│ │ :
│ │ ├── subFlowN/ # 子流程N的目錄外厂;
│ │ └── ...
│ ├── flow2/ # 流程的目錄冕象;
│ │ └── ...
│ └── ...
│
├── module2/ # 模塊的目錄;
│ └── ...
│
├── publicModule/ # 公共模塊的目錄汁蝶;
│ └── ...
├── publicFlow/ # 公共流程的目錄渐扮;
│ └── ...
├── publicPage/ # 公共頁面的目錄论悴;
│ └── ...
├── publicComponent/ # 公共組件的目錄;
│ └── ...
└── ...
十三墓律、新概念的定義
為了方便描述膀估,我定義了以下概念:
假設(shè): 有 A 和 B 2個模塊,且,在A模塊中使用了B模塊;
則: 稱 A模塊 為 B模塊 的 使用者,B模塊 為 A模塊 的 提供者;
十四辫秧、Vuex的拆分方案
在 Vuex 中,每個應(yīng)用程序一般只有一個 store 我注,而 state停士、mutations、actions瞒窒、getters 等等 的配置是在創(chuàng)建store的地方捺僻,這也使得 mutations、actions 的邏輯集中在一個位置崇裁,雖然 Vuex 提供了 modules 用以支持模塊化匕坯,但它不適用于如下場景:在子模塊中創(chuàng)建全局的 state ! 所以拔稳,Vuex的拆分方案中葛峻,modules 并沒有起致有效的作用;為了解決這個問題巴比,我研究出了如下拆分方案:
1. 分散式Vuex的拆分規(guī)則
-
在 提供者 中導(dǎo)出 存儲著與提供者相關(guān)的 store 配置對象 的數(shù)組:
//B的storeConfigs,是數(shù)組形式术奖,里面是Vuex中正常的store的配置對象 let bStoreConfigs = [ { modules:{}, state:{}, mutations:{}, actions:{}, getters:{}, plugins:[], strict:false } ] //導(dǎo)出 B 的 store 配置數(shù)組 export {bStoreConfigs}
-
在 使用者 中導(dǎo)入 提供者 的 store 配置數(shù)組,并拼接到 使用者 的 store 配置數(shù)組的后面轻绞,然后導(dǎo)出拼接后的 store 配置數(shù)組:
// 導(dǎo)入B的 store 配置數(shù)組 import {bStoreConfigs} from './B.js' // 定義 A 的 store 的配置數(shù)組采记,并拼接 B 的 store 配置數(shù)組 let aStoreConfigs = [ { modules:{}, state:{}, mutations:{}, actions:{}, getters:{}, plugins:[], strict:false }, // 拼接 B 的 store 配置數(shù)組 ...bStoreConfigs ] //導(dǎo)出 拼接后的 的 store 配置數(shù)組 export {aStoreConfigs}
-
在創(chuàng)建 store 的地方,導(dǎo)入 App 根組件的 store 配置數(shù)組政勃,然后用工具函數(shù)
mergeStoreConfigs
把 根組件的 store 配置數(shù)組合并成正常的 store 配置對象唧龄,最后用該對象創(chuàng)建 store:// 導(dǎo)入根組件的 store 配置數(shù)組 import {appStoreConfigs} from './App.js' // 用工具函數(shù) `mergeStoreConfigs` 把 根組件的 store 配置數(shù)組合并成正常的 store 配置對象; let storeOptions = mergeStoreConfigs(appStoreConfigs); // 創(chuàng)建 store const store = new Vuex.Store(storeOptions);
2. 相關(guān)工具函數(shù)的實現(xiàn)
- 把store的配置數(shù)組中所有的配置對象合并成一個store配置對象
mergeStoreConfigs(storeConfigs)
/**
* 把 store 配置數(shù)組中所有的配置對象合并成一個store配置對象
* @param storeConfigs : Array<StoreOptions> store 的配置數(shù)組
* @returns StoreOptions store選項對象
*
*/
function mergeStoreConfigs(storeConfigs) {
if (!(storeConfigs instanceof Array)) {
throw new Error(`storeConfigs必須是數(shù)組類型奸远,但您傳的是:${storeConfigs}`);
}
let storeOptions = storeConfigs.reduce(function (merged, config) {
Object.keys(config).forEach(function (key) {
let configPropValue = config[key];
let mergedPropValue = merged[key];
let newPropValue = null;
// 注意:有些case有 break既棺,有些沒有;這樣寫可能不太好理解懒叛,但比用 if else 節(jié)省了好幾行代碼丸冕,從而提高了性能
switch (key) {
case "plugins": {
newPropValue = [...mergedPropValue, ...configPropValue];
break;
}
case "initState":
case "strict": {
newPropValue = mergedPropValue || configPropValue;
break;
}
case "modules": {
if (mergedPropValue) {
Object.keys(configPropValue).forEach(function (configModulesKey) {
let mergedModule = mergedPropValue[configModulesKey];
if (mergedModule) {
let configModule = configPropValue[configModulesKey];
//遞歸調(diào)用自己來合并modules中的同名 StoreOptions
let moduleStoreOptions = mergeStoreConfigs([mergedModule, configModule]);
configPropValue[configModulesKey] = moduleStoreOptions;
try {
delete mergedPropValue[configModulesKey];
} catch (e) {
}
}
});
}
newPropValue = { ...mergedPropValue, ...configPropValue };
break;
}
case "state": {
if (typeof configPropValue == "function") {
configPropValue = configPropValue();
}
newPropValue = { ...mergedPropValue, ...configPropValue };
break;
}
default: {
newPropValue = { ...mergedPropValue, ...configPropValue };
}
}
merged[key] = newPropValue;
});
return merged;
}, {});
return storeOptions;
}
- 用初始 state 配置 store的選項對象
configStoreOptionsWhitInitState(storeOptions, initState, edulcoration)
/**
* 用初始 state 配置 store的選項對象
* @param storeOptions : StoreOptions store的選項對象
* @param initState : State 初始state
* @param edulcoration : boolean 可選;是否除雜芍瑞,即:是否需要忽略 storeOptions 的 state 中沒有的屬性 晨仑;
* @returns StoreConfig 返回帶有初始 state 的配置對象
*
*
* 說明:
* 由于模塊 modules 中的 state 會覆蓋全局的 state ,所以,通過把初始 state 傳給全局的 state 的方式不能達到給模塊設(shè)置初始值的目的洪己;
* 此方法就是為解決這個問題而來的妥凳;
*
* 注意:
* 如果某些模塊 module 不想用入?yún)?initState 中的 state 作為初始值,只用 module 本身設(shè)置的 state 作為初始值答捕,則可以在該模塊的 storeOptions 對象中增加一個布爾類型的選項 initState 逝钥,其值為 true ,即可拱镐;
* initState 選項表示:是否用該 storeOptions 中配置的 state 作為初始的 state
*
* 示例:
* {
* modules:{},
* state:{},
* mutations:{},
* actions:{},
* getters:{},
* plugins:[],
* strict:false,
* initState:true
* }
*
*/
function configStoreOptionsWhitInitState(storeOptions, initState, edulcoration) {
if (initState) {
let modules = storeOptions.modules;
if (modules) {
Object.keys(modules).forEach(function (key) {
let subStoreOptions = modules[key];
let subInitState = initState[key];
subStoreOptions = configStoreOptionsWhitInitState(subStoreOptions, subInitState,edulcoration);
modules[key] = subStoreOptions;
try {
delete initState[key];
} catch (e) {
}
});
storeOptions.modules = modules;
}
let configState = storeOptions.state;
if (edulcoration){
if (configState && !storeOptions.initState) {
let configStateKeys = Object.keys(configState) ;
storeOptions.state = Object.assignKeys(configState,configStateKeys,initState) ;
}
}else {
if (storeOptions.initState) {
storeOptions.state = {...initState, ...configState};
} else {
storeOptions.state = {...configState, ...initState};
}
}
}
return storeOptions;
}
- 綜合mergeStoreConfigs和configStoreOptionsWhitInitState
mergeStoreConfigsWhitInitState(storeConfigs,initState)
/**
* 把 store 配置數(shù)組中所有的配置對象合并成一個帶有初始 statestore配置對象
* @param storeConfigs : Array<StoreOptions> store 的配置數(shù)組
* @param initState : State 初始state
* @returns StoreOptions 返回帶有初始 state 的選項對象
*/
function mergeStoreConfigsWhitInitState (storeConfigs, initState) {
if (!(storeConfigs instanceof Array)) {
throw new Error(`storeConfigs必須是數(shù)組類型艘款,但您傳的是:${storeConfigs}`);
}
let initStoreConfigs = storeConfigs.map((storeConf)=>{
return configStoreOptionsWhitInitState(storeConf, initState, true);
});
let storeOptions = mergeStoreConfigs(initStoreConfigs);
return storeOptions;
}
十五、vue-router的拆分方案
vue-router 是將路由規(guī)則集中在一個位置沃琅,這使它們與布局組件分離哗咆。以下是 vue-router 的核心思想(以下稱為 集中式路由思想
):
- 路由集中在一個地方;
- 布局和頁面嵌套是通過路由的配置對象的嵌套而來的益眉;
- 布局和頁面組件是完全純粹的晌柬,它們是路由的一部分;
這與早期版本 ReactRouter 的思想是一樣的郭脂,但是它與我們的 項目結(jié)構(gòu)的核心思想
相違背年碘;
根據(jù) 項目結(jié)構(gòu)的核心思想
,我們的路由的核心思想(以下稱為 分散式路由思想
)應(yīng)該是:
- 路由分散在組件中展鸡;組件負責它自已的
<router-view>
的路由配置屿衅;即:組件負責它自身的路由配置; - 路由的嵌套是通過組件的嵌套而來的莹弊;
- 布局由組件負責涤久,路由是組件的一部分;
這個思想與新版本的 ReactRouter (react-router-dom) 的思想是一致的箱硕;
顯然拴竹,若要對當前 vue-router 實現(xiàn) 分散式路由思想
,則需要對路由配置進行拆分剧罩;為此栓拜,并且兼顧了表意明確,我定制了如下拆分規(guī)則:
分散式vue-router的拆分規(guī)則
-
在 提供者 中導(dǎo)出 提供者的子路由配置數(shù)組:
export let bChildRoutes = [ { path:bSubPath1, component:BSubComponent }, { path:bSubPath2, component:BSubComponent2 }, { path:bSubPath3, component:BSubComponentN } ];
-
在 使用者 中導(dǎo)入 提供者 的子路由配置數(shù)組惠昔,并根據(jù)如下情況配置 使用者 的子路由配置數(shù)組:
-
如果使用者是組件目錄的配置文件 或 組件的配置文件幕与,則將提供者的子路由作為使用者的子路由的子路由配置數(shù)組進行配置;
import {B,bChildRoutes} from './B.js'; export let aChildRoutes = [ { path:bPath, component:B, children:bChildRoutes }, { path:aSubPath1, component:ASubComponent1 }, { path:aSubPath2, component:ASubComponent2 }, { path:aSubPath3, component:ASubComponentN } ];
-
如果使用者是容器目錄的配置文件镇防,則將提供者的子路由作為使用者的子路由配置數(shù)組中的元素進行配置啦鸣;
import {B,bChildRoutes} from './B.js'; export let aChildRoutes = [ { path:aSubPath1, component:ASubComponent1 }, { path:aSubPath2, component:ASubComponent2 }, { path:aSubPath3, component:ASubComponentN }, ...bChildRoutes ];
-
-
在創(chuàng)建 router 的地方,導(dǎo)入 App 根組件的子路由配置數(shù)組来氧,并將其作為 router 配置對象中的
routes
字段的值來創(chuàng)建 router 實例:// 導(dǎo)入根組件的子路由配置數(shù)組 import {appChildRoutes} from './App.js' // 創(chuàng)建 router const router = new VueRouter({ routes:appChildRoutes });
十六诫给、組件的vue-router和Vuex的配置對象的安放位置
通過上面的拆分方案香拉,vue-router 和 Vuex 的配置對象都可以分散到各個組件的目錄中,但是中狂,這些配置對象具體是寫在組件的vue文件中還是寫在單獨的js文件中凫碌,這一點還未確定;關(guān)于這點胃榕,有以下幾個方案盛险,但各有優(yōu)缺點,具體如下:
注意: 本規(guī)范采用方案4:匯總到單獨的JS文件
方案1:集中在vue文件
這種方案是把組件的 vue-router 和 Vuex 的配置對象都寫在vue文件中勋又;
優(yōu)點:
- 在導(dǎo)入組件時苦掘,只需要寫一個導(dǎo)入語句,但可以分別導(dǎo)入組件楔壤、vue-router鹤啡、Vuex 的各個配置對象:
- 不需要額外創(chuàng)建文件;
缺點:
- vue文件的內(nèi)容臃腫挺邀;
- 不能通過 Vue 的 ESlint 檢查揉忘;
vue 的ESLint 檢查太嚴格,不過可以禁用Vue的ESLint來解決這個問題端铛; - 不能用于異步加載組件的情景;
當使用異步加載組件場景中疲眷,由于 vue-router 和 Vuex 的配置對象不能及時提供禾蚕,會導(dǎo)致一些問題;
方案2:分散到單獨的JS文件
這種方案是把組件的 vue-router 和 Vuex 的配置對象分別寫在單獨的JS文件中狂丝;
優(yōu)點:
- 避免了vue文件的內(nèi)容臃腫换淆;
- 能通過 Vue 的 ESlint 檢查;
- 能用于異步加載組件的情景几颜;
缺點:
- 在導(dǎo)入組件時倍试,需要寫3個導(dǎo)入語句用來分別導(dǎo)入組件、vue-router蛋哭、Vuex 的配置對象县习;
- 需要額外創(chuàng)建2個js文件(vue-router的配置文件、Vuex的配置文件)谆趾;
方案3:分散到單獨的JS文件躁愿,然后再匯總導(dǎo)出
這種方案是把組件的 vue-router 和 Vuex 的配置對象分別寫在單獨的JS文件中;
優(yōu)點:
- 避免了vue文件的內(nèi)容臃腫沪蓬;
- 能通過 Vue 的 ESlint 檢查彤钟;
- 能用于異步加載組件的情景;
- 在導(dǎo)入組件時跷叉,需要寫1個或者2個(異步組件場景中需要分別導(dǎo)入配置對象 和 組件)導(dǎo)入語句就可以導(dǎo)入組件逸雹、vue-router营搅、Vuex 的配置對象;
- 合理 webpack 的配置字段
resolve.mainFiles
和resolve.extensions
梆砸,可以使導(dǎo)入更方便剧防;
缺點:
- 需要額外創(chuàng)建3個js文件(vue-router的配置文件、Vuex的配置文件辫樱、匯總導(dǎo)出的文件)峭拘;
方案4:匯總到單獨的JS文件
這種方案是把組件的 vue-router 和 Vuex 的配置對象寫在一個單獨的JS文件中;
優(yōu)點:
- 避免了vue文件的內(nèi)容臃腫狮暑;
- 能通過 Vue 的 ESlint 檢查鸡挠;
- 能用于異步加載組件的情景;
- 在導(dǎo)入組件時搬男,需要寫1個或者2個(異步組件場景中需要分別導(dǎo)入配置對象 和 組件)導(dǎo)入語句就可以導(dǎo)入組件拣展、vue-router、Vuex 的配置對象缔逛;
- 合理 webpack 的配置字段
resolve.mainFiles
和resolve.extensions
备埃,可以使導(dǎo)入更方便;
缺點:
- 需要額外創(chuàng)建1個js文件褐奴;
十七按脚、組件的分類
為了允分體現(xiàn)《優(yōu)秀代碼的原則》,應(yīng)該把項目的業(yè)務(wù)邏輯抽離出來敦冬,所以辅搬,業(yè)務(wù)邏輯與UI應(yīng)該是分離開的、松耦合的脖旱;為此堪遂,借鑒 ReactRedux
的組件分離思想,把組件分為2類: 容器組件 和 展示組件 萌庆;
- 展示組件 : 只負責展示UI溶褪,不負責業(yè)務(wù)邏輯,任何與業(yè)務(wù)相關(guān)的數(shù)據(jù)践险、回調(diào)都通過組件接口(如:props猿妈、event、slot等等)傳遞捏境;
- 容器組件 : 只負責業(yè)務(wù)邏輯于游,不負責UI,任何與UI相關(guān)的展示垫言、邏輯都通過 展示組件 來完成贰剥;
容器組件 和 展示組件 的區(qū)別如下:
組件類別 | 展示組件 | 容器組件 |
---|---|---|
作用 | 展示UI(如:布局、樣式) | 處理業(yè)務(wù)邏輯(如:數(shù)據(jù)獲取) |
直接使用Vuex | 否 | 是 |
數(shù)據(jù)來源 | 組件接口(如:props筷频、slot等等) | Vuex |
數(shù)據(jù)修改 | 發(fā)送event | 向 Vuex 派發(fā)action 或 提交mutation |
十八蚌成、組件結(jié)構(gòu)規(guī)范
- 根據(jù)組件的分離思想前痘,把組件分為:容器組件 和 展示組件;
- 組件的 主vue模塊担忧、主js模塊 和 主樣式模塊 均用組件的名字作為文件名芹缔;
- 使用
組件的vue-router和Vuex的配置對象的安放位置/方案4:匯總到單獨的JS文件
來組織組件的 vue-router 和 Vuex 的配置對象,把組件的 vue-router 和 Vuex 的配置對象寫到config.js
文件中瓶盛; - 對于經(jīng)常需要導(dǎo)出的東西最欠,需要按照統(tǒng)一的格式規(guī)范命名導(dǎo)出的名字,目前已有如下導(dǎo)出格式規(guī)范:
-
<組件名>ChildRoutes
:組件的子路由配置數(shù)組惩猫; 類型:Array<Route>芝硬; -
<組件名>StoreConfigs
:組件的 store 配置數(shù)組; 類型:Array<StoreOptions>轧房;
-
1. 容器組件結(jié)構(gòu)規(guī)范
- 以容器組件為單位拌阴,為每個容器組件創(chuàng)建以組件名為名的獨立目錄,容器組件目錄的層級結(jié)構(gòu) 要與 容器組件 的組件層級結(jié)構(gòu)對應(yīng)奶镶;
- 容器組件目錄下有以下幾個目錄:
container/ : 容器組件的組件目錄迟赃; ├── assets/ : 容器組件所特有的除代碼以外的資源的容器目錄; ├── component/ : 容器組件所特有的展示組件的容器目錄厂镇; ├── Container.vue : 容器組件的vue模塊纤壁; ├── config.js : 容器組件vue-router和Vuex的配置模塊; ├── <容器目錄1> : 用于放置某類東西的容器目錄剪撬; ├── <容器目錄2> : 用于放置某類東西的容器目錄摄乒; : : ├── <容器目錄N> : 用于放置某類東西的容器目錄; : : ├── subContainer1/ : 子容器組件1的組件目錄残黑; ├── subContainer2/ : 子容器組件2的組件目錄; : : └── subContainerN/ : 子容器組件N的組件目錄斋否;
- 容器組件的 config.js 模塊中定義并導(dǎo)出(如果有的話)以下內(nèi)容:
-
<組件名>ChildRoutes
:組件的子路由配置數(shù)組梨水; 類型:Array<Route>; -
<組件名>StoreConfigs
:組件的 store 配置數(shù)組茵臭; 類型:Array<StoreOptions>疫诽;
-
- 容器組件所有特有的展示組件需要放在該容器組件的子目錄
component
中;
2. 展示組件結(jié)構(gòu)規(guī)范
- 展示組件的 config.js 模塊定義并導(dǎo)出(如果有的話)以下內(nèi)容:
-
<組件名>ChildRoutes
:組件的子路由配置數(shù)組旦委; 類型:Array<Route>奇徒;
-
- 如果展示組件的邏輯或結(jié)構(gòu)比較復(fù)雜,則展示組件也可以有自己獨立的目錄缨硝;
- 如果展示組件有自己的獨立目錄摩钙,則展示組件的目錄結(jié)構(gòu)應(yīng)該如下:
performer/ : 展示組件的組件目錄; ├── assets/ : 展示組件所特有的除代碼以外的資源的容器目錄查辩; ├── Performer.vue : 展示組件的vue模塊胖笛; ├── config.js : 展示組件vue-router的配置模塊网持; ├── <容器目錄1> : 用于放置某類東西的容器目錄; ├── <容器目錄2> : 用于放置某類東西的容器目錄长踊; : : ├── <容器目錄N> : 用于放置某類東西的容器目錄功舀; : : ├── subPerformer1 : 子展示組件1的模塊 或 組件目錄; ├── subPerformer2 : 子展示組件2的模塊 或 組件目錄身弊; : : └── subPerformerN : 子展示組件N的模塊 或 組件目錄辟汰;
- 展示組件的 config.js 模塊中不應(yīng)該定義以下內(nèi)容:
-
<組件名>StoreConfigs
:組件的 store 配置數(shù)組; 類型:Array<StoreOptions>阱佛;
-