ts watch路由 参数变化_【Ts重构Vue】05-实现computed和watch功能

如何创建computed和watch?

在项目中computed和watch非常实用,它们是如何实现的呢?

我们的编码目标是下面的demo能够成功渲染,最终渲染结果

未读消息:2

let v = new Vue({

el: '#app',

data () {

return {

news: [1]

}

},

computed: {

newsCount() {

return this.news.length

},

},

render (h) {

return h('h1', '未读消息:' + this.newsCount)

}

})

setTimeout(() => {

v.news.push(2)

}, 1000)

复制代码

Vue响应式原理

85e3b0706f45358f5504112fed651bed.png

根据上图可以知道,Vue将数据构造为响应式的,如果需要监听数据则要新建Watch的实例,建立Dep和Watch之间联系。

实现watch

watch的常见用法如下:

watch: {

news () {

console.log('watch news!')

}

}

复制代码

watch功能依托Watch类实现,在Vue初始化时,为所有watch属性创建Watch实例。

function initWatch(vm: Vue) {

const watch = vm.$options.watch

for (let key in watch) {

new Watch(vm._proxyThis, key, watch[key], { user: true })

}

}

复制代码

new Watch实例化过程中,会将key转为函数,执行该函数可以获取被监听的属性值,另外会将watch[key]函数保存在this.cb变量中。

this.getter = isFunction(key) ? key : parsePath(key) || noop

this.cb = cb

function parsePath(key: string): any {

return function(vm: any) {

return vm[key]

}

}

复制代码

接着直接执行上一步的函数this.getter,收集所有依赖。

private get(): any {

let vm = this.vm

pushTarget(this)

let value = this.getter.call(vm, vm)

popTarget()

return value

}

复制代码

当被监听属性发生变化时,会通知Watch实例进行更新,从而执行this.cb.call(vm, value, this.value)函数。

实现computed

computed的调用形式主要有以下两种:

computed: {

newsCount() {

return this.news.length

},

newsStr: {

get () {

return this.news.join(',')

},

set (val) {

this.news = val.split(',')

}

}

}

复制代码

computed属性可以定义get和set函数,因此比较特殊:1.它的值依赖于其他数据属性;2.修改它也会驱动视图进行更新。

computed功能同样依赖Watch类实现,在Vue初始化时,为所有的computed属性创建watch实例:new Watch(vm._proxyThis, getter, noop, {lazy: true})。

function initComputed(vm: Vue) {

let proxyComputed: any

const computed = vm.$options.computed

if (!isPlainObject(computed)) return

for (let key in computed) {

let userDef = computed[key]

let getter = isFunction(userDef) ? userDef : userDef.get

vm._computedWatched[key] = new Watch(vm._proxyThis, getter, noop, {

lazy: true

})

}

vm.$options.computed = proxyComputed = observeComputed(

computed,

vm._computedWatched,

vm._proxyThis

)

for (let key in computed) {

proxyForVm(vm._proxyThis, proxyComputed, key)

}

}

复制代码

接着将computed属性本身设置为响应式,同时调用createComputedGetter对属性进行封装。

当修改computed属性时,computed触发闭包变量dep.notify通知渲染更新。

当修改news属性时,会触发Vue进行渲染更新,在重新获取computed属性值的时候,会执行createComputedGetter封装后的函数,其本质是执行上一步的getter函数,并将计算结果返回。

function observeComputed(obj: VueComputed, _computedWatched: any, proxyThis: any): Object {

if (!isPlainObject(obj) || isProxy(obj)) return obj

let proxyObj = createProxy(obj)

for (let key in obj) {

defineComputed(proxyObj, key, obj[key], _computedWatched[key], proxyThis)

}

return proxyObj

}

function defineComputed(

obj: any,

key: string,

userDef: VueComputedMethod,

watcher: any,

proxyThis: any

): void {

if (!isProxy(obj)) return

let dep: Dep = new Dep()

const handler: any = {}

if (isFunction(userDef)) {

handler.get = createComputedGetter(watcher)

handler.set = noop

} else if (isObject(userDef)) {

handler.get = createComputedGetter(watcher)

handler.set = userDef.set || noop

}

defineProxyObject(obj, key, {

get(target, key) {

Dep.Target && dep.depend()

return handler.get.call(proxyThis)

},

set(target, key, newVal) {

handler.set.call(proxyThis, newVal)

dep.notify()

return true

}

})

}

function createComputedGetter(watcher: Watch): Function {

return function computedGetter() {

if (watcher) {

// 计算值

watcher.evaluate()

// 将computed-dep添加watch对象

Dep.Target && watcher.depend()

return watcher.value

}

}

}

复制代码

分析computed肯定要提到其缓存特性,这又是如何实现的?

我们知道获取computed的属性值时,会执行createComputedGetter封装后的函数,通过给Watch类添加dirty属性控制是否重新计算computed的属性值。Watch类的其他函数中肯定需要配合修改,如evaluate和update方法。

function createComputedGetter(watcher: Watch): Function {

return function computedGetter() {

if (watcher) {

// 计算值

if (watcher.dirty) {

watcher.evaluate()

}

// 将computed-dep添加watch对象

Dep.Target && watcher.depend()

return watcher.value

}

}

}

复制代码

Vue的数据处理流程

Vue在实例化过程中,会对传入的数据进行初始化处理。

首先肯定是为prop、data创建闭包变量dep,接着才是初始化computed和watch的属性,在后者中创建Watch实例监听属性的变化。

initProps(this)

initMethods(this)

initData(this)

initComputed(this)

initWatch(this)

复制代码

总结

Vue的响应式渲染依赖Dep和Watch,computed和watch功能也依赖它们,另外,Vue还封装了方法$watch对属性进行监听。为了支持上述功能,Watch和Dep添加了一些配置项,在理解源码时,可以进行一定忽略。

Dep和Watch设计的相当巧妙,我们自己编程能不能想到这样的方式?推荐学习下设计模式,或许能有所帮助。

系列文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值