watch基本使用
computed
关注点在模板
场景:专注于模板中复杂的逻辑
特点:当计算属性所依赖的数据发生了变化时,会重新执行该方法,会缓存上一次的结果
watch
关注点在数据更新
场景:监听某个数据,给数据绑定侦听函数,当数据改变时,执行该函数
特点:当数据更新时,需要完成什么样的逻辑
可以监听computed里面的属性
const App = {
data() {
return {
num: 1
}
},
template: `
<div>
<p>{{ num }}</p>
<button @click="changeNum">CHANGE NUM</button>
</div>
`,
watch: {
// 第一次默认不执行
// 可以获取到新值和旧值
num(newValue, oldValue) {
if(newValue !== oldValue) {
this.getData()
}
}
},
methods: {
changeNum() {
this.num += 1
},
getData() {
console.log('当num变化时,获取数据')
}
}
}
Vue.createApp(App).mount('#app')
computed和watch实现
app.js
import Vue from './Vue'
const vm = new Vue({
data() {
return {
a: 1,
b: 2
}
},
computed: {
total() {
console.log('computed', this)
return this.a + this.b
},
},
watch: {
total(newValue, oldValue) {
console.log('total', newValue, oldValue)
},
a(newValue, oldValue) {
console.log('a', newValue, oldValue)
},
b(newValue, oldValue) {
console.log('b', newValue, oldValue)
}
}
})
console.log(vm)
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
vm.a = 100
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
vm.b = 200
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
Vue -> index.js
import reactive from './reactive'
import Computed from './Computed'
import Watcher from './Watcher'
class Vue {
constructor(options) {
const { data, computed, watch } = options
this.$data = data()
this.init(this, computed, watch)
}
init(vm, computed, watch) {
this.initData(vm)
const computedIns = this.initComputed(vm, computed)
const watcherIns = this.initWatcher(vm, watch)
this.$update = computedIns.update.bind(computedIns)
this.$watcher = watcherIns.invoke.bind(watcherIns)
// this.initWatcher(vm, watch)
}
initData(vm) {
reactive.dataReactive(vm, (key, value) => {
console.log(key, value)
}, (key, newValue, oldValue) => {
// 每次更改数据时 执行
// 新值旧值相等直接reutrn
if(newValue === oldValue) return
// 在computed属性结果变化时 watcher 通过callback触发
this.$update(key, this.$watcher)
// 这里只能监听到data里面的数据 监听不到computed里面的数据挂载到vm实例里面的
this.$watcher(key, newValue, oldValue)
})
}
initComputed(vm, computed) {
const computedIns = new Computed()
for(let key in computed) {
computedIns.addComputed(vm, computed, key)
}
return computedIns
}
initWatcher(vm, watch) {
const watcherIns = new Watcher()
for(let key in watch) {
watcherIns.addWatcherData(vm, watch, key)
}
return watcherIns
}
}
export default Vue
Vue -> reactive.js
class Reactive {
constructor(vm) {
}
dataReactive(vm, __get__, __set__) {
const _data = vm.$data
for(let key in _data) {
Object.defineProperty(vm, key, {
get() {
__get__ && __get__(key, _data[key])
return _data[key]
},
set(newValue) {
const oldValue = _data[key]
_data[key] = newValue
__set__ && __set__(key, _data[key], oldValue)
}
})
}
}
}
export default new Reactive()
Vue -> Computed.js
class Computed {
constructor() {
/**
* computedData []
* {
* key: value,
* value: key get fn()
* get: fn
* dep: [a, b...] 依赖的数据
* }
*/
this.computedData = []
}
addComputed(vm, computed, key) {
const descriptor = Object.getOwnPropertyDescriptor(computed, key), // 拿到对象里面某个自有属性的属性描述符
descriptorFn = descriptor.value.get || descriptor.value, // 拿到某个自由属性的方法
value = descriptorFn.call(vm), // 执行该方法计算出conputed属性的值 需要更改this指向 谁调用指向谁 默认会指向computedData -> {}
get = descriptorFn.bind(vm), // 拿到某个自由属性的方法 get
dep = this._collectDep(descriptorFn); // 取出方法所依赖的数据
this.addComputedProp({
key,
value,
get,
dep
})
// 找到计算属性里面的值
const selectItem = this.computedData.find(item => item.key === key)
// 存在时 赋值给vm 实例上
if(selectItem) {
Object.defineProperty(vm, selectItem.key, {
// 当每次获取计算属性里面的值的时候 直接从vm中拿
get() {
return selectItem.value
},
set(newValue) {
// 每次更改都需要重新调用get 不直接用newValue
selectItem.value = selectItem.get()
}
})
}
}
update(key, cb) {
// 当计算属性里面所依赖的数据变化了的话,直接重新执行get
this.computedData.map(item => {
const dep = item.dep,
_key = dep.find(el => el === key);
if(_key) {
const oldValue = item.value
item.value = item.get()
// 触发watcher
// key要使用item.key 其他的key是计算属性所依赖的key
cb && cb(item.key, item.value, oldValue)
}
})
}
addComputedProp(prop) {
this.computedData.push(prop)
}
_collectDep(fn) {
// 找到方法里面this.的数据
const matched = fn.toString().match(/this\.(.+?)/g)
return matched.map(item => item.split('.')[1])
}
}
export default Computed
Vue -> Watcher.js
class Watcher {
constructor() {
/**
* watcherData = []
* {
* key: 值,
* fn: key fn
* }
*/
this.watcherData = []
}
addWatcherData(vm, watch, key) {
this.addWatcherDataProp({
key,
fn: watch[key].bind(vm)
})
}
invoke(key, newValue, oldValue) {
this.watcherData.map(item => {
// key相同时 就执行wathcer fn
if(item.key === key) {
item.fn(newValue, oldValue)
}
})
}
addWatcherDataProp(prop) {
this.watcherData.push(prop)
}
}
export default Watcher
源码地址:https://gitee.com/me_peng/review/tree/f57ecc02337329329e0f14774a2aed7e6ff55588/vue/5.watch