Vue基礎:響應式
最近換了東家,比較忙、比較累,但部落格的更新速度不能明顯下降。Fighting!
寫在前面
Vue不是框架(前端框架往往需要解決路由、試圖管理、資料持久化等流程),Vue只關注檢視層。
webpack構建Vue專案
使用webpack構建Vue專案,在配置檔案中會看到如下程式碼:
module.exports = {
// ...
resolve: {
alias: {
// 使用完整版構建,而非執行時構建版本
'vue$': 'vue/dist/vue.esm.js'
}
}
}
- 完整版:同時包含編譯器和執行時的構建。
- 編譯器
- 執行時:用來建立 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
var webpack = require('webpack')
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
]
}
- UMD:UMD 構建可以直接通過
<script>
標籤用在瀏覽器中。Unpkg CDN 的vue.js
)。 - CommonJS:CommonJS 構建用來配合老的打包工具比如 browserify 或 webpack 1。這些打包工具的預設檔案 (
pkg.main
) 是隻包含執行時的 CommonJS 構建 (vue.runtime.common.js
)。 - ES Module:ES module 構建用來配合現代打包工具比如 webpack 2 或 rollup。這些打包工具的預設檔案 (
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.then
和 MutationObserver
,如果執行環境不支援,會採用setTimeout(fn, 0)
代替。
Vue.js 通常鼓勵開發人員沿著“資料驅動”的方式思考,避免直接接觸DOM,但是有時我們確實要這麼做。為了在資料變化之後等待Vue完成更新DOM,可以在資料變化之後立即使用 Vue.nextTick(callback)
或者vm.$nextTick()
,這樣回撥函式在DOM更新完成後就會呼叫。
需要注意的是,對於物件新增新成員,需要如下處理:
// 全域性方法
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.firstName
和 vm.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。