11/ customRef
自定义一个ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并应返回一个带有 get 和 set 的对象。
如果上面那段没看懂的话,可以跳过。
简单的说,就是在 ref 原有的 set、get 的基础上,加入我们自己写的代码,以达到一定的目的。
话说,官网写的例子还真是…… 反正一开始我是没看懂,后来又反复看,又把代码敲出来运行,又查了一下“debounce”的意思。最后终于明白了,这是一个防抖(延迟响应)的代码。
一般用户在文本框里输入内容,立即就会响应,但是如果在查询功能里面的话就会有点小郁闷。用户输入个“1”,立即就去后端查询“1”, 然后用户又输入个“2”,立即又去后端查询“12”, 然后用户又输入个“3”,立即又去后端查询“123”。…… 这个响应是很快,但是有点“折腾”的嫌疑,那么能不能等用户把“123”都输入好了,再去后端查询呢?
官网的例子就是实现这样的功能的,我们把例子完善一下,就很明显了。
const useDebouncedRef = (value, delay = 200) => { let timeout return customRef((track, trigger) => { return { get() { track() // vue内部的跟踪函数 return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger() // vue内部的自动更新的函数。 }, delay) // 延迟时间 } } }) } setup () { const text = useDebouncedRef('hello', 1000) // 定义一个 v-model console.log('customRef', text) const update = () => { // 修改后延迟刷新 text.value = '1111' + new Date().valueOf() } return { text, update } }
customRef 对象:{{text}} <br><br> <input v-model="text" type="text">
- get 没有改变,直接用原方法。
- set 使用 setTimeout 实现延迟响应的功能,把 Vue 内部的 trigger() 放在 setTimeout 里面就好。
这样就可以了,延迟多长的时间可以自定义,这里是一秒。一秒内用户输入的内容,会一次性更新,而不是每输入一个字符就更新一次。
- v-model="text" 可以作为 v-model 来使用。
12/ customRef 的 源码
我们再来看看 customRef 内部源码的实现方式。
function customRef(factory) { return new CustomRefImpl(factory); } class CustomRefImpl { constructor(factory) { this.__v_isRef = true; const { get, set } = factory(() => track(this, "get" /* GET */, 'value'), () => trigger(this, "set" /* SET */, 'value')); this._get = get; this._set = set; } get value() { return this._get(); } set value(newVal) { this._set(newVal); } }
很简单的是不是,就是先这样,然后在那样,最后就搞定了。好吧,就是把 factory参数解构出来,分成set和get,做成内部函数,然后在内部的set和get里面调用。
13/ 自定义 ref 的实例 —— 写一个自己的计算属性。
一提到计算属性,我们会想到 Vue 提供的 computed,那么如果让我们使用自定义ref 来实现计算属性的功能的话,要如何实现呢?(注意:只是练习用)
我们可以这样来实现:
const myComputed = (_get, _set) => { return customRef((track, trigger) => { return { get() { track() if (typeof _get === 'function') { return _get() } else { console.warn(`没有设置 get 方法`) } }, set(newValue) { if (typeof _set === 'function') { _set(newValue) trigger() } else { console.warn(`没有设置 set 方法`) } } } }) } setup () { const refCount = ref(0) const myCom = myComputed(() => refCount.value + 1) // const myCom = myComputed(() => refCount.value, (val) => { refCount.value = val}) const update = () => { // 修改原型 refCount.value = 3 } const setRef = () => { // 直接赋值 refCount.value += 1 } return { refCount, // 基础类型 myCom, // 引用类型 update, // 修改属性 setRef // 直接设置 } }
<div> 展示 自定义 的 customRef 实现计算属性 <br> ref 对象:{{refCount}} <br><br> 自定义的计算属性 对象:{{myCom}} <br><br> <input v-model="myCom" type="text"> <el-button @click="update" type="primary">修改属性el-button><br><br> div>
- myComputed 首先定义一个函数,接收两个参数,一个get,一个set。
- customRef 返回一个 customRef 的实例,内部设置get 和 set。
- 调用方法 调用的时候,可以只传入get函数,也可以传入get、set两个函数。修改 refCount.value 的时候,v-model 的 myCom 也会发生变化。
- 实用性 那么这种方式有啥使用效果呢?在做组件的时候,组件的属性props是不能直接用在内部组件的 v-model 里面的,因为props只读,那么怎么办呢?
可以在组件内部设置一个ref,然后对props做监听,或者用computed来做。除了上面的几种方法外,也可以用这里的方法来实现,把 refCount 变成 props 的属性就可以了,然后set里面使用 smit 提交。
14/ computed
写完了自己的计算属性后,我们还是来看看 Vue 提供的计算属性。代码来自于 vue.global.js ,调整了一下先后顺序。
function computed(getterOrOptions) { let getter; let setter; if (isFunction(getterOrOptions)) { getter = getterOrOptions; setter = () => { console.warn('Write operation failed: computed value is readonly'); } ; } else { getter = getterOrOptions.get; setter = getterOrOptions.set; } return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set); } class ComputedRefImpl { constructor(getter, _setter, isReadonly) { this._setter = _setter; this._dirty = true; this.__v_isRef = true; this.effect = effect(getter, { lazy: true, scheduler: () => { if (!this._dirty) { this._dirty = true; trigger(toRaw(this), "set" /* SET */, 'value'); } } }); this["__v_isReadonly" /* IS_READONLY */] = isReadonly; } get value() { if (this._dirty) { this._value = this.effect(); this._dirty = false; } track(toRaw(this), "get" /* GET */, 'value'); return this._value; } set value(newValue) { this._setter(newValue); } }
- computed 暴露给我们用的方法,来定义一个计算属性。只有一个参数,可以是一个函数(function),也可以是一个对象。内部会做一个判断,然后做拆分。
- ComputedRefImpl 是不是有点眼熟?这个是 ref 同款系列,都是 RefImpl 风格的,而且内部代码结构也很相似。这个是computed 的主体类,也是先定义内部属性,然后设置value的get和set。在get和set里面,调用外部设置的函数。
源码:
https://gitee.com/naturefw/nf-vue-cdn/tree/master/cdn/project-compositionapi
在线演示:
https://naturefw.gitee.io/nf-vue-cdn/cdn/project-compositionapi/
本文作者:自然框架
个人网址:jyk.cnblogs.com
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。