【JS Utils】Vue2 自定义计算属性 (兼容 uniapp 和 Vue 2.7 以前版本)
·
Vue2 自定义计算属性
- 简述:类似 Vue 2.7 / Vue 3.x 的
computed组合式API,可自由创建计算属性对象,灵活设置计算属性到Vue实例中。兼容 uniapp 环境和 Vue 2.7 以前版本下使用。
源码
/**
* 创建Vue计算属性对象
* @param {Vue} vm Vue实例
* @param {Function|Object} getterOrOptions 取值方法或配置项
* @param {Function} getterOrOptions.get 取值方法
* @param {Function} getterOrOptions.set 设值方法
* @param {Boolean} getterOrOptions.cache 是否使用缓存 (默认是)
* @return {Object}
*/
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 { get, set, cache } = typeof getterOrOptions == 'function' ? { get: getterOrOptions } : getterOrOptions
cache = get ? cache ?? true : false
let watcher = new Watcher(vm, get || noop, noop, { lazy: true })
return Object.defineProperties(Object.create(null), {
value: {
configurable: true,
enumerable: true,
set: set ? set.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
} : get || noop
},
effect: { configurable: true, enumerable: true, value: watcher },
__v_isRef: { configurable: true, value: true },
__v_isReadonly: { configurable: true, value: !set }
})
}
/**
* 设置Vue计算属性
* @param {Vue} vm Vue实例
* @param {Object} data 设置的对象
* @param {Object} props 计算属性表 { [key]: Function | { get(rawGet), set(newVal, rawSet), cache, configurable, enumerable } }
* @return {Object} 处理后的对象源
*/
function setVueComputeProps(vm, data, props) {
data ||= {}
let noop = createVueComputed.noop ||= function () {}
let getDesc = Object.getOwnPropertyDescriptor
let proto = Object.getPrototypeOf(data)
let watchers = vm === data ? vm._computedWatchers ||= Object.create(null) : null
for (let key in props) {
let { get, set, cache, configurable, enumerable } = typeof props[key] == 'function' ? { get: props[key] } : props[key]
let desc = getDesc(data, key) || proto && getDesc(proto, key) || {}
cache = get ? cache ?? true : false
configurable ??= desc.configurable ?? true
enumerable ??= desc.enumerable ?? true
let rawGet = desc.get?.bind(data), rawSet = desc.set?.bind(data)
let useGet = get ? function () { return get.call(this, rawGet) } : noop
let useSet = set ? function (val) { set.call(this, val, rawSet) } : noop
let ref = cache ? createVueComputed(vm, useGet) : null
if (watchers) watchers[key]?.teardown(), cache ? (watchers[key] = ref.effect) : delete watchers[key]
Object.defineProperty(data, key, {
configurable, enumerable, set: useSet,
get: cache ? getDesc(ref, 'value').get : useGet
})
}
return data
}
// 可绑定到Vue原型链中使用
Vue.prototype.$computed = function () {
return createVueComputed(this, ...arguments)
}
Vue.prototype.$setComputed = function () {
return setVueComputeProps(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原型链中 (可选)
Vue.prototype.$computed = function () {
return createVueComputed(this, ...arguments)
}
Vue.prototype.$setComputed = function () {
return setVueComputeProps(this, ...arguments)
}
export default {
data() {
return {
val1: 100,
val2: 200,
val3: 'test'
}
},
created() {
// 在 created 周期内且当前为根实例时 (this === this.$root),需嵌套在 this.$nextTick 下使用。
// 在 created 周期内且当前不为根实例或 mounted 周期内,可同步执行。
const initComputed = () => this.$setComputed(this, {
// 基本设置方式
demo1() {
let res = this.val1 + 1
console.log(`val1 get: ${res}`)
return res
},
// 带配置设置方式,与 computed 配置一致
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}`)
}
},
// 可以覆盖实例下原有的 data, props, computed, methods 内的属性
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>
更多推荐



所有评论(0)