在使用Vue.js自定義組件時,很多時候,我們都期望數據是雙向綁定的。
Vue.js實現雙向數據綁定的兩種方式
1. v-model
調用組件時:
<comp v-model="something"></comp>
或
<comp :value="something"></comp>
在組件內部,必須:
- 接受一個
value
屬性 - 在有新的 value 時觸發
input
事件
this.$emit('input', newValue)
如:
<comp v-model="something"></comp>
Vue.component('comp', {
...,
props: ['value'],
computed: {
currentValue: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
}
}
},
...
})
2. props .sync 修飾符
調用組件時:
<comp :foo.sync="bar"></comp>
在組件內部,
- 接受一個
foo
屬性 - 需要更新
foo
時,它需要顯式地觸發一個更新事件
this.$emit('update:foo', newValue)
如:
<comp :foo.sync="bar"></comp>
Vue.component('comp', {
...,
props: ['foo'],
data () {
return {
currentValue: this.foo
}
}
watch: {
currentValue (val, oldVal) {
this.$emit('update:foo', val)
}
}
...
})
Vue實現雙向數據綁定的原理分析
v-model
用于雙向綁定數據,其本質為語法糖,即
<comp v-model="something"></comp>
相當于
<comp v-bind:value="something" v-on:input="something = $event.target.value"></comp>
props .sync
修飾符 用于雙向數據綁定,其本質也為語法糖,即
<comp :foo.sync="bar"></comp>
相當于
<comp :foo="bar" @update:foo="val => bar = val"></comp>
以上,我們可以很容易的看出,實現一個自定義組件的雙向數據綁定,其實就是父組件傳遞一個屬性給子組件,在子組件中該屬性的值改變時顯式的去觸發一個事件(v-model觸發input事件,.sync觸發update:props事件)。
那在Vue.js內部究竟是如何實現雙向數據綁定的呢?我們下面繼續分析下。
在Vue.js中,采用觀察者-訂閱者模式來進行雙向數據綁定,通過Object.defineProperty()
方法來劫持各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
既然是觀察者-訂閱者模式,那觀察者-訂閱者是如何實現的呢?我們來看源碼
觀察者(Observer)關鍵代碼:
class Observer {
constructor (data) {
this.walk(data)
}
walk (data) {
// 遍歷 data 對象屬性,調用 defineReactive 方法
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
defineReactive(data, keys[i], data[keys[i]])
}
}
}
訂閱者(Watcher)關鍵代碼:
class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb
this.vm = vm
this.expOrFn = expOrFn
this.value = this.get()
}
update(){
this.run()
}
run(){
const value = this.get()
if(value !==this.value){
this.value = value
this.cb.call(this.vm)
}
}
get(){
const value = this.vm._data[this.expOrFn]
return value
}
}
觀察者和訂閱者都有了,但是它們如何進行通信呢?
首先,
觀察者會遍歷 data 對象的所有屬性,每個屬性通過調用 defineReactive
方法,轉換為getter/setter
defineReactive 關鍵代碼如下:
function defineReactive(obj, key, value) {
var dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
if (Dep.target) {
dep.depend() // 依賴收集
}
return value
},
set: function reactiveSetter(newVal) {
if (value === newVal) {
return
}
value = newVal
// 對新值進行觀測,如果改變則通知訂閱者
dep.notify()
}
})
}
defineReactive 方法將data的屬性轉換為訪問器屬性
get時進行依賴收集,
set時,如果數據有改變,則進行訂閱通知
通過上面的分析,我們知道了,觀察者觀測到數據更新時會通知訂閱者,但是它是如何通知訂閱者(Watcher)的呢?
當然是通過訂閱器了!
訂閱器(Dep)關鍵代碼如下:
class Dep {
constructor (id, subs) {
this.id = 0++
this.subs = []
}
addSub () {
this.subs.push(sub)
}
removeSub () {
remove(this.subs, sub)
}
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()
}
}
}
通過訂閱器,訂閱者接收到數據改變的通知
由此,Observer
、 Dep
、 Watcher
就形成了一個數據響應系統,也就是Vue.js實現雙向數據綁定最核心的原理。