Vue 淺析與實踐


歡迎大家前往騰訊雲社區,獲取更多騰訊海量技術實踐干貨哦~

作者:曾柏羲 

導語

入職接到的第一個需求是實現一個關於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.x 的虛擬 DOM diff 原理

基於 TVUE 框架在中型移動端項目的直出同構實踐

騰訊工程師們怎么玩 Vue.js?


 

此文已由作者授權騰訊雲技術社區發布,轉載請注明原文出處

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM