Vue2 自定义计算属性
- 简述:类似 Vue 2.7 / Vue 3.x 的
computed 组合式API,可自由创建计算属性对象,灵活设置计算属性到Vue实例中。兼容 uniapp 环境和 Vue 2.7 以前版本下使用。
源码
function createVueComputed(vm, getterOrOptions) {
let Dep = createVueComputed.Dep ||= (vm._data || vm.$parent?._data)?.__ob__.dep.constructor
let Watcher = createVueComputed.Watcher ||= (vm._watcher || vm.$parent?._watcher)?.constructor
if (!Dep || !Watcher) throw new Error('Cannot find necessary dependency methods.')
let noop = createVueComputed.noop ||= function () {}
let isFn = typeof getterOrOptions == 'function'
let getter = isFn ? getterOrOptions : getterOrOptions.get
let setter = isFn ? void 0 : getterOrOptions.set
let cache = getter ? isFn || (getterOrOptions.cache ?? true) : false
let watcher = new Watcher(vm, getter || noop, noop, { lazy: true })
return Object.defineProperties(Object.create(null), {
value: {
configurable: true,
enumerable: true,
set: setter ? setter.bind(null) : noop,
get: cache ? function () {
if (watcher.dirty) watcher.evaluate()
if (Dep.target) {
Dep.target.onTrack && Dep.target.onTrack({
effect: Dep.target,
target: this,
type: 'get',
key: 'value'
})
watcher.depend()
}
return watcher.value
} : getter || noop
},
effect: { configurable: true, enumerable: true, value: watcher },
__v_isRef: { configurable: true, value: true },
__v_isReadonly: { configurable: true, value: !setter }
})
}
function setVueInstanceComputed(vm, properties) {
let noop = setVueInstanceComputed.noop ||= function () {}
let getDesc = Object.getOwnPropertyDescriptor
let proto = Object.getPrototypeOf(vm)
let watchers = vm._computedWatchers ||= Object.create(null)
for (let key in properties) {
let desc = getDesc(vm, key) || getDesc(proto, key) || {}
let isFn = typeof properties[key] == 'function'
let getter = isFn ? properties[key] : properties[key].get
let setter = isFn ? void 0 : properties[key].set
let cache = getter ? isFn || (properties[key].cache ?? true) : false
let rawGet = desc.get?.bind(vm), rawSet = desc.set?.bind(vm)
let useGet = getter ? function () { return getter.call(this, rawGet) } : noop
let useSet = setter ? function (val) { setter.call(this, val, rawSet) } : noop
let ref = cache ? createVueComputed(vm, useGet) : null
if (watchers[key]) watchers[key].teardown(), delete watchers[key]
if (cache) watchers[key] = ref.effect
Object.defineProperty(vm, key, {
configurable: desc.configurable ?? true,
enumerable: desc.enumerable ?? true,
set: useSet,
get: cache ? getDesc(ref, 'value').get : useGet
})
}
}
Vue.prototype.$computed = function () {
return createVueComputed(this, ...arguments)
}
Vue.prototype.$setComputed = function () {
return setVueInstanceComputed(this, ...arguments)
}
使用示例
<template>
<div>
<div>
Demo1: {{ demo1 }} <button @click="val1++">++</button>
</div>
<div>
Demo2: {{ demo2 }} <button @click="demo2++">++</button>
</div>
<div>
Demo3: <input type="text" v-model="val3" />
</div>
<div>
Bind new computed ref: {{ val4 ? val4.value : '' }}
<div>
<button v-if="val4" @click="bindVal4">bind</button>
<button v-else @click="unbindVal4">unbind</button>
</div>
</div>
</div>
</template>
<script>
Vue.prototype.$computed = function () {
return createVueComputed(this, ...arguments)
}
Vue.prototype.$setComputed = function () {
return setVueInstanceComputed(this, ...arguments)
}
export default {
data() {
return {
val1: 100,
val2: 200,
val3: 'test'
}
},
created() {
const initComputed = () => this.$setComputed({
demo1() {
let res = this.val1 + 1
console.log(`val1 get: ${res}`)
return res
},
demo2: {
cache: false,
get() {
let res = this.val2 + 2
console.log(`val2 get: ${res}`)
return res
},
set(newVal) {
this.val2 = newVal
console.log(`val2 set: ${newVal}`)
}
},
val3: {
get(rawGet) {
let res = `x_${rawGet()}`
console.log(`val3 get: ${res}`)
return res
},
set(newVal, rawSet) {
rawSet((newVal = newVal.replace(/^x_/, '')))
console.log(`val3 set: ${newVal}`)
}
}
})
this === this.$root ? this.$nextTick(initComputed) : initComputed()
},
methods: {
bindVal4() {
this.val4 = this.$computed(() => {
let res = this.val1 + this.val2
console.log(`val4 get: ${res}`)
return res
})
},
unbindVal4() {
this.val4.effect.teardown()
}
}
}
</script>