歡迎大家前往騰訊雲社區,獲取更多騰訊海量技術實踐干貨哦~
作者:曾柏羲
導語
入職接到的第一個需求是實現一個關於K歌實體售賣的ERP系統,管理系統過去做過不少,這次打算換個姿勢,基於時下正熱但早已不新鮮的Vue 2.0技術實現。本文首先對Vue的相關技術進行簡單介紹與分析,接着總結開發實踐(主要描述 Vuex 實踐)過程中的流程規范,並記錄在此過程中遇到的問題與關鍵點,最后做出一點實踐的總結與思考。
Vue
圖:vue 2.0
簡介
眾所周知,如今的前端框架/解決方案數不勝數,從最初的Backbone,到Angular和Meteor等,這當中有很多都為前端的工程化管理和建設提供了一整套解決方案,是一種“大”框架,但這樣的框架往往具備一定的排它性,使得開發的自由和靈活度受到限制。
與此不同的是,Vue對自己的定位是一個漸進式的JavaScript框架,它最核心的部分是只是為了解決視圖層方面的問題,提供聲明式渲染和組件化管理模式。同時對於路由管理、狀態管理和構建工作方面又有自己的解決方案,開發者可以自由地選擇或者組合,從而能夠更加靈活自如地進行項目開發。
響應式原理
手動改變DOM操作是件損耗性能的事情,幾乎所有MVX框架都遵循一個原則:視圖的狀態應該由數據描述,並且通過數據驅動變化。
圖:雙向數據綁定
Vue采用發布者-訂閱者模式實現雙向數據綁定,首先Vue將會獲取到需要監聽的對象的所有屬性,通過 Object.defineProperty 方法完成對象屬性的劫持,將其轉化為getter和setter,當屬性被訪問或修改時,立即將變化通知給訂閱者,並由訂閱者完成相應的邏輯操作,主要流程下圖所示。
圖:響應式原理
整個過程中主要涉及了 Observer、Dep 和 Watcher三個類,相關源碼(已精簡)如下:
Observer:
主要處理屬性監聽邏輯,將監聽屬性轉化為get/set屬性,當屬性被訪問時,調用dep.depend() 方法,而屬性被修改時,則調用了dep.notify()方法。
export function defineReactive ( obj: Object, key: string, val: any ) { const dep = new Dep() // 監聽屬性的get和set方法 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { dep.depend() return val }, set: function reactiveSetter (newVal) { val = newVal dep.notify() } }) }
Dep:
擔任發布者的角色,維護訂閱者列表,負責訂閱者的添加和通知工作,上面所提到的depend()和notify()方法在這里實現。
export default class Dep { id: number; // 訂閱者列表 subs: Array<Watcher>; // 添加訂閱者 addSub (sub: Watcher) { this.subs.push(sub) } // 屬性被訪問時調用該方法,通知依賴的目標(即訂閱者)添加該依賴, // 同時將其加入訂閱者列表中(調用addSub()方法) depend () { if (Dep.target) { Dep.target.addDep(this) } } // 當監聽到依賴的屬性發生改變時,通知訂閱者執行狀態更新操作 notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
Watcher:
擔任訂閱者角色,即上述代碼中的 Dep.target,可以訂閱多個Dep,在每次收到發布者消息通知時觸發update()方法執行更新邏輯。
export default class Watcher { deps: Array<Dep>; newDeps: Array<Dep>; depIds: ISet; newDepIds: ISet; // 為該指令添加依賴(發布者) addDep (dep: Dep) { const id = dep.id this.newDepIds.add(id) this.newDeps.push(dep) dep.addSub(this) } // 更新視圖邏輯,依賴的屬性值發生改變時觸發 update () { // 省略 } }
狀態管理
過去為了實現父子組件或者平行組件的數據通信,常見的做法是直接或間接地使用 props 屬性和 emit() 方法來實現,這樣的做法耦合度強,且難以應付復雜場景下的狀態管理。
Vuex 的出現可以很好地規避此類問題,它是一種Vue應用的專用狀態管理模式,負責集中式地存儲和管理整個Vue應用程序的組件狀態,實現更好的狀態共享。Vuex將組件狀態的存儲和管理放在了 Store 里面,並為其提供了4種特性,分別是 state、actions、mutations 和 getters:
- state,作為驅動應用的數據源,保存了組件的各種狀態;
- mutations,類似於事件,是改變 state 的唯一入口,且里面的操作必須是同步的;
- actions,類似於 mutations,里面可以進行一些如 ajax 請求等異步的邏輯操作,但如果想對 state 的狀態進行修改,必須通過 mutaions 完成;
- getters,可以對 state 進行某些處理動作,並對處理后的結果提供訪問接口。
圖:vuex狀態管理
Vuex狀態管理流程如上圖所示,主要分為如下四個步驟:
- 視圖層中的 Components 通過 this.$store.xxx 或 getters 方法從 state 中獲取數據並渲染;
- 用戶在 Components 中執行某些動作(如點擊按鈕fetch數據)時,通過調用 dispatch() 方法將執行動作的指令發送到 Actions 中對應的方法;
- Actions 解析請求指令,完成相應的邏輯(如ajax數據請求),並在最后(ajax請求結束后)通過 commit() 方法通知 mutations 對 state 狀態進行修改;
- Mutations 收到 commit 請求后,對 state 進行賦值操作,以完成數據的修改。
圖:vue devtools
對於開發過程中 Vuex 狀態的追蹤,可以通過 Vue Devtools 的 “Vuex” 一欄進行查看,如上圖所示,安裝方法可以自行搜索。
項目實踐
圖:K歌erp
簡單介紹
全民K歌初期在試水麥克風售賣活動中取得了良好的市場反饋,為繼續推動周邊實體的售賣,現在期望搭建一個屬於自己的售賣平台。整個需求分為H5和PC兩部分,其中H5為用戶購買實體周邊的入口,PC則是對用戶的訂單數據進行管理。本項目為需求中的PC部分,共由訂單數據概覽、待審核、待發貨、已發貨和退換貨五頁組成。
圖:項目頁面
頁面具體介紹如下:
- 概覽頁,包含了整個平台上的交易數據,貨品的實時庫存以及當前用戶的待辦事項;
- 待審核,顯示最新的訂單數據,用戶可以在該頁中執行訂單審核動作;
- 待發貨,已經通過審核的訂單將會移動至該頁,該頁為管理員提供了物流信息導入和已經確認發貨的動作;
- 已發貨,顯示已經發貨的訂單列表,用戶可以對發貨的地址進行修改,同時也可以執行申請退款的操作;
- 退換貨,包含了“待處理“、“”已通過“和“已拒絕”的tab頁。其中待處理tab顯示了前端發起退貨或erp上執行“申請退款“的訂單列表,而用戶在該列表中執行的動作(允許/拒絕退款)將會使數據移至“”已通過“或“已拒絕”的列表中。
此外,對於所有的列表頁,需要提供批量操作,如批量審核、發貨退款和物流信息導入等,並提供分頁操作。同時登陸需要通過K歌掃碼完成,所有的CGI調用需要在K歌的登陸態下進行。
項目構建
項目的構建使用 vue-cli 完成,具體操作流程如下:
圖: vue-cli構建項目
由於公司網絡原因,在執行 vue-init webpack [project-name] 時會出現無法下載模板庫的錯誤,解決辦法可以是通過設置如圖中所示的 npm 代理,或者是直接下載 vue 模板中的wepack庫並在本地運行完成。
開始動手
圖: 開發實踐
(1) 目錄規划
本文針對 vue-cli 構建出的項目進行結構上的調整,最終得出如下所示的目錄結構:
圖:項目目錄結構
如上圖,client目錄為客戶端的主要開發目錄,主要包含了程序入口文件 app.js、客戶端路由文件 router、客戶端視圖曾view、組件模塊components和應用狀態管理層(即Vuex)Store。
(2) Vuex規范
前面已經提到,訂單相關的頁面共有四頁,對應着待審核、待發貨、已發貨和退換貨四種狀態,由於每種狀態的相關操作邏輯不同,在開發過程中將Store中的order模塊划分為review、ship、completed和refund四個子模塊。下面代碼展示了一個簡化的review子模塊的代碼,其他模塊的代碼與之類似,都包含了 state、mutations、getters和actions對象。
// store/modules/order/review/index.js const state = { re_order_data: {} } const mutations = { [types.GET_RE_ORDER_LIST] (state, data) { state.re_order_data = data } } const getters = { re_order_list: state => state.re_order_list } const actions = { getReOrderList ({commit , state, rootState}, params) { common.ajax({ url: orderList, data:{ type: 5, filter_by: STATUS_HAVE_PAY } }).always(res => { commit('GET_RE_ORDER_LIST', res.data) }) } } export default { state, mutations, getters, actions }
此時你也許注意到,Store被划分成多個模塊,而每個模塊里面可能又會有更細粒度的划分。在實際的運行過程中,需要對這些模塊進行整合,這里需要用到Vuex提供的modules屬性,相關代碼如下:
import app from './modules/app' import menu from './modules/menu' import order from './modules/order' const store = new Vuex.Store({ modules: { app, menu, ...order } })
(3) 組件調用
組件對Vuex中state狀態的調用邏輯通常是放在 data 或 computed 屬性中,但需要注意的是,如果期望得到的是響應式的數據,則必須將調用邏輯放在計算屬性 computed 中,這樣當每次state狀態發生變化時,computed 屬性中的數據都會被重新計算,同時重新觸發更新視圖。此外組件也可以直接調用Vuex中的mutations和actions事件,這通常放在methods屬性中進行。
(4) 其他
路由處理,對於一個單頁應用,自然少不了路由處理,項目的路由使用官方的vue-router處理,使用router.beforeEach()方法在每次路由跳轉前進行攔截,判斷用戶是否登錄,如沒有登錄則跳轉至登錄頁。
網絡請求,Vue 2.0開始不再維護 vue-resourse,轉而推薦 axios 作為標准的網絡請求庫,但是由於 axios 不支持 jsonp 跨域方式,遂放棄,在項目中使用了團隊的 ajax 模塊。
延遲加載,項目使用了webpack作為打包構建工具,打包結束后默認情況下會產生兩個js文件:app.js和vendor.js,而項目在一開始就已經加載了這兩個js文件,如果要想實現路由的延遲加載,需要將路由請求的組件定位為異步組件,並結合webpack的代碼分割特性實現,方法是通過 require.ensure 的方式引入組件。
最后說句
本篇文章前半部分對 vue2.0 進行了淺析:分析關於響應式方面的源碼,了解具體的實現原理與模式,並對Vuex 數據管理做了知識梳理與流程說明。后半部分則是在項目中應用了 Vue 的技術棧,並對實踐過程中的代碼細節和關鍵點做了總結。
在整個開發過程中,能夠較為深刻地體會到vue對於代碼編寫的舒適性(來自於組件化的管理方式)以及vuex對於代碼組織方面的優雅。另外由於時間上的關系(實際上是因為懶), 還沒有仔細了解關於Vue 渲染方面的原理,希望可以找個時間在后續補上這部分內容。
謝謝閱讀,歡迎指正 (=^_^=)
參考文獻
- Vue2.0 中,“漸進式框架”和“自底向上增量開發的設計”這兩個概念是什么?
- Vue 2.0——漸進式前端解決方案
- 深入Vue 2,0響應式原理
- https://github.com/vuejs/vue/tree/dev/src/core/observer
相關閱讀
此文已由作者授權騰訊雲技術社區發布,轉載請注明原文出處