隨着業務的不斷累積,目前咱們 ToC 端
主要項目,除去 node_modules
, build 配置文件
,dist 靜態資源文件
的代碼量爲 137521
行,後臺管理系統下各個子應用代碼,除去依賴等文件的總行數也達到 100萬
多一點。css
代碼量意味不了什麼,只能證實模塊不少,但相同兩個項目,在運行時性能相同
狀況下, 你的10 萬
行代碼能容納並維護150
個模塊,而且開發順暢,個人項目中10 萬
行代碼卻只能容納100
個模塊,添加功能也好,維護起來也較爲繁瑣,這就很值得思考。
本文會在主要描述以 Vue 技術棧
爲技術主體
,ToC 端
項目業務主體
,在構建過程當中,遇到或者總結的點(也會說起一些 ToB 項目的場景),可能並不適合你的業務場景(僅供參考),我會盡量多的描述問題與其中的思考,最大可能的幫助到須要的同窗,也辛苦開發者發現問題或者不合理/不正確的地方及時向我反饋,會盡快修改,歡迎有更好的實現方式來 pr
。html
能夠參考螞蟻金服數據體驗技術團隊
編寫的文章:前端
本文並非基於上面文章寫的,不過當時在看到他們文章以後以爲有類似的地方,相較於這篇文章,本文可能會枯燥些,會有大量代碼,同窗能夠直接用上倉庫看。vue
首先要思考咱們的項目最終的構建主體
是單頁面
,仍是多頁面
,仍是單頁 + 多頁
,經過他們的優缺點來分析:node
單頁面(SPA)webpack
懶加載
可有效減小首頁白屏時間,相較於多頁面
減小了用戶訪問靜態資源服務器的次數等。懶加載
也有他的弊端,不作特殊處理不利於 SEO 等。多頁面(MPA):ios
URL
,cookie
,storage
等方式,較爲侷限。SPA + MPAgit
老 MPA 項目遷移至 SPA 的狀況
,缺點結合二者,兩種主體通訊方式也只能以兼容MPA 爲準
HTML 串
的狀況下),想保證用戶體驗在 SPA 中開發一個頁面,在 MPA 中也開發一個頁面,去掉沒用的依賴,或者直接用原生 JS 來開發,分享出去是 MPA 的文章頁面,這樣能夠加快分享出去的打開速度,同時也能減小靜態資源服務器的壓力,由於若是分享出去的是 SPA 的文章頁面,那 SPA 所需的靜態資源至少都須要去進行協商請求
,固然若是服務配置了強緩存就忽略以上所說。咱們首先根據業務所需,來最終肯定構建主體
,而咱們選擇了體驗至上的 SPA
,並選用 Vue
技術棧。es6
其實咱們看開源的絕大部分項目中,目錄結構都會差不太多,咱們能夠綜合一下來個通用的 src
目錄:github
src ├── assets // 資源目錄 圖片,樣式,iconfont ├── components // 全局通用組件目錄 ├── config // 項目配置,攔截器,開關 ├── plugins // 插件相關,生成路由、請求、store 等實例,並掛載 Vue 實例 ├── directives // 拓展指令集合 ├── routes // 路由配置 ├── service // 服務層 ├── utils // 工具類 └── views // 視圖層
components
中咱們會存放 UI 組件庫中的那些常見通用組件了,在項目中直接經過設置別名
來使用,若是其餘項目須要使用,就發到 npm
上。
// components 簡易結構 components ├── dist ├── build ├── src ├── modal ├── toast └── ... ├── index.js └── package.json
若是想最終編譯成 es5
,直接在 html 中使用或者部署 CDN 上,在 build
配置簡單的打包邏輯,搭配着 package.json
構建 UI組件 的自動化打包發佈,最終部署 dist
下的內容,併發布到 npm
上便可。
而咱們也可直接使用 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 仍是有的
,期待你的加入。
最後附上部分產品截圖~
(逃~)