vue 階段性總結


  使用vue開發已經有一段時間了,本文主要是記錄平時使用過程中踩過的一些坑,以及一些心得。一方面可以自我總結提高,另一方面可以將自己的經驗分享出來。

  一、為什么要用框架

    現在前端行業發展飛快,我們在選用技術棧的時候,一方面要易於上手,另一方面要適合自己的項目。vue就是這樣一個前端框架,易於上手,有成熟的文檔可以參考、同樣有成熟的社區可以討論問題。最不濟可以閱讀源碼,vue的源碼還是比較易讀的。

    為什么我們開發的時候需要使用框架?以前的jquery不好嗎?原生js開發不好嗎?使用這些框架能夠解決哪些痛點?我就來說說我自己的一些淺見。

    1、組件化

    組件化開發可以解耦,便於復用,我跟人而言組件化是模塊化更進一步的解耦。最初我們在開發前端項目的時候,瀏覽器原生並不支持模塊開發,我們需要使用一些庫來給模塊化的js提供一個運行平台,例如之前的 requireJs、seaJs等。如果你經歷過原始的開發再轉型到模塊化開發,你就會明白模塊化的好處。因為當你在沒有模塊的開發過程當中,你會發現所有的變量都是全局的,所有的方法都是全局的(當然局部變量除外)。當你在你的js文件當中使用了某個變量的時候,你都找不到這個變量是定義在當前的js文件當中還是定義在其他的js文件中,這就很頭疼了。還有就是你引用了別人的js文件,你無法確認你寫的方法是不是覆蓋了別人的方法。當你使用了模塊化開發的時候,你就能通過模塊引入的方式來進行你需要依賴的方法、類、對象、常量、變量的引入。並且模塊中所有定義的方法變量都不是全局的,這樣你會少了很多麻煩。

    上面簡要談了談模塊化的好處,模塊化對你的js邏輯來說基本上是夠用了,但是我們前端不僅有js邏輯,還有html、css這些界面樣式。這些東西怎么去解耦呢?怎么擁有自己的局部作用域呢?怎么去復用呢?組件就很好地解決了這個問題。試想一下你需要在頁面上做一個擁有提交功能的按鈕,你需要在html中寫入元素,然后再在js中給這個按鈕添加事件。當你的頁面上有很多這種按鈕的時候,你需要去復制多份,這樣一沒有起到解耦的效果,二無法復用。現在如果瀏覽器廠商特意為你的需求定制了一個標簽,你只需要在你的html文件中使用這個標簽就能完成你所需要的功能,這樣是不是很簡單,如果你需要多份這樣的按鈕,你要做的只是將這個標簽寫到其他地方。當然瀏覽器不可能為你定制這樣一個標簽,所以你需要自己為自己定制這樣一個標簽。這就是組件化的思想。

    2、數據綁定

    這個可以說是前端三大框架真正能夠解決痛點的地方了,因為前面所說的組件化,瀏覽器馬上就要原生支持 webcomponent 了。在原來的開發模式中我們修改頁面中的某個元素中的value值,我們需要獲取到相應的元素,然后再修改元素的value。獲取元素的過程就是操作DOM的過程,瀏覽器為js提供了操作DOM的接口,前端就是展示頁面,也就是改變html及css。改變html和css需要使用腳本語言才能動態地修改,這就是js存在的重要意義。但是操作DOM的過程是繁瑣的,而數據綁定可以幫我們從操作DOM的過程中解放出來。試想一下我們如果只需要改變 value的值就可以讓頁面發生相應的更改,這樣我們就可以免去操作DOM的麻煩。我們稱這種改變UI的方式為數據綁定。

    不管框架是如何實現數據綁定的,我們需要關心的只有數據,因為UI是被數據所驅動的。只要數據發生改變UI就能發生相應的更改。其實細想起來,我們做的UI頁面就是一個html DOM樹,這顆DOM樹完全可以通過內存中的對象來實現數據映射。框架要做的工作就是將內存中的對象與DOM樹發生綁定關系,其實內存中的這個對象就是我們經常看到的虛擬DOM。

  二、vue的數據綁定原理

    vue這個框架很好地實現了以上兩個優點。我曾經讀過一些vue的源碼,下面說說我對vue發生數據綁定的一些理解,如果掌握了vue的工作原理,我們可以在工作當中很好地解釋一些難以理解的問題,我認為還是很有必要的。

 

    一般來說我們改變了數據瀏覽器並不知道需要重新渲染哪些DOM,瀏覽器只知道當我們顯式地改變了DOM的時候才會去重新渲染。很顯然改變數據然后通知瀏覽器去渲染就是我們的vue起的作用。那么vue是怎么知道我們改變了數據的呢?

    第一個問題:vue如何知道我們修改了數據?

    其實vue並不是萬能的,它並不知道我隨手寫的一個變量是否發生了改變。也就是說vue所監控的變量是有要求的,這個要求就是顯式地聲明在data和props中的對象屬性。也就是說我們改變data對象或者對象中的屬性的時候vue是知道我們修改了這些屬性的。vue是如何知道的呢?

    在我們實例化Vue這個類的時候,它會遞歸地對data對象設置監聽,監聽的方式是 使用瀏覽器支持的 Object.defineProperty() 這個類靜態方法對這些屬性或對象設置get、set方法。這兩個方法有點類似鈎子函數,當你去改變data中的某個屬性時就會觸發set方法,當你去獲取data中的某個屬性時就會觸發get方法。很顯然你可以在get和set方法中執行一些操作去修改頁面中的DOM。

    vue就是這樣對我們的數據進行監控的。前面說了 vue是在初始化的時候遞歸地對屬性進行監聽的,當你改變了data中某個屬性的引用的時候vue會重新對新的對象進行遞歸地監聽。注意這里說的是改變引用的時候才會觸發監聽綁定,也就是說當你的對象引用沒有發生改變,只是給對象增加了一個屬性的時候vue是無法對新的屬性進行監聽的。這也就是為什么你在沒有在data中聲明屬性而是后面添加的屬性vue沒有辦法監聽到的原因。通常我們還會犯一個錯誤,就是我們把開始聲明的對象引用改變了,但是新的引用中的屬性跟原來的引用的屬性有所區別,這樣原來的屬性就會丟失引用。例如

  

 1 data () {
 2     return {
 3         pro1: {  // 初始聲明的引用具有a b兩個屬性
 4             a: 1,
 5             b: 2
 6         }
 7     }
 8 }
 9 
10 ...
11 
12 // 后面的某個時刻
13 this.pro1 = {a: 3, c: 4};
14 
15 // 在這里你會發現原來 模板中與 pro1.b 發生綁定關系已經被丟失了

 

  就上面的問題而言,我們平時的工作當中如何避免這種情況呢。現階段我采用的方法是 使用 Object.assign()靜態方法或者自己編寫一個merge方法,進行數據的合並操作。由於Object.assign()方法存在局限性,我們自己編寫的merge方法可以更加靈活,所以我們會采用merge方法進行vue的數據變更。merge方法的大概實現如下:

/**
 * 數據合並方法
 * @param {Object} target 目標對象
 * @param {Object} origin 源對象
 * @param {String} stand 合並標准,默認為左樹標准
 * @returns {void} 無返回值
 */
export function mergeData (target, origin, stand = 'left') {
  if (!target || !origin) {
    return
  }
  if (Utils.dataType(target) !== Utils.dataType(origin)) {
    console.error('目標對象與源對象的數據類型不同,無法實現合並')
    return
  }

  let flag = stand === 'left'
  for (let prop in target) {
    if (Utils.dataType(target[prop]) === 'object') {
      // target[prop] = (target[prop].constructor === Array) ? [] : {}// 三元運算,將s[prop]初始化為數組或者對象
      mergeData(target[prop], origin[prop])
    } else if (Utils.dataType(target[prop]) === 'array') {
      // 兼容處理
      if (!origin[prop]) {
        origin[prop] = []
      }
      if (origin[prop].length > 0) {
        // 該條件是為了剔除重復的數據
        target[prop].length = 0
      }
      target[prop].push(...origin[prop])
    } else {
      let defaultVal
      switch (Utils.dataType(target[prop])) {
        case 'object':
          defaultVal = flag ? (target[prop] || {}) : (origin[prop] || {})
          break
        case 'array':
          // defaultVal = target[prop] || []
          defaultVal = flag ? (target[prop] || []) : (origin[prop] || [])
          break
        case 'string':
          // defaultVal = target[prop] || ''
          defaultVal = flag ? (target[prop] || '') : (origin[prop] || '')
          break
        case 'number':
          // defaultVal = target[prop] || 0
          defaultVal = flag ? (target[prop] || 0) : (origin[prop] || 0)
          break
        case 'boolean':
          // defaultVal = target[prop] || false
          defaultVal = flag ? (target[prop] || false) : (origin[prop] || false)
          break
      }
      target[prop] = (origin[prop] || defaultVal)
    }
  };
}

     第二個問題:Vue怎么知道我們數據改變之后需要對哪些UI狀態做出修改?

    Vue是通過一點一點的截斷的方式對模板文件進行解析的,例如:Vue在解析模板的過程當中遇到了<div>{{msg}}</div>,首先會解析出來div標簽做為一個node節點,然后接着去截取{{msg}}作為文本節點,並且發現這個文本節點中有特殊的{{}},就會將msg作為一個表達式,並且認為msg是自己需要綁定的UI數據。最后會解析到</div>來最終確定div節點的閉合。實際過程中Vue把模板解析完成之后會生成對應的虛擬DOM樹,這個DOM樹是存在於內存當中,並且會標記其中需要關心的變量和表達式,將這些需要關系的變量或者表達式與自己data作用域中的數據進行綁定,綁定的過程就是在對數據監聽的get、set方法中來進行相應的VNODE修改,VNODE修改流程結束之后會觸發render方法,從而達到改變指定UI的效果。

   三、組件數據共享

    分組件開發當然能簡化我們的工作,數據綁定又讓我們只用關注於數據,因為vue接管了我們的UI,它可以將我們操作的數據動作映射為UI狀態的改變。所以我們在平時開發的過程當中需要關心數據的改變已經數據的傳遞。數據的傳遞指的是組件之間數據的傳遞,因為我們在開發工作當中往往都會有多個組件共享數據的情況。這些組件之間的關系可能是父子組件、兄弟組件、祖先后代組件這些關系。這里我想說一下最簡單的父子組件之間的數據傳遞方式。因為兄弟組件和祖先后代組件這種組件關系Vue提供了Vuex和事件總線這種解決方案。

    從我的工作經驗來說父子組件之間的數據傳遞方式有兩大類,一類就是Vue官方推薦的做法,父傳子通過props來進行傳遞,子傳父通過$emit這種發布訂閱模式來進行。第二類就是父傳子通過props進行傳遞,子傳父通過改變對象屬性引用的方式來進行.

如果多層級組件之間使用vue $emit來改變父組件的數據很閑的很繁瑣,我們可以使用改變對象屬性的方式來改變這一現象。
    
    在Vue中我們不能直接在子組件中直接修改父組件的數據,這一行為Vue會發出警告。這是因為在Vue2.0 中只支持單項數據流,也就是說只能通過父組件傳遞數據給子組件,並且父組件改變數據時子組件可以同步監控到修改,從而引起UI的修改。但是我們可以在父組件中傳入一個對象到子組件,子組件不直接修改父組件傳過來的對象的引用,而是修改傳過來的對象的屬性,這樣Vue就不會發出警告,並且父組件可以獲得子組件的修改。
// 父組件模板
<ChildComponent :parent-props="testProp" />
// 父組件數據
data: {
  testProp: {
    pro1: 'a',
    pro2: 'b'
  }
}

// 子組件
props: ['testProp'],
methods: {
  doing () {
    this.testProp.a = 'c'
  }
}
    通過以上步驟,可以實現在子組件中修改父組件傳遞過來的數據的目的,這種不管組件的層級有多深都可以實現數據的傳遞和修改,這在有些情況下是很有用的。比如在 element-ui 這個組件庫當中,就是通過這個數據傳遞的方式來實現 Form 組件的。
<el-form :model="formData" :rules="formRules">
  <el-form-item prop="name">
    <el-input v-model="formData.name" />
  </el-form-item>
</el-form>

// 數據
data: {
  formData: {
    name: ''
  },
  formRules: {
    name: [
      // ...
    ]
  }
}
    element-ui 就是首先傳入一個 formData這個對象到 el-form 組件中的,並且通過prop屬性給 el-form-item 組件,讓該組件得以跟 formData.name 這條數據進行綁定,從而實現表單校驗。
    (完)


免責聲明!

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



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