Vue項目組織規(guī)范

目錄

  • 一、項目結(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的拆分方案
      1. 分散式Vuex的拆分規(guī)則
      1. 相關(guān)工具函數(shù)的實現(xiàn)
  • 十五虎敦、vue-router的拆分方案
  • 十六、組件的vue-router和Vuex的配置對象的安放位置
    • 方案1:集中在vue文件
    • 方案2:分散到單獨的JS文件
    • 方案3:分散到單獨的JS文件政敢,然后再匯總導(dǎo)出
    • 方案4:匯總到單獨的JS文件
  • 十七其徙、組件的分類
  • 十八、組件結(jié)構(gòu)規(guī)范
      1. 容器組件結(jié)構(gòu)規(guī)范
      1. 展示組件結(jié)構(gòu)規(guī)范

前言

本規(guī)范是我依照 關(guān)注點分離 的思想喷户,針對 vue 項目制定的規(guī)范唾那,如果您在使用當中發(fā)現(xiàn)不當 或 需要完善的地方,您都可以通過以下方式聯(lián)系我摩骨,期待與您的交流:

內(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 中的資源批什;
    示例:
    1. 先將 process.env.BASE_URL 傳遞給組件;
      data () {
        return {
          baseUrl: process.env.BASE_URL
        }
      }
      
    2. 然后农曲,在模板中引用:
      <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/ 目錄:

  1. 在 webpack 的配置字段 resolve.alias 中給 src/common/assets/ 目錄設(shè)置別名:
    webpackConfig.resolve.alias = {
       'c-assets': resolve('src/common/assets'),
     }
    
  2. 通過上面設(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ī)則

  1. 在 提供者 中導(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}
    
  2. 在 使用者 中導(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}
    
  3. 在創(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ī)則

  1. 在 提供者 中導(dǎo)出 提供者的子路由配置數(shù)組:

    export let bChildRoutes = [
        { path:bSubPath1, component:BSubComponent },
        { path:bSubPath2, component:BSubComponent2 },
        { path:bSubPath3, component:BSubComponentN }
    ];
    
  2. 在 使用者 中導(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
      ];
      
  3. 在創(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.mainFilesresolve.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.mainFilesresolve.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>阱佛;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帖汞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瘫絮,更是在濱河造成了極大的恐慌涨冀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麦萤,死亡現(xiàn)場離奇詭異鹿鳖,居然都是意外死亡,警方通過查閱死者的電腦和手機壮莹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門翅帜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人命满,你說我怎么就攤上這事涝滴。” “怎么了胶台?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵歼疮,是天一觀的道長。 經(jīng)常有香客問我诈唬,道長韩脏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任铸磅,我火速辦了婚禮赡矢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阅仔。我一直安慰自己吹散,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布八酒。 她就那樣靜靜地躺著空民,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丘跌。 梳的紋絲不亂的頭發(fā)上袭景,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天唁桩,我揣著相機與錄音,去河邊找鬼耸棒。 笑死荒澡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的与殃。 我是一名探鬼主播单山,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幅疼!你這毒婦竟也來了米奸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤爽篷,失蹤者是張志新(化名)和其女友劉穎悴晰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逐工,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡铡溪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泪喊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棕硫。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袒啼,靈堂內(nèi)的尸體忽然破棺而出哈扮,到底是詐尸還是另有隱情,我是刑警寧澤蚓再,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布滑肉,位于F島的核電站,受9級特大地震影響摘仅,放射性物質(zhì)發(fā)生泄漏赦邻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一实檀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧按声,春花似錦膳犹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渐裂,卻和暖如春豺旬,著一層夾襖步出監(jiān)牢的瞬間钠惩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工族阅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篓跛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓坦刀,卻偏偏與公主長得像愧沟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲤遥,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容