Vue 常見業務場景以及細節心得

Vue 是當今最火的前端框架之一,這就不用多說了。這裡就只留兩點建議給剛上手的童鞋:

  1. 怎麼去學習:
  2. 我建議可以直接去看 Vue 官方文檔,官方文檔寫得挺好的,而且學習曲線還是挺平滑的。這也是 Vue 的一個優點。要注意什麼:
  3. 要理解其框架思想。上一代的以 jQuery 為代表的框架的思想是直接操作 DOM,更新視圖。現在主流以 Vue、React 為代表的前端框架,最大的特點就是數據驅動,操作數據,視圖更新的工作留給框架去做。兩種思想是截然不同的,如果之前是接觸 jQuery 比較多,這裡就需要注意,不要被 jQuery 的開發思維給影響到,必要時可以將之前的思維拋棄掉,以新的思維方式去接受並且開發。 這裡就不多說如何上手 Vue 了,直接去官方文檔開啟 Vue 的學習旅程吧。在這主要分享一下,在這個項目過程中,所遇到的典型業務場景,解決方案,還有一些心得,希望能對你們有所幫助。

常見的業務場景

1、全局註冊與局部註冊的選擇

基於 Vue 的開發,接觸最多的便是組件化開發了,這也是框架解決了的前端開發痛點之一。在 Vue 中,組件註冊分為全局註冊和局部註冊,兩者的區別以及常見的業務場景有哪些呢:

  • 全局註冊
Vue.component('component-name', { // ... 選項 ...})

一個網頁,一般由基礎功能塊,再由盒子模型合理佈局之後,便組成了我們常見的網頁。這些基礎性功能塊,再由一些基礎元素組成,比如 button 標籤、input 標籤等等。這些大家應該都瞭解。 在這基礎上,整個頁面基本遵守頁面設計統一的原則,基礎元素一般要設計統一,但又要在多個地方複用,所以此時就是全局組件的發揮之地。我們將多處需要複用的元素,封裝成全局組件,功能與設計統一維護,而需要用到的地方,直接調用就好了,無需二次開發。這就是全局組件的便利之處。

  • 局部註冊
var ComponentA = { /* ... */ }new Vue({ el: '#app', components: { 'component-a': ComponentA }})

一張網頁,一般對應著一個 Vue 實例,也就是根組件,最外層的組件。在這基礎上,內部我們一般會以功能劃分為若干個模塊,比如頭部,內容區等等。這些模塊,都是一個個獨立的組件。這就是局部註冊的業務場景,雖然犧牲一定複用性(但其在內部組件一樣可以多處複用),但是可以獨立維護內部功能以及獨立的業務邏輯。這樣剝離出來,代碼以及業務邏輯都很清晰,而不是全部都冗雜在外層組件中。

所以在兩者的選擇上,全局組件更偏向於搭建基礎組件,而局部組件偏向於業務模塊,根據業務劃分不同的組件。

總結:

  • 全局組件最大的特點就是複用,要發揮這個特點才註冊成全局,不然會造成一定的浪費。最常見的業務場景就是基礎組件,比如 button、input 等等。市面上也有很多不錯的組件庫,比如 iView 。局部組件最大的特點就是相關業務邏輯獨立維護,降低耦合度以及提高業務的清晰度。

2、怎麼去把握業務邊界

上面提到,我們在組件化開發的時候,需要根據功能去劃分模塊。模塊的劃分,過細或者不夠具體,都會在開發過程中造成一定的影響。所以,如何去劃分模塊,或者說如何把握好一個模塊的功能邊界,明確其業務範圍,也是需要我們去注意的。通常,我們可以從以下幾個問題去思考:

  • 劃分出來的模塊,它需要做什麼?不需要做什麼?它向外暴露什麼?同時向外接受什麼?

考慮好這幾個問題之後,模塊結構基本也清晰了。

  1. 這個模塊需要做什麼 ,不需要做什麼
  2. 首先,我們從這兩個問題入手思考,這兩個問題也是為了讓我們確定這個模塊的功能邊界。比如,我們設計一個彈窗組件,我們從這兩個問題出發思考,它需要做什麼,彈出、顯示內容、關閉,沒了。顯示什麼內容呢,它不需要關心,這就是它不需要做的事情。從這兩個問題出發,去把握好模塊的功能邊界,劃分出來的模塊結構也會比較清晰。模塊向外暴露什麼,向外接受什麼
  3. 這其實是考慮如何進行數據通信了。在 Vue 中,最常見的就是父子之間的通信了,父組件通過 props 傳遞數據給子組件,子組件通過 emit 觸發自定義事件發送信息給父組件。這就是父子之間的通信過程。
  4. 但是有一個點容易被忽略,當 props 是一個引用類型的時候,這個時候傳遞就不是單向數據流了。因為子組件拿到的是一個引用,它可以修改這個引用對象上的值,父組件的數據也會隨之相應地變化了,所以這就變成了雙向數據流。
  5. 這是不應該的,子組件不應該直接修改父組件的數據的,而且當你這麼做的時候,Vue 也會提示你不要這樣操作。所以在 props 中一般只傳遞基本類型的值,避免傳遞引用類型的值,同時最好在定義 props 的時候,寫好類型和默認值,校驗一次。
  6. 但有些場景下,傳遞引用類型會方便一些。我個人分為兩種情況:當這個組件是全局複用時,比如 input 、button 等等這些全局複用的自定義組件,就只傳遞基本類型。當這個組件是某個功能模塊內部使用,比如局部註冊的組件,因為它侷限於某個模塊下,並且數據量大的時候,傳遞引用類型就方便一點。但還是要記得不能直接修改引用對象的值。
  7. 若需要有對這個引用某個值進行操作,可以將這個值賦予 data 或者 computed 屬性,再去相對應的操作。(這也是為了將數據修改約束在頂層組件,這樣做的好處是數據流清晰,只有一處修改數據的地方。出問題追溯數據也比較好追。因為框架是基於數據驅動,要面對的問題都是因為數據,所以要對數據的結構、來源,去向以及修改做好約束,或者管理好數據。)

這是向外接受要思考的方向。 向外暴露呢,或者說向外提供什麼呢?

組件化開發,內部高度自治,所以一個邏輯到了某個組件,組件內部處理完之後,需要向外部告知。此時需要注意一個原則:

應該向外告知,我發生了什麼,而不能是,你去幹什麼?

可能有點拗口,但是你仔細思考一下就理解了,後者其實是決定了外部行為,這樣就存在了耦合,這樣的設計是不合理的。前者只是告知外部它自己發生了什麼,它不管外部是根據這個信息,進行什麼響應,這樣的設計才是合理的。雖然這句話聽起來,看似很相似。

3、數據配置

前面也提到,對數據的結構、來源,去向以及修改做好約束,或者管理好數據。因為數據驅動,我們要高度關注數據,合理設計數據結構以及管理數據。數據配置是我在項目中用得最多的一種數據管理方式,其實在很多框架細節上,隨處可見。

什麼是數據配置?

舉個栗子,最常見的業務場景,後臺管理。

Vue 常見業務場景以及細節心得

大致的代碼結構如下:

內容區

這是一個簡單的後臺管理頁面,左側面板有一系列的菜單項,右側內容區基於左側菜單切換而變化。左側菜單結構一致,只不過是數據不同而已。在這個例子也可以看出:

  • 頁面結構與數據的關係。頁面結構只是數據的外層表現而已,並且是基於數據結構去變化的所以再抽象一點,將數據從頁面結構抽象出來,便就是這個頁面所需要的數據結構了。如果我們將剝離出來的數據結構,單獨維護,這便是我所說的數據配置了。

數據配置有什麼好處呢?

  • 只關注數據結構,不用關注頁面結構,即使增刪菜單項、或者修改結構層級,我也只需在數據這一層,進行調整就好了,不需要再進入邏輯層或者頁面層進行變動。這種方式對於後期維護相當友好。不管是擴展還是維護,都是相當的便利的。記得在我這個項目開發過程中,後臺管理頁面有過一定的變動,在 PM 跟我講完需要變動的地方的時候,我也基本都改好了...因為我只需更換數據層就好了,就比方我修改一個配置文檔而已,只要文檔標註寫清楚了,誰來改都是一樣的便利。

適合的場景:

  • 相似的結構,只是數據有所不同。這適合的場景也是比較廣泛的,基本也可以搭配組件複用去實現的。

上面說到,右側內容區是基於左側菜單切換而變化的。這個實現是基於路由實現的,Vue 也提供了 vue-router 官方路由庫,具體的文檔也是可以直接去看官方文檔。在這裡想是,對於路由的管理,同樣也可以運用數據配置的方式,統一在一處管理路由配置項,還可以與其他數據結構嵌套使用。比如接上上面的例子:

// 路由配置// compons.serviceManage、compons.serviceList 就是內容區的內容組件var serviceRoutes = { serviceManage: { path: '/serviceManage', name: 'serviceManage', component: compons.serviceManage }, serviceList: { path: '/serviceList', name: 'serviceList', component: compons.serviceList}};// ...// 菜單項配置// 二級菜單var subServer = [ { type: 'manage', name: '管理服務', extClass: 'server-manage', router: serviceRoutes.serviceManage }, { type: 'list', name: '服務列表', extClass: 'server-list', router: serviceRoutes.serviceList },];// ....// 如何使用// 上面配置的路由配置對象全部放進來,下面會循環創建路由對象。var routerList = [serviceRoutes, articleRoutes, ...];// 路由配置,循環路由配置var router = [];routerList.forEach(router => { router.push(router); });// 根實例var manage = new Vue({ router,// 將配置好的路由對象傳進來 el: '#manage', data: { menuDef: menu }});

後期在維護這一塊的時候,只需要對數據結構的配置以及路由的配置維護就好了。

4、如何優雅地搭配 jQuery 使用

在 Vue 的開發過程中,大多時候並不需要你去親自去操作 DOM,你只要關注數據層,通過數據驅動,由框架去完成視圖的更新。

但在某些特殊場景下,你切確需要去進行一些底層的 DOM 操作,比如 hover 某個模塊出現 tip,或者 hover 某個結構出現工具欄。這種業務場景,你可能想到的是,在模塊內部處理 mouseenter 和 mouseleave 事件的時候,進行這塊邏輯的處理。這是比較典型的 JQuery 思維去處理這個問題,這樣處理方案有很多缺點:

  • 無法複用,你所做的處理,只是限定在某個模塊下的,別的地方無法直接複用。耦合高,因為這個業務本身就可以剝離出去的,成為一個單獨的組件維護了。如果還夾雜在其他模塊內部,會造成模塊內部邏輯以及結構不清晰。後期維護難。

如果是用 Vue 的方式去實現呢?

Vue 提供了一個自定義指令 (directive) 的接口給我們,這種方式就很好地解決了這個業務場景下的問題。在 Vue 裡面,它不建議你直接在實例內部中進行一些 DOM 操作,如果你需要進行一些底層 DOM 操作,你可以將它們抽離到自定義指令中來。而在需要用到的時候,我直接使用就好了,完美解決上面的處理方案存在的問題。

// jQuery 的方式,錯誤的方式
var app = new Vue({ el: '#app', data: {}, methods: { handleMouseEnter: function () { document.querySelector('#tip').style.display = 'block'; }, handleMouseLeave: function () { document.querySelector('#tip').style.display = 'none'; } }});// Vue 的方式,正確的選擇
Vue.directive('tip', { inserted: function(el, binding) {var tipDom = document.querySelector('#tip');if(tipDom) { // 如果已經存在,不需要創建 DOM // do something} else { // 如果不存在,創建 DOM tipDom = document.createElement('div'); tipDom.setAttribute('id', 'tip'); document.body.appendChild(tip); // do something}// 為綁定該指令的元素註冊事件el.addEventListener('mouseenter', function() { // do something});el.addEventListener('mouseleave', function() { // do something}); }});var app = new Vue({ el: '#app', data: { tipText: 'Hello, world!' },});

這便是這種業務場景下,Vue 的優雅實現。很好地將業務邏輯剝離出來成自定義指令,一處實現,多處複用。代碼邏輯清晰,耦合度低,後期好維護等等優點鋪面而來。簡直如浴春風啊~

具體相關的自定義指令的知識點以及運用,大家可以去查看自定義指令的官方文檔。

結尾

在開發過程中,還有很多細節一點的場景,比如如何結合 Vue 優雅地實現動畫,父子通信,兄弟通信需要注意的細節等等。這些細節就相對瑣碎了一點,一一講起來篇幅也會太長了。後期有讀者遇到相關問題,也可以再另起一篇幅,再給大家嘮嗑嘮嗑~

這篇就到這了。

Hello,world!

Vue 常見業務場景以及細節心得


分享到:


相關文章: