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.constructorlet Watcher = createVueComputed.Watcher ||= (vm._watcher || vm.$parent?._watcher)?.constructorif (!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.getlet setter = isFn ? void 0 : getterOrOptions.setlet cache = getter ? isFn || (getterOrOptions.cache ?? true) : falselet 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.getOwnPropertyDescriptorlet 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].getlet setter = isFn ? void 0 : properties[key].setlet cache = getter ? isFn || (properties[key].cache ?? true) : falselet rawGet = desc.get?.bind(vm), rawSet = desc.set?.bind(vm)let useGet = getter ? function () { return getter.call(this, rawGet) } : nooplet useSet = setter ? function (val) { setter.call(this, val, rawSet) } : nooplet ref = cache ? createVueComputed(vm, useGet) : nullif (watchers[key]) watchers[key].teardown(), delete watchers[key]if (cache) watchers[key] = ref.effectObject.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 + 1console.log(`val1 get: ${res}`)return res},demo2: {cache: false,get() {let res = this.val2 + 2console.log(`val2 get: ${res}`)return res},set(newVal) {this.val2 = newValconsole.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.val2console.log(`val4 get: ${res}`)return res})},unbindVal4() {this.val4.effect.teardown()}}
}
</script>