隨着業務的不斷累積,目前咱們 ToC 端
主要項目,除去 node_modules
, build 配置文件
,dist 靜態資源文件
的代碼量爲 137521
行,後臺管理系統下各個子應用代碼,除去依賴等文件的總行數也達到 100萬
多一點。css
代碼量意味不了什麼,只能證實模塊不少,但相同兩個項目,在
運行時性能相同
狀況下,你的10 萬
行代碼能容納並維護150
個模塊,而且開發順暢,個人項目中10 萬
行代碼卻只能容納100
個模塊,添加功能也好,維護起來也較爲繁瑣,這就很值得思考。html
本文會在主要描述以 Vue 技術棧
爲技術主體
,ToC 端
項目業務主體
,在構建過程當中,遇到或者總結的點(也會說起一些 ToB 項目的場景),可能並不適合你的業務場景(僅供參考),我會盡量多的描述問題與其中的思考,最大可能的幫助到須要的同窗,也辛苦開發者發現問題或者不合理/不正確的地方及時向我反饋,會盡快修改,歡迎有更好的實現方式來 pr
。前端
能夠參考螞蟻金服數據體驗技術團隊
編寫的文章:vue
本文並非基於上面文章寫的,不過當時在看到他們文章以後以爲有類似的地方,相較於這篇文章,本文可能會枯燥些,會有大量代碼,同窗能夠直接用上倉庫看。node
首先要思考咱們的項目最終的構建主體
是單頁面
,仍是多頁面
,仍是單頁 + 多頁
,經過他們的優缺點來分析:webpack
懶加載
可有效減小首頁白屏時間,相較於多頁面
減小了用戶訪問靜態資源服務器的次數等。懶加載
也有他的弊端,不作特殊處理不利於 SEO 等。URL
,cookie
,storage
等方式,較爲侷限。老 MPA 項目遷移至 SPA 的狀況
,缺點結合二者,兩種主體通訊方式也只能以兼容MPA 爲準
HTML 串
的狀況下),想保證用戶體驗在 SPA 中開發一個頁面,在 MPA 中也開發一個頁面,去掉沒用的依賴,或者直接用原生 JS 來開發,分享出去是 MPA 的文章頁面,這樣能夠加快分享出去的打開速度,同時也能減小靜態資源服務器的壓力,由於若是分享出去的是 SPA 的文章頁面,那 SPA 所需的靜態資源至少都須要去進行協商請求
,固然若是服務配置了強緩存就忽略以上所說。咱們首先根據業務所需,來最終肯定構建主體
,而咱們選擇了體驗至上的 SPA
,並選用 Vue
技術棧。ios
其實咱們看開源的絕大部分項目中,目錄結構都會差不太多,咱們能夠綜合一下來個通用的 src
目錄:git
src
├── assets // 資源目錄 圖片,樣式,iconfont
├── components // 全局通用組件目錄
├── config // 項目配置,攔截器,開關
├── plugins // 插件相關,生成路由、請求、store 等實例,並掛載 Vue 實例
├── directives // 拓展指令集合
├── routes // 路由配置
├── service // 服務層
├── utils // 工具類
└── views // 視圖層
複製代碼
components
中咱們會存放 UI 組件庫中的那些常見通用組件了,在項目中直接經過設置別名
來使用,若是其餘項目須要使用,就發到 npm
上。es6
// components 簡易結構
components
├── dist
├── build
├── src
├── modal
├── toast
└── ...
├── index.js
└── package.json
複製代碼
若是想最終編譯成 es5
,直接在 html 中使用或者部署 CDN 上,在 build
配置簡單的打包邏輯,搭配着 package.json
構建 UI組件 的自動化打包發佈,最終部署 dist
下的內容,併發布到 npm
上便可。github
而咱們也可直接使用 es6
的代碼:
import 'Components/src/modal'
複製代碼
假設咱們發佈的 npm 包
叫 bm-ui
,而且下載到了本地 npm i bm-ui -S
:
修改項目的最外層打包配置,在 rules 裏 babel-loader
或 happypack
中添加 include
,node_modules/bm-ui
:
// webpack.base.conf
...
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
// 這裏添加
include: [resolve('src'), resolve('test'), resolve('node_modules/bm-ui')]
},{
...
}]
...
複製代碼
而後搭配着 babel-plugin-import
直接在項目中使用便可:
import { modal } from 'bm-ui'
複製代碼
同時有多個組件庫的話,又或者有同窗專門進行組件開發的話,把 components 內部細分
一下,多一個文件分層。
components
├── bm-ui-1
├── bm-ui-2
└── ...
複製代碼
你的打包配置文件能夠放在 components
下,進行統一打包,固然若是要開源出去仍是放在對應庫下。
這個點其實會是項目中常常被忽略的,或者說不多聚合到一塊兒,但同時我認爲是整個項目中的重要之一,後續會有例子說道。
config
├── index.js // 全局配置/開關
├── interceptors // 攔截器
├── index.js // 入口文件
├── axios.js // 請求/響應攔截
├── router.js // 路由攔截
└── ...
└── ...
複製代碼
咱們在 config/index.js
可能會有以下配置:
// config/index.js
// 當前宿主平臺 兼容多平臺應該經過一些特定函數來取得
export const HOST_PLATFORM = 'WEB'
// 這個就很少說了
export const NODE_ENV = process.env.NODE_ENV || 'prod'
// 是否強制全部請求訪問本地 MOCK,看到這裏同窗不難猜到,每一個請求也能夠單獨控制是否請求 MOCK
export const AJAX_LOCALLY_ENABLE = false
// 是否開啓監控
export const MONITOR_ENABLE = true
// 路由默認配置,路由表並不今後注入
export const ROUTER_DEFAULT_CONFIG = {
waitForData: true,
transitionOnLoad: true
}
// axios 默認配置
export const AXIOS_DEFAULT_CONFIG = {
timeout: 20000,
maxContentLength: 2000,
headers: {}
}
// vuex 默認配置
export const VUEX_DEFAULT_CONFIG = {
strict: process.env.NODE_ENV !== 'production'
}
// API 默認配置
export const API_DEFAULT_CONFIG = {
mockBaseURL: '',
mock: true,
debug: false,
sep: '/'
}
// CONST 默認配置
export const CONST_DEFAULT_CONFIG = {
sep: '/'
}
// 還有一些業務相關的配置
// ...
// 還有一些方便開發的配置
export const CONSOLE_REQUEST_ENABLE = true // 開啓請求參數打印
export const CONSOLE_RESPONSE_ENABLE = true // 開啓響應參數打印
export const CONSOLE_MONITOR_ENABLE = true // 監控記錄打印
複製代碼
能夠看出這裏聚集了項目中全部用到的配置,下面咱們在 plugins
中實例化插件,注入對應配置,目錄以下:
plugins
├── api.js // 服務層 api 插件
├── axios.js // 請求實例插件
├── const.js // 服務層 const 插件
├── store.js // vuex 實例插件
├── inject.js // 注入 Vue 原型插件
└── router.js // 路由實例插件
複製代碼
這裏先舉出兩個例子,看咱們是如何注入配置,攔截器並實例化的
實例化 router
:
import Vue from 'vue'
import Router from 'vue-router'
import ROUTES from 'Routes'
import {ROUTER_DEFAULT_CONFIG} from 'Config/index'
import {routerBeforeEachFunc} from 'Config/interceptors/router'
Vue.use(Router)
// 注入默認配置和路由表
let routerInstance = new Router({
...ROUTER_DEFAULT_CONFIG,
routes: ROUTES
})
// 注入攔截器
routerInstance.beforeEach(routerBeforeEachFunc)
export default routerInstance
複製代碼
實例化 axios
:
import axios from 'axios'
import {AXIOS_DEFAULT_CONFIG} from 'Config/index'
import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from 'Config/interceptors/axios'
let axiosInstance = {}
axiosInstance = axios.create(AXIOS_DEFAULT_CONFIG)
// 注入請求攔截
axiosInstance
.interceptors.request.use(requestSuccessFunc, requestFailFunc)
// 注入響應攔截
axiosInstance
.interceptors.response.use(responseSuccessFunc, responseFailFunc)
export default axiosInstance
複製代碼
咱們在 main.js
注入插件:
// main.js
import Vue from 'vue'
GLOBAL.vbus = new Vue()
// import 'Components'// 全局組件註冊
import 'Directives' // 指令
// 引入插件
import router from 'Plugins/router'
import inject from 'Plugins/inject'
import store from 'Plugins/store'
// 引入組件庫及其組件庫樣式
// 不須要配置的庫就在這裏引入
// 若是須要配置都放入 plugin 便可
import VueOnsen from 'vue-onsenui'
import 'onsenui/css/onsenui.css'
import 'onsenui/css/onsen-css-components.css'
// 引入根組件
import App from './App'
Vue.use(inject)
Vue.use(VueOnsen)
// render
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
複製代碼
axios
實例咱們並無直接引用,相信你也猜到他是經過 inject
插件引用的,咱們看下 inject
:
import axios from './axios'
import api from './api'
import consts from './const'
GLOBAL.ajax = axios
export default {
install: (Vue, options) => {
Vue.prototype.$api = api
Vue.prototype.$ajax = axios
Vue.prototype.$const = consts
// 須要掛載的都放在這裏
}
}
複製代碼
這裏能夠掛載你想在業務中( vue
實例中)便捷訪問的 api
,除了 $ajax
以外,api
和 const
兩個插件是咱們服務層中主要的功能,後續會介紹,這樣咱們插件流程大體運轉起來,下面寫對應攔截器的方法。
在ajax 攔截器
中(config/interceptors/axios.js
):
// config/interceptors/axios.js
import {CONSOLE_REQUEST_ENABLE, CONSOLE_RESPONSE_ENABLE} from '../index.js'
export function requestSuccessFunc (requestObj) {
CONSOLE_REQUEST_ENABLE && console.info('requestInterceptorFunc', `url: ${requestObj.url}`, requestObj)
// 自定義請求攔截邏輯,能夠處理權限,請求發送監控等
// ...
return requestObj
}
export function requestFailFunc (requestError) {
// 自定義發送請求失敗邏輯,斷網,請求發送監控等
// ...
return Promise.reject(requestError);
}
export function responseSuccessFunc (responseObj) {
// 自定義響應成功邏輯,全局攔截接口,根據不一樣業務作不一樣處理,響應成功監控等
// ...
// 假設咱們請求體爲
// {
// code: 1010,
// msg: 'this is a msg',
// data: null
// }
let resData = responseObj.data
let {code} = resData
switch(code) {
case 0: // 若是業務成功,直接進成功回調
return resData.data;
case 1111:
// 若是業務失敗,根據不一樣 code 作不一樣處理
// 好比最多見的受權過時跳登陸
// 特定彈窗
// 跳轉特定頁面等
location.href = xxx // 這裏的路徑也能夠放到全局配置裏
return;
default:
// 業務中還會有一些特殊 code 邏輯,咱們能夠在這裏作統一處理,也能夠下方它們到業務層
!responseObj.config.noShowDefaultError && GLOBAL.vbus.$emit('global.$dialog.show', resData.msg);
return Promise.reject(resData);
}
}
export function responseFailFunc (responseError) {
// 響應失敗,可根據 responseError.message 和 responseError.response.status 來作監控處理
// ...
return Promise.reject(responseError);
}
複製代碼
定義路由攔截器
(config/interceptors/router.js
):
// config/interceptors/router.js
export function routerBeforeFunc (to, from, next) {
// 這裏能夠作頁面攔截,不少後臺系統中也很是喜歡在這裏面作權限處理
// next(...)
}
複製代碼
最後在入口文件(config/interceptors/index.js)
中引入並暴露出來便可:
import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from './ajax'
import {routerBeforeEachFunc} from './router'
let interceptors = {
requestSuccessFunc,
requestFailFunc,
responseSuccessFunc,
responseFailFunc,
routerBeforeEachFunc
}
export default interceptors
複製代碼
請求攔截這裏代碼都很簡單,對於 responseSuccessFunc
中 switch default
邏輯作下簡單說明:
responseObj.config.noShowDefaultError
這裏可能不太好理解咱們在請求的時候,能夠傳入一個 axios 中並無意義的 noShowDefaultError
參數爲咱們業務所用,當值爲 false 或者不存在時,咱們會觸發全局事件 global.dialog.show
,global.dialog.show
咱們會註冊在 app.vue
中:
// app.vue
export default {
...
created() {
this.bindEvents
},
methods: {
bindEvents() {
GLOBAL.vbus.$on('global.dialog.show', (msg) => {
if(msg) return
// 咱們會在這裏註冊全局須要操控試圖層的事件,方便在非業務代碼中經過發佈訂閱調用
this.$dialog.popup({
content: msg
});
})
}
...
}
}
複製代碼
這裏也能夠把彈窗狀態放入
Store
中,按團隊喜愛,咱們習慣把公共的涉及視圖邏輯的公共狀態在這裏註冊,和業務區分開來。
GLOBAL
是咱們掛載 window
上的全局對象
,咱們把須要掛載的東西都放在 window.GLOBAL
裏,減小命名空間衝突的可能。vbus
其實就是咱們開始 new Vue()
掛載上去的GLOBAL.vbus = new Vue()
複製代碼
Promise.reject
出去,咱們就能夠在 error
回調裏面只處理咱們的業務邏輯,而其餘如斷網
、超時
、服務器出錯
等均經過攔截器進行統一處理。對比下處理先後在業務中的發送請求的代碼
:
攔截器處理前:
this.$axios.get('test_url').then(({code, data}) => {
if( code === 0 ) {
// 業務成功
} else if () {}
// em... 各類業務不成功處理,若是遇到通用的處理,還須要抽離出來
}, error => {
// 須要根據 error 作各類抽離好的處理邏輯,斷網,超時等等...
})
複製代碼
攔截器處理後:
// 業務失敗走默認彈窗邏輯的狀況
this.$axios.get('test_url').then(({data}) => {
// 業務成功,直接操做 data 便可
})
// 業務失敗自定義
this.$axios.get('test_url', {
noShowDefaultError: true // 可選
}).then(({data}) => {
// 業務成功,直接操做 data 便可
}, (code, msg) => {
// 當有特定 code 須要特殊處理,傳入 noShowDefaultError:true,在這個回調處理就行
})
複製代碼
在應對項目開發過程當中需求的不可預見性時,讓咱們能處理的更快更好
到這裏不少同窗會以爲,就這麼簡單的引入判斷,無關緊要, 就如咱們最近作的一個需求來講,咱們 ToC 端項目以前一直是在微信公衆號中打開的,而咱們須要在小程序中經過 webview 打開大部分流程,而咱們也沒有時間,沒有空間
在小程序中重寫近 100 + 的頁面流程,這是咱們開發之初並無想到的。這時候必須把項目兼容到小程序端,在兼容過程當中可能須要解決如下問題:
api
,在小程序中無用,須要調用小程序的邏輯,須要作兼容。能夠看出,稍微不慎,會影響公衆號現有邏輯。
interceptors/minaAjax.js
, interceptors/minaRouter.js
,原有的換更爲 interceptors/officalAjax.js
,interceptors/officalRouter.js
,在入口文件interceptors/index.js
,根據當前宿主平臺
,也就是全局配置 HOST_PLATFORM
,經過代理模式
和策略模式
,注入對應平臺的攔截器,在minaAjax.js
中重寫請求路徑和權限處理,在 minaRouter.js
中添加頁面攔截配置,跳轉到特定頁面,這樣一併解決了上面的問題 1,2,3
。問題 4
其實也比較好處理了,拷貝須要兼容 api
的頁面,重寫裏面的邏輯,經過路由攔截器一併作跳轉處理
。問題 5
也很簡單,拓展兩個自定義指令 v-mina-show 和 v-mina-hide ,在展現不一樣步的地方能夠直接使用指令。最終用最少的代碼,最快的時間完美上線,絲毫沒影響到現有 toC 端業務,並且這樣把全部兼容邏輯絕大部分聚合到了一塊兒,方便二次拓展和修改。
雖然這只是根據自身業務結合來講明,可能沒什麼說服力,不過不難看出全局配置/攔截器 雖然代碼很少,但倒是整個項目的核心之一,咱們能夠在裏面作更多 awesome
的事情。
directives
裏面沒什麼可說的,不過不少難題均可以經過他來解決,要時刻記住,咱們能夠再指令裏面操做虛擬 DOM。
而咱們根據本身的業務性質,最終根據業務流程來拆分配置:
routes
├── index.js // 入口文件
├── common.js // 公共路由,登陸,提示頁等
├── account.js // 帳戶流程
├── register.js // 掛號流程
└── ...
複製代碼
最終經過 index.js 暴露出去給 plugins/router
實例使用,這裏的拆分配置有兩個注意的地方:
業務線
劃分,有的項目更適合以 功能
劃分。文章開頭說到單頁面靜態資源過大,首次打開/每次版本升級
後都會較慢,能夠用懶加載
來拆分靜態資源,減小白屏時間,但開頭也說到懶加載
也有待商榷的地方:
這就須要咱們根據項目狀況在空間和時間
上作一些權衡。
如下幾點能夠做爲簡單的參考:
公司後臺管理系統
中,能夠以操做 view 爲單位進行異步加載,通用組件所有同步加載的方式。功能模塊拆分
進行異步組件加載。打包出來的 main.js 的大小,絕大部分都是在路由中引入的並註冊的視圖組件。
服務層做爲項目中的另外一個核心之一,「自古以來」都是你們比較關心的地方。
不知道你是否看到過以下組織代碼方式:
views/
pay/
index.vue
service.js
components/
a.vue
b.vue
複製代碼
在 service.js
中寫入編寫數據來源
export const CONFIAG = {
apple: '蘋果',
banana: '香蕉'
}
// ...
// ① 處理業務邏輯,還彈窗
export function getBInfo ({name = '', id = ''}) {
return this.$ajax.get('/api/info', {
name,
id
}).then({age} => {
this.$modal.show({
content: age
})
})
}
// ② 不處理業務,僅僅寫請求方法
export function getAInfo ({name = '', id = ''}) {
return this.$ajax.get('/api/info', {
name,
id
})
}
...
複製代碼
簡單分析:
我相信②在絕大多數項目中都能看到。
那麼咱們的目的就很明顯了,解決冗餘,方便使用,咱們把枚舉和請求接口的方法,經過插件,掛載到一個大對象上,注入 Vue 原型,方面業務使用便可。
service
├── api
├── index.js // 入口文件
├── order.js // 訂單相關接口配置
└── ...
├── const
├── index.js // 入口文件
├── order.js // 訂單常量接口配置
└── ...
├── store // vuex 狀態管理
├── expands // 拓展
├── monitor.js // 監控
├── beacon.js // 打點
├── localstorage.js // 本地存儲
└── ... // 按需拓展
└── ...
複製代碼
首先抽離請求接口模型,可按照領域模型抽離
(service/api/index.js
):
{
user: [{
name: 'info',
method: 'GET',
desc: '測試接口1',
path: '/api/info',
mockPath: '/api/info',
params: {
a: 1,
b: 2
}
}, {
name: 'info2',
method: 'GET',
desc: '測試接口2',
path: '/api/info2',
mockPath: '/api/info2',
params: {
a: 1,
b: 2,
b: 3
}
}],
order: [{
name: 'change',
method: 'POST',
desc: '訂單變動',
path: '/api/order/change',
mockPath: '/api/order/change',
params: {
type: 'SUCCESS'
}
}]
...
}
複製代碼
定製下須要的幾個功能:
定製好功能,開始編寫簡單的 plugins/api.js
插件:
import axios from './axios'
import _pick from 'lodash/pick'
import _assign from 'lodash/assign'
import _isEmpty from 'lodash/isEmpty'
import { assert } from 'Utils/tools'
import { API_DEFAULT_CONFIG } from 'Config'
import API_CONFIG from 'Service/api'
class MakeApi {
constructor(options) {
this.api = {}
this.apiBuilder(options)
}
apiBuilder({ sep = '|', config = {}, mock = false, debug = false, mockBaseURL = '' }) {
Object.keys(config).map(namespace => {
this._apiSingleBuilder({
namespace,
mock,
mockBaseURL,
sep,
debug,
config: config[namespace]
})
})
}
_apiSingleBuilder({ namespace, sep = '|', config = {}, mock = false, debug = false, mockBaseURL = '' }) {
config.forEach( api => {
const {name, desc, params, method, path, mockPath } = api
let apiname = `${namespace}${sep}${name}`,// 命名空間
url = mock ? mockPath : path,//控制走 mock 仍是線上
baseURL = mock && mockBaseURL
// 經過全局配置開啓調試模式。
debug && console.info(`調用服務層接口${apiname},接口描述爲${desc}`)
debug && assert(name, `${apiUrl} :接口name屬性不能爲空`)
debug && assert(apiUrl.indexOf('/') === 0, `${apiUrl} :接口路徑path,首字符應爲/`)
Object.defineProperty(this.api, `${namespace}${sep}${name}`, {
value(outerParams, outerOptions) {
// 請求參數自動截取。
// 請求參數不穿則發送默認配置參數。
let _data = _isEmpty(outerParams) ? params : _pick(_assign({}, params, outerParams), Object.keys(params))
return axios(_normoalize(_assign({
url,
desc,
baseURL,
method
}, outerOptions), _data))
}
})
})
}
}
function _normoalize(options, data) {
// 這裏能夠作大小寫轉換,也能夠作其餘類型 RESTFUl 的兼容
if (options.method === 'POST') {
options.data = data
} else if (options.method === 'GET') {
options.params = data
}
return options
}
// 注入模型和全局配置,並暴露出去
export default new MakeApi({
config: API_CONFIG,
...API_DEFAULT_CONFIG
})['api']
複製代碼
掛載到 Vue 原型
上,上文有說到,經過 plugins/inject.js
import api from './api'
export default {
install: (Vue, options) => {
Vue.prototype.$api = api
// 須要掛載的都放在這裏
}
}
複製代碼
這樣咱們能夠在業務中
愉快的使用業務層代碼:
// .vue 中
export default {
methods: {
test() {
this.$api['order/info']({
a: 1,
b: 2
})
}
}
}
複製代碼
即便在業務以外
也可使用:
import api from 'Plugins/api'
api['order/info']({
a: 1,
b: 2
})
複製代碼
固然對於運行效率要求高
的項目中,避免內存使用率過大
,咱們須要改造 API,用解構的方式引入使用,最終利用 webpack
的 tree-shaking
減小打包體積。幾個簡單的思路
通常來講,多人協做時候你們均可以先看
api
是否有對應接口,當業務量上來的時候,也確定會有人出現找不到,或者找起來比較費勁,這時候咱們徹底能夠在 請求攔截器中,把當前請求的url
和api
中的請求作下判斷,若是有重複接口請求路徑,則提醒開發者已經配置相關請求,根據狀況是否進行二次配置便可。
最終咱們能夠拓展 Service 層的各個功能:
基礎
異步與後端交互
常量枚舉
Vuex
狀態管理拓展
監控
功能,自定義蒐集策略,調用 api
中的接口發送打點
功能,自定義蒐集策略,調用 api
中的接口發送const
,localStorage
,monitor
和 beacon
根據業務自行拓展暴露給業務使用便可,思想也是同樣的,下面着重說下 store(Vuex)
。
插一句:若是看到這裏沒感受不妥的話,想一想上面
plugins/api.js
有沒有用單例模式
?該不應用?
答案是否認的,就算你的項目達到 10 萬行代碼,那也並不意味着你必須使用 Vuex,應該由業務場景決定。
vbus
作好命名空間,來解耦便可。let vbus = new Vue()
vbus.$on('print.hello', () => {
console.log('hello')
})
vbus.$emit('print.hello')
複製代碼
多人協做項目管理
,有道雲筆記
,網易雲音樂
,微信網頁版/桌面版
等應用,功能集中,空間利用率高,實時交互的項目,無疑 Vuex 是較好的選擇
。這類應用中咱們能夠直接抽離業務領域模型
:store
├── index.js
├── actions.js // 根級別 action
├── mutations.js // 根級別 mutation
└── modules
├── user.js // 用戶模塊
├── products.js // 產品模塊
├── order.js // 訂單模塊
└── ...
複製代碼
固然對於這類項目,vuex
或許不是最好的選擇,有興趣的同窗能夠學習下 rxjs
。
後臺系統
或者頁面之間業務耦合不高的項目
,這類項目是佔比應該是很大的,咱們思考下這類項目:全局共享狀態很少,可是不免在某個模塊中會有複雜度較高的功能(客服系統,實時聊天,多人協做功能等),這時候若是爲了項目的可管理性,咱們也在 store
中進行管理,隨着項目的迭代咱們不難遇到這樣的狀況:
store/
...
modules/
b.js
...
views/
...
a/
b.js
...
複製代碼
先梳理咱們的目標:
store
上,想提升運行效率。(冗餘)咱們藉助 Vuex 提供的 registerModule
和 unregisterModule
一併解決這些問題,咱們在 service/store
中放入全局共享的狀態:
service/
store/
index.js
actions.js
mutations.js
getters.js
state.js
複製代碼
通常這類項目全局狀態很少,若是多了拆分 module 便可。
編寫插件生成 store 實例
:
import Vue from 'vue'
import Vuex from 'vuex'
import {VUEX_DEFAULT_CONFIG} from 'Config'
import commonStore from 'Service/store'
Vue.use(Vuex)
export default new Vuex.Store({
...commonStore,
...VUEX_DEFAULT_CONFIG
})
複製代碼
對一個須要狀態管理頁面或者模塊進行分層:
views/
pageA/
index.vue
components/
a.vue
b.vue
...
children/
childrenA.vue
childrenB.vue
...
store/
index.js
actions.js
moduleA.js
moduleB.js
複製代碼
module 中直接包含了 getters
,mutations
,state
,咱們在 store/index.js
中作文章:
import Store from 'Plugins/store'
import actions from './actions.js'
import moduleA from './moduleA.js'
import moduleB from './moduleB.js'
export default {
install() {
Store.registerModule(['pageA'], {
actions,
modules: {
moduleA,
moduleB
},
namespaced: true
})
},
uninstall() {
Store.unregisterModule(['pageA'])
}
}
複製代碼
最終在 index.vue
中引入使用, 在頁面跳轉以前註冊這些狀態和管理狀態的規則,在路由離開以前,先卸載這些狀態和管理狀態的規則:
import store from './store'
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters('pageA', ['aaa', 'bbb', 'ccc'])
},
beforeRouterEnter(to, from, next) {
store.install()
next()
},
beforeRouterLeave(to, from, next) {
store.uninstall()
next()
}
}
複製代碼
固然若是你的狀態要共享到全局,就不執行 uninstall
。
這樣就解決了開頭的三個問題,不一樣開發者在開發頁面的時候,能夠根據頁面特性,漸進加強的選擇某種開發形式。
這裏簡單列舉下其餘方面,須要自行根據項目深刻和使用。
這裏網上已經有不少優化方法:dll
,happypack
,多線程打包
等,但隨着項目的代碼量級,每次 dev 保存的時候編譯的速度也是會越來越慢的,而一過慢的時候咱們就不得不進行拆分,這是確定的,而在拆分以前儘量容納更多的可維護的代碼,有幾個能夠嘗試和規避的點:
moment.js
這樣的庫)等。sass
的話,善用 %placeholder
減小無用代碼打包進來。
MPA 應用
中樣式冗餘過大,%placeholder
也會給你帶來幫助。
不少大公司都有本身的 mock 平臺
,當先後端定好接口格式,放入生成對應 mock api
,若是沒有 mock 平臺,那就找相對好用的工具如 json-server
等。
請強制使用 eslint
,掛在 git 的鉤子上。按期 diff 代碼,按期培訓等。
很是建議用 TS 編寫項目,可能寫 .vue 有些彆扭,這樣前端的大部分錯誤在編譯時解決,同時也能提升瀏覽器運行時效率,可能減小 re-optimize
階段時間等。
這也是項目很是重要的一點,若是你的項目還未使用一些測試工具,請儘快接入,這裏不過多贅述。
當項目到達到必定業務量級時,因爲項目中的模塊過多,新同窗維護成本,開發成本都會直線上升,不得不拆分項目,後續會分享出來咱們 ToB
項目在拆分系統中的簡單實踐。
時下有各類成熟的方案,這裏只是一個簡單的構建分享,裏面依賴的版本都是咱們穩定下來的版本,須要根據本身實際狀況進行升級。
項目底層構建每每會成爲前端忽略的地方,咱們既要從一個大局觀來看待一個項目或者整條業務線,又要對每一行代碼精益求精,對開發體驗不斷優化,慢慢累積後才能更好的應對未知的變化。
若是前端同窗想嘗試使用 Vue
開發 App
,或者熟悉 weex
開發的同窗,能夠來嘗試使用咱們的開源解決方案 eros
,雖然沒作過什麼廣告,但不徹底統計,50 個在線 APP 仍是有的
,期待你的加入。
最後附上部分產品截圖~
(逃~)