vue3?ref實(shí)現(xiàn)響應(yīng)式的方法
前言
最近在我的vue源碼交流群
有位面試官分享了一道他的面試題:vue3的ref是如何實(shí)現(xiàn)響應(yīng)式的?下面有不少小伙伴回答的是Proxy
,其實(shí)這些小伙伴只回答對了一半。
當(dāng)ref接收的是一個(gè)對象時(shí)確實(shí)是依靠
Proxy
去實(shí)現(xiàn)響應(yīng)式的。但是ref還可以接收
string
、number
或boolean
這樣的原始類型,當(dāng)是原始類型時(shí),響應(yīng)式就不是依靠Proxy
去實(shí)現(xiàn)的,而是在value
屬性的getter
和setter
方法中去實(shí)現(xiàn)的響應(yīng)式。
本文將通過debug的方式帶你搞清楚當(dāng)ref接收的是對象和原始類型時(shí),分別是如何實(shí)現(xiàn)響應(yīng)式的。注:本文中使用的vue版本為3.4.19
。
看個(gè)demo
還是老套路,我們來搞個(gè)demo,index.vue
文件代碼如下:
<template> <div> <p>count的值為:{{ count }}</p> <p>user.count的值為:{{ user.count }}</p> <button @click="count++">count++</button> <button @click="user.count++">user.count++</button> </div> </template> <script setup lang="ts"> import { ref } from "vue"; const count = ref(0); const user = ref({ count: 0, }); </script>
在上面的demo中我們有兩個(gè)ref變量,count
變量接收的是原始類型,他的值是數(shù)字0。
count
變量渲染在template的p標(biāo)簽中,并且在button的click事件中會(huì)count++
。
user
變量接收的是對象,對象有個(gè)count
屬性。
同樣user.count
也渲染在另外一個(gè)p標(biāo)簽上,并且在另外一個(gè)button的click事件中會(huì)user.count++
。
接下來我將通過debug的方式帶你搞清楚,分別點(diǎn)擊count++
和user.count++
按鈕時(shí)是如何實(shí)現(xiàn)響應(yīng)式的。
開始打斷點(diǎn)
第一步從哪里開始下手打斷點(diǎn)呢?
既然是要搞清楚ref是如何實(shí)現(xiàn)響應(yīng)式的,那么當(dāng)然是給ref打斷點(diǎn)吖,所以我們的第一個(gè)斷點(diǎn)是打在const count = ref(0);
代碼處。這行代碼是運(yùn)行時(shí)代碼,是跑在瀏覽器中的。
要在瀏覽器中打斷點(diǎn),需要在瀏覽器的source面板中打開index.vue
文件,然后才能給代碼打上斷點(diǎn)。
那么第二個(gè)問題來了,如何在source面板中找到我們這里的index.vue
文件呢?
很簡單,像是在vscode中一樣使用command+p
(windows中應(yīng)該是control+p)就可以喚起一個(gè)輸入框。在輸入框里面輸入index.vue
,然后點(diǎn)擊回車就可以在source面板中打開index.vue
文件。如下圖:
然后我們就可以在瀏覽器中給const count = ref(0);
處打上斷點(diǎn)了。
RefImpl類
刷新頁面此時(shí)斷點(diǎn)將會(huì)停留在const count = ref(0);
代碼處,讓斷點(diǎn)走進(jìn)ref
函數(shù)中。在我們這個(gè)場景中簡化后的ref
函數(shù)代碼如下:
function ref(value) { return createRef(value, false); }
可以看到在ref
函數(shù)中實(shí)際是直接調(diào)用了createRef
函數(shù)。
接著將斷點(diǎn)走進(jìn)createRef
函數(shù),在我們這個(gè)場景中簡化后的createRef
函數(shù)代碼如下:
function createRef(rawValue, shallow) { return new RefImpl(rawValue, shallow); }
從上面的代碼可以看到實(shí)際是調(diào)用RefImpl
類new了一個(gè)對象,傳入的第一個(gè)參數(shù)是rawValue
,也就是ref綁定的變量值,這個(gè)值可以是原始類型,也可以是對象、數(shù)組等。
接著將斷點(diǎn)走進(jìn)RefImpl
類中,在我們這個(gè)場景中簡化后的RefImpl
類代碼如下:
class RefImpl { private _value: T private _rawValue: T constructor(value) { this._rawValue = toRaw(value); this._value = toReactive(value); } get value() { trackRefValue(this); return this._value; } set value(newVal) { newVal = toRaw(newVal); if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = toReactive(newVal); triggerRefValue(this, 4, newVal); } } }
從上面的代碼可以看到RefImpl
類由三部分組成:constructor
構(gòu)造函數(shù)、value
屬性的getter
方法、value
屬性的setter
方法。
RefImpl類的constructor構(gòu)造函數(shù)
constructor
構(gòu)造函數(shù)中的代碼很簡單,如下:
constructor(value) { this._rawValue = toRaw(value); this._value = toReactive(value); }
在構(gòu)造函數(shù)中首先會(huì)將toRaw(value)
的值賦值給_rawValue
屬性中,這個(gè)toRaw
函數(shù)是vue暴露出來的一個(gè)API,他的作用是根據(jù)一個(gè) Vue 創(chuàng)建的代理返回其原始對象。因?yàn)?code>ref函數(shù)不光能夠接受普通的對象和原始類型,而且還能接受一個(gè)ref對象,所以這里需要使用toRaw(value)
拿到原始值存到_rawValue
屬性中。
接著在構(gòu)造函數(shù)中會(huì)執(zhí)行toReactive(value)
函數(shù),將其執(zhí)行結(jié)果賦值給_value
屬性。toReactive
函數(shù)看名字你應(yīng)該也猜出來了,如果接收的value是原始類型,那么就直接返回value。如果接收的value不是原始類型(比如對象),那么就返回一個(gè)value轉(zhuǎn)換后的響應(yīng)式對象。這個(gè)toReactive
函數(shù)我們在下面會(huì)講。
_rawValue
屬性和_value
屬性都是RefImpl
類的私有屬性,用于在RefImpl
類中使用的,而暴露出去的也只有value
屬性。
經(jīng)過constructor
構(gòu)造函數(shù)的處理后,分別給兩個(gè)私有屬性賦值了:
_rawValue
中存的是ref綁定的值的原始值。如果ref綁定的是原始類型,比如數(shù)字0,那么
_value
屬性中存的就是數(shù)字0。如果ref綁定的是一個(gè)對象,那么
_value
屬性中存的就是綁定的對象轉(zhuǎn)換后的響應(yīng)式對象。
RefImpl類的value屬性的getter方法
我們接著來看value
屬性的getter
方法,代碼如下:
get value() { trackRefValue(this); return this._value; }
當(dāng)我們對ref的value屬性進(jìn)行讀操作時(shí)就會(huì)走到getter
方法中。
我們知道template經(jīng)過編譯后會(huì)變成render函數(shù),執(zhí)行render函數(shù)會(huì)生成虛擬DOM,然后由虛擬DOM生成真實(shí)DOM。
在執(zhí)行render函數(shù)期間會(huì)對count
變量進(jìn)行讀操作,所以此時(shí)會(huì)觸發(fā)count
變量的value
屬性對應(yīng)的getter
方法。
在getter
方法中會(huì)調(diào)用trackRefValue
函數(shù)進(jìn)行依賴收集,由于此時(shí)是在執(zhí)行render函數(shù)期間,所以收集的依賴就是render函數(shù)。
最后在getter
方法中會(huì)return返回_value
私有屬性。
RefImpl類的value屬性的setter方法
我們接著來看value
屬性的setter
方法,代碼如下:
set value(newVal) { newVal = toRaw(newVal); if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = toReactive(newVal); triggerRefValue(this, 4, newVal); } }
當(dāng)我們對ref的value的屬性進(jìn)行寫操作時(shí)就會(huì)走到setter
方法中,比如點(diǎn)擊count++
按鈕,就會(huì)對count
的值進(jìn)行+1
,觸發(fā)寫操作走到setter
方法中。
給setter
方法打個(gè)斷點(diǎn),點(diǎn)擊count++
按鈕,此時(shí)斷點(diǎn)將會(huì)走到setter
方法中。初始化count
的值為0,此時(shí)點(diǎn)擊按鈕后新的count
值為1,所以在setter
方法中接收的newVal
的值為1。如下圖:
從上圖中可以看到新的值newVal
的值為1,舊的值this._rawValue
的值為0。然后使用if (hasChanged(newVal, this._rawValue))
判斷新的值和舊的值是否相等,hasChanged
的代碼也很簡單,如下:
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
Object.is
方法大家平時(shí)可能用的比較少,作用也是判斷兩個(gè)值是否相等。和==
的區(qū)別為Object.is
不會(huì)進(jìn)行強(qiáng)制轉(zhuǎn)換,其他的區(qū)別大家可以參看mdn上的文檔。
使用hasChanged
函數(shù)判斷到新的值和舊的值不相等時(shí)就會(huì)走到if語句里面,首先會(huì)執(zhí)行this._rawValue = newVal
將私有屬性_rawValue
的值更新為最新值。接著就是執(zhí)行this._value = toReactive(newVal)
將私有屬性_value
的值更新為最新值。
最后就是執(zhí)行triggerRefValue
函數(shù)觸發(fā)收集的依賴,前面我們講過了在執(zhí)行render函數(shù)期間由于對count
變量進(jìn)行讀操作。觸發(fā)了getter
方法,在getter
方法中將render函數(shù)作為依賴進(jìn)行收集了。
所以此時(shí)執(zhí)行triggerRefValue
函數(shù)時(shí)會(huì)將收集的依賴全部取出來執(zhí)行一遍,由于render函數(shù)也是被收集的依賴,所以render函數(shù)會(huì)重新執(zhí)行。重新執(zhí)行render函數(shù)時(shí)從count
變量中取出的值就是新值1,接著就是生成虛擬DOM,然后將虛擬DOM掛載到真實(shí)DOM上,最終在頁面上count
變量綁定的值已經(jīng)更新為1了。
看到這里你是不是以為關(guān)于ref實(shí)現(xiàn)響應(yīng)式已經(jīng)完啦?
我們來看demo中的第二個(gè)例子,user
對象,回顧一下在template和script中關(guān)于user
對象的代碼如下:
<template> <div> <p>user.count的值為:{{ user.count }}</p> <button @click="user.count++">user.count++</button> </div> </template> <script setup lang="ts"> import { ref } from "vue"; const user = ref({ count: 0, }); </script>
在button按鈕的click事件中執(zhí)行的是:user.count++
,前面我們講過了對ref的value屬性進(jìn)行寫操作會(huì)走到setter
方法中。但是我們這里ref綁定的是一個(gè)對象,點(diǎn)擊按鈕時(shí)也不是對user.value
屬性進(jìn)行寫操作,而是對user.value.count
屬性進(jìn)行寫操作。所以在這里點(diǎn)擊按鈕不會(huì)走到setter
方法中,當(dāng)然也不會(huì)重新執(zhí)行收集的依賴。
那么當(dāng)ref綁定的是對象時(shí),我們改變對象的某個(gè)屬性時(shí)又是怎么做到響應(yīng)式更新的呢?
這種情況就要用到Proxy
了,還記得我們前面講過的RefImpl
類的constructor
構(gòu)造函數(shù)嗎?代碼如下:
class RefImpl { private _value: T private _rawValue: T constructor(value) { this._rawValue = toRaw(value); this._value = toReactive(value); } }
其實(shí)就是這個(gè)toReactive
函數(shù)在起作用。
Proxy實(shí)現(xiàn)響應(yīng)式
還是同樣的套路,這次我們給綁定對象的名為user
的ref打個(gè)斷點(diǎn),刷新頁面代碼停留在斷點(diǎn)中。還是和前面的流程一樣最終斷點(diǎn)走到RefImpl
類的構(gòu)造函數(shù)中,當(dāng)代碼執(zhí)行到this._value = toReactive(value)
時(shí)將斷點(diǎn)走進(jìn)toReactive
函數(shù)。代碼如下:
const toReactive = (value) => (isObject(value) ? reactive(value) : value);
在toReactive
函數(shù)中判斷了如果當(dāng)前的value
是對象,就返回reactive(value)
,否則就直接返回value。這個(gè)reactive
函數(shù)你應(yīng)該很熟悉,他會(huì)返回一個(gè)對象的響應(yīng)式代理。因?yàn)?code>reactive不接收number這種原始類型,所以這里才會(huì)判斷value
是否是對象。
我們接著將斷點(diǎn)走進(jìn)reactive
函數(shù),看看他是如何返回一個(gè)響應(yīng)式對象的,在我們這個(gè)場景中簡化后的reactive
函數(shù)代碼如下:
function reactive(target) { return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ); }
從上面的代碼可以看到在reactive
函數(shù)中是直接返回了createReactiveObject
函數(shù)的調(diào)用,第三個(gè)參數(shù)是mutableHandlers
。從名字你可能猜到了,他是一個(gè)Proxy對象的處理器對象,后面會(huì)講。
接著將斷點(diǎn)走進(jìn)createReactiveObject
函數(shù),在我們這個(gè)場景中簡化后的代碼如下:
function createReactiveObject( target, isReadonly2, baseHandlers, collectionHandlers, proxyMap ) { const proxy = new Proxy(target, baseHandlers); return proxy; }
在上面的代碼中我們終于看到了大名鼎鼎的Proxy
了,這里new了一個(gè)Proxy
對象。new的時(shí)候傳入的第一個(gè)參數(shù)是target
,這個(gè)target
就是我們一路傳進(jìn)來的ref綁定的對象。第二個(gè)參數(shù)為baseHandlers
,是一個(gè)Proxy對象的處理器對象。這個(gè)baseHandlers
是調(diào)用createReactiveObject
時(shí)傳入的第三個(gè)參數(shù),也就是我們前面講過的mutableHandlers
對象。
在這里最終將Proxy代理的對象進(jìn)行返回,我們這個(gè)demo中ref綁定的是一個(gè)名為user
的對象,經(jīng)過前面講過函數(shù)的層層return后,user.value
的值就是這里return返回的proxy
對象。
當(dāng)我們對user.value
響應(yīng)式對象的屬性進(jìn)行讀操作時(shí),就會(huì)觸發(fā)這里Proxy的get攔截。
當(dāng)我們對user.value
響應(yīng)式對象的屬性進(jìn)行寫操作時(shí),就會(huì)觸發(fā)這里Proxy的set攔截。
get
和set
攔截的代碼就在mutableHandlers
對象中。
Proxy的set和get攔截
在源碼中使用搜一下mutableHandlers
對象,看到他的代碼是這樣的,如下:
const mutableHandlers = new MutableReactiveHandler();
從上面的代碼可以看到mutableHandlers
對象是使用MutableReactiveHandler
類new出來的一個(gè)對象。
我們接著來看MutableReactiveHandler
類,在我們這個(gè)場景中簡化后的代碼如下:
class MutableReactiveHandler extends BaseReactiveHandler { set(target, key, value, receiver) { let oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (target === toRaw(receiver)) { if (hasChanged(value, oldValue)) { trigger(target, "set", key, value, oldValue); } } return result; } }
在上面的代碼中我們看到了set
攔截了,但是沒有看到get
攔截。
MutableReactiveHandler
類是繼承了BaseReactiveHandler
類,我們來看看BaseReactiveHandler
類,在我們這個(gè)場景中簡化后的BaseReactiveHandler
類代碼如下:
class BaseReactiveHandler { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); track(target, "get", key); return res; } }
在BaseReactiveHandler
類中我們找到了get
攔截,當(dāng)我們對Proxy代理返回的對象的屬性進(jìn)行讀操作時(shí)就會(huì)走到get
攔截中。
前面講過了經(jīng)過層層return后user.value
的值就是這里的proxy
響應(yīng)式對象,而我們在template中使用user.count
將其渲染到p標(biāo)簽上,在template中讀取user.count
,實(shí)際就是在讀取user.value.count
的值。
同樣的template經(jīng)過編譯后會(huì)變成render函數(shù),執(zhí)行render函數(shù)會(huì)生成虛擬DOM,然后將虛擬DOM轉(zhuǎn)換為真實(shí)DOM渲染到瀏覽器上。在執(zhí)行render函數(shù)期間會(huì)對user.value.count
進(jìn)行讀操作,所以會(huì)觸發(fā)BaseReactiveHandler
這里的get
攔截。
在get
攔截中會(huì)執(zhí)行track(target, "get", key)
函數(shù),執(zhí)行后會(huì)將當(dāng)前render函數(shù)作為依賴進(jìn)行收集。到這里依賴收集的部分講完啦,剩下的就是依賴觸發(fā)的部分。
我們接著來看MutableReactiveHandler
,他是繼承了BaseReactiveHandler
。在BaseReactiveHandler
中有個(gè)get
攔截,而在MutableReactiveHandler
中有個(gè)set
攔截。
當(dāng)我們點(diǎn)擊user.count++
按鈕時(shí),會(huì)對user.value.count
進(jìn)行寫操作。由于對count
屬性進(jìn)行了寫操作,所以就會(huì)走到set
攔截中,set
攔截代碼如下:
class MutableReactiveHandler extends BaseReactiveHandler { set(target, key, value, receiver) { let oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (target === toRaw(receiver)) { if (hasChanged(value, oldValue)) { trigger(target, "set", key, value, oldValue); } } return result; } }
我們先來看看set
攔截接收的4個(gè)參數(shù),第一個(gè)參數(shù)為target
,也就是我們proxy代理前的原始對象。第二個(gè)參數(shù)為key
,進(jìn)行寫操作的屬性,在我們這里key
的值就是字符串count
。第三個(gè)參數(shù)是新的屬性值。
第四個(gè)參數(shù)receiver
一般情況下是Proxy返回的代理響應(yīng)式對象。這里為什么會(huì)說是一般是呢?看一下MDN上面的解釋你應(yīng)該就能明白了:
假設(shè)有一段代碼執(zhí)行
obj.name = "jen"
,obj
不是一個(gè) proxy,且自身不含name
屬性,但是它的原型鏈上有一個(gè) proxy,那么,那個(gè) proxy 的set()
處理器會(huì)被調(diào)用,而此時(shí),obj
會(huì)作為 receiver 參數(shù)傳進(jìn)來。
接著來看set
攔截函數(shù)中的內(nèi)容,首先let oldValue = target[key]
拿到舊的屬性值,然后使用Reflect.set(target, key, value, receiver)
在Proxy
中一般都是搭配Reflect
進(jìn)行使用,在Proxy
的get
攔截中使用Reflect.get
,在Proxy
的set
攔截中使用Reflect.set
。
這樣做有幾個(gè)好處,在set攔截中我們要return一個(gè)布爾值表示屬性賦值是否成功。如果使用傳統(tǒng)的obj[key] = value
的形式我們是不知道賦值是否成功的,而使用Reflect.set
會(huì)返回一個(gè)結(jié)果表示給對象的屬性賦值是否成功。在set攔截中直接將Reflect.set
的結(jié)果進(jìn)行return即可。
還有一個(gè)好處是如果不搭配使用可能會(huì)出現(xiàn)this
指向不對的問題。
前面我們講過了receiver
可能不是Proxy返回的代理響應(yīng)式對象,所以這里需要使用if (target === toRaw(receiver))
進(jìn)行判斷。
接著就是使用if (hasChanged(value, oldValue))
進(jìn)行判斷新的值和舊的值是否相等,如果不相等就執(zhí)行trigger(target, "set", key, value, oldValue)
。
這個(gè)trigger
函數(shù)就是用于依賴觸發(fā),會(huì)將收集的依賴全部取出來執(zhí)行一遍,由于render函數(shù)也是被收集的依賴,所以render函數(shù)會(huì)重新執(zhí)行。重新執(zhí)行render函數(shù)時(shí)從user.value.count
屬性中取出的值就是新值1,接著就是生成虛擬DOM,然后將虛擬DOM掛載到真實(shí)DOM上,最終在頁面上user.value.count
屬性綁定的值已經(jīng)更新為1了。
這就是當(dāng)ref綁定的是一個(gè)對象時(shí),是如何使用Proxy去實(shí)現(xiàn)響應(yīng)式的過程。
看到這里有的小伙伴可能會(huì)有一個(gè)疑問,為什么ref使用RefImpl
類去實(shí)現(xiàn),而不是統(tǒng)一使用Proxy
去代理一個(gè)擁有value
屬性的普通對象呢?比如下面這種:
const proxy = new Proxy( { value: target, }, baseHandlers );
如果是上面這樣做那么就不需要使用RefImpl
類了,全部統(tǒng)一成Proxy去使用響應(yīng)式了。
但是上面的做法有個(gè)問題,就是使用者可以使用delete proxy.value
將proxy
對象的value
屬性給刪除了。而使用RefImpl
類的方式去實(shí)現(xiàn)就不能使用delete
的方法去將value
屬性給刪除了。
總結(jié)
這篇文章我們講了ref
是如何實(shí)現(xiàn)響應(yīng)式的,主要分為兩種情況:ref接收的是number這種原始類型、ref接收的是對象這種非原始類型。
當(dāng)ref接收的是number這種原始類型時(shí)是依靠
RefImpl
類的value
屬性的getter
和setter
方法中去實(shí)現(xiàn)的響應(yīng)式。當(dāng)我們對ref的value屬性進(jìn)行讀操作時(shí)會(huì)觸發(fā)value的
getter
方法進(jìn)行依賴收集。當(dāng)我們對ref的value屬性進(jìn)行寫操作時(shí)會(huì)進(jìn)行依賴觸發(fā),重新執(zhí)行render函數(shù),達(dá)到響應(yīng)式的目的。
當(dāng)ref接收的是對象這種非原始類型時(shí),會(huì)調(diào)用
reactive
方法將ref的value屬性轉(zhuǎn)換成一個(gè)由Proxy
實(shí)現(xiàn)的響應(yīng)式對象。當(dāng)我們對ref的value屬性對象的某個(gè)屬性進(jìn)行讀操作時(shí)會(huì)觸發(fā)
Proxy
的get攔截進(jìn)行依賴收集。當(dāng)我們對ref的value屬性對象的某個(gè)屬性進(jìn)行寫操作時(shí)會(huì)觸發(fā)
Proxy
的set攔截進(jìn)行依賴觸發(fā),然后重新執(zhí)行render函數(shù),達(dá)到響應(yīng)式的目的。
最后我們講了為什么ref不統(tǒng)一使用Proxy
去代理一個(gè)有value
屬性的普通對象去實(shí)現(xiàn)響應(yīng)式,而是要多搞個(gè)RefImpl
類。
因?yàn)槿绻褂?code>Proxy去代理的有value屬性的普通的對象,可以使用delete proxy.value
將proxy
對象的value
屬性給刪除了。而使用RefImpl
類的方式去實(shí)現(xiàn)就不能使用delete
的方法去將value
屬性給刪除了。
到此這篇關(guān)于vue3的ref是如何實(shí)現(xiàn)響應(yīng)式的?的文章就介紹到這了,更多相關(guān)vue3 ref響應(yīng)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue CLI4.0 webpack配置屬性之productionSourceMap用法
這篇文章主要介紹了Vue CLI4.0 webpack配置屬性之productionSourceMap用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06vue異步axios獲取的數(shù)據(jù)渲染到頁面的方法
今天小編就為大家分享一篇vue異步axios獲取的數(shù)據(jù)渲染到頁面的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08element-ui循環(huán)顯示radio控件信息的方法
今天小編就為大家分享一篇element-ui循環(huán)顯示radio控件信息的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue基礎(chǔ)之v-bind屬性、class和style用法分析
這篇文章主要介紹了vue基礎(chǔ)之v-bind屬性、class和style用法,結(jié)合實(shí)例形式分析了vue.js中v-bind綁定及class、style樣式控制相關(guān)操作技巧,需要的朋友可以參考下2019-03-03vue 設(shè)置 input 為不可以編輯的實(shí)現(xiàn)方法
今天小編就為大家分享一篇vue 設(shè)置 input 為不可以編輯的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09使用vue-virtual-scroller遇到的問題及解決
這篇文章主要介紹了使用vue-virtual-scroller遇到的問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03