1. 程式人生 > >Vue基礎:響應式

Vue基礎:響應式

最近換了東家,比較忙、比較累,但部落格的更新速度不能明顯下降。Fighting!

寫在前面

Vue不是框架(前端框架往往需要解決路由、試圖管理、資料持久化等流程),Vue只關注檢視層。

webpack構建Vue專案

使用webpack構建Vue專案,在配置檔案中會看到如下程式碼:

module.exports = {
  // ...
  resolve: {
    alias: {
      // 使用完整版構建,而非執行時構建版本
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}
  • 完整版:同時包含編譯器和執行時的構建。
  • 編譯器
    :用來將模板字串編譯成為 JavaScript 渲染函式的程式碼。
  • 執行時:用來建立 Vue 例項,渲染並處理 virtual DOM 等行為的程式碼。基本上就是除去編譯器的其他一切。
// 需要編譯器
new Vue({
  template: '<div>{{ hi }}</div>'
})
// 不需要編譯器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

所以,專案中往往我們需要採用完整版,而非執行時構建版本!

壓縮Vue

npm安裝的Vue,並不是壓縮後的版本。然而,CommonJS 和 ES Module 構建同時保留原始的 process.env.NODE_ENV

檢測,以決定它們應該執行在什麼模式下。可以通過控制該環境變數,對Vue進行壓縮,以減少最終檔案的大小。

var webpack = require('webpack')
module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    })
  ]
}
  • UMD:UMD 構建可以直接通過 <script> 標籤用在瀏覽器中。Unpkg CDN 的
    https://unpkg.com/vue
    預設檔案就是執行時 + 編譯器的 UMD 構建 (vue.js)。
  • CommonJS:CommonJS 構建用來配合老的打包工具比如 browserifywebpack 1。這些打包工具的預設檔案 (pkg.main) 是隻包含執行時的 CommonJS 構建 (vue.runtime.common.js)。
  • ES Module:ES module 構建用來配合現代打包工具比如 webpack 2rollup。這些打包工具的預設檔案 (pkg.module) 是隻包含執行時的 ES Module 構建 (vue.runtime.esm.js)。這也是為什麼上述需要通過別名指定載入Vue的完整版本!

資料驅動

View(Dom) ==> DomListener(Vue) ==> Model(Pojo)
View(Dom) <== Directives(Vue) <== Model(Pojo)

每個指令都會觀察一片資料,管理其相關的Dom操作;資料變化,Dom操作自行發生變化!所有的元素都是響應式的!

需要注意的是:在Vue中資料流是單向的

<input v-model="message">實現所謂的雙向,不過是<input v-bind:value='message' v-on:input="message = arguments[0]"的語法糖。資料傳遞機制完全和子父元件通訊方式一致,所以要讓元件的 v-model 生效,子元件它應該:

  • 接受一個 value 屬性
  • 在有新的值時觸發 input 事件
 components: {
    'child-component': {
      props: ['value'],
      template: `
      <div>
        <input v-bind:value="value"
               @input="updateValue($event.target.value)">
        </input>
      </div>
      `,
      methods: {
        updateValue(value) {
          // 同樣需要觸發input事件,只是父元件中無需顯示宣告@inpt
          this.$emit('input', value);
        }
      }
    }
  }

響應式原理

Vue將遍歷Data物件所有的屬性,並使用 Object.defineProperty (ES5方法,Vue只支援IE9+d的原因) 把屬性全部轉為 getter/setter。

每個元件例項都有相應的watcher例項物件,它會在元件渲染的過程中把屬性記錄為依賴,之後當依賴項的setter被呼叫時,會通知watcher重新計算,從而致使它關聯的元件得以更新。

Vue**非同步**執行DOM更新,如果同一個watcher被多次觸發,只會一次推入到佇列中。Vue在內部嘗試對非同步佇列使用原生的 Promise.thenMutationObserver,如果執行環境不支援,會採用setTimeout(fn, 0)代替。

Vue.js 通常鼓勵開發人員沿著“資料驅動”的方式思考,避免直接接觸DOM,但是有時我們確實要這麼做。為了在資料變化之後等待Vue完成更新DOM,可以在資料變化之後立即使用 Vue.nextTick(callback)或者vm.$nextTick(),這樣回撥函式在DOM更新完成後就會呼叫。

**watcher**依賴/監聽Data屬性

需要注意的是,對於物件新增新成員,需要如下處理:

// 全域性方法
Vue.set(vm.someObject, 'b', 2);
// 例項方法
this.$set(this.someObject,'b',2);

可以使用 Object.assign()_.extend() 方法來新增屬性。但是,新增到物件上的新屬性不會觸發更新。在這種情況下可以建立一個新的物件,讓它包含原物件的屬性和新的屬性(開發中會經常遇到):

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

陣列:改變原陣列的方法,如:push()、pop()、shift()、unshift()、splice()、sort()、reverse()可以檢測變化;不改變原陣列的方法,如filter()、concat()、slice()不會自動變化。

// 用新陣列替換舊陣列(Vue做了相關處理,非常高效的操作)
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

對於直接修改陣列某一項值,或者修改其長度,可以通過以下方式實現:

Vue.set(example1.items, indexOfItem, newValue)
example1.items.splice(newLength)

計算屬性、方法和觀察者

computed vs methods vs watch

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // a computed getter
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }
})

對於任何複雜邏輯,你都應當使用計算屬性。Vue 知道 vm.reversedMessage 依賴於 vm.message,因此當 vm.message 發生改變時,所有依賴於 vm.reversedMessage 的繫結也會更新。計算屬性預設只有 getter ,不過在需要時你也可以提供一個 setter。

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

執行 vm.fullName = 'John Doe' 時,setter 會被呼叫,vm.firstNamevm.lastName 也相應地會被更新

注意:三者中都不能使用箭頭函式,箭頭函式綁定了父級作用域的上下文,所以this將不會按照期望指向Vue例項!

<div id="app">
  <input type="text" v-model="message">
  <p>{{computedMsg}}</p>
  <p>{{methodMsg()}}</p>
  <p>{{watchMsg}}</p>
</div>

<script>
  const app = new Vue({
    el: '#app',
  data() {
    return {
        message: '',
        watchMsg: ''
    }
  },
  computed: {
    computedMsg() {
        return this.message.toUpperCase();
    }
  },
  methods: {
    methodMsg() {
        return this.message.toUpperCase();
    }
  },
  watch: {
    message() {
        return  this.watchMsg = this.message.toUpperCase();
    }
  }
});
</script>  
  • computed不能被watch(本身就是響應式的),計算屬性的結果會被快取,除非依賴的響應式屬性變化才會重新計算。注意,如果例項範疇之外的依賴是不會觸發計算屬性更新的;
  • 每當觸發重新渲染時,method呼叫方式將總是再次執行函式;
  • watch是更通用的方式來觀察和響應Vue例項上的資料變動,不要濫用,在資料變化響應時,執行非同步操作或開銷較大的操作可以選用watch,其他情況儘量使用computed。