Vue3源碼分析偵聽器watch的實現(xiàn)原理
watch 的本質(zhì)
所謂的watch,其本質(zhì)就是觀測一個響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時通知并執(zhí)行相應(yīng)的回調(diào)函數(shù)。實際上,watch 的實現(xiàn)本質(zhì)就是利用了 effect 和 options.scheduler 選項。如下例子所示:
// watch 函數(shù)接收兩個參數(shù),source 是響應(yīng)式數(shù)據(jù),cb 是回調(diào)函數(shù) function watch(source, cb){ effect( // 觸發(fā)讀取操作,從而建立聯(lián)系 () => source.foo, { scheduler(){ // 當(dāng)數(shù)據(jù)變化時,調(diào)用回調(diào)函數(shù) cb cb() } } ) }
如上面的代碼所示嗎,source 是響應(yīng)式數(shù)據(jù),cb 是回調(diào)函數(shù)。如果副作用函數(shù)中存在 scheduler 選項,當(dāng)響應(yīng)式數(shù)據(jù)發(fā)生變化時,會觸發(fā) scheduler 函數(shù)執(zhí)行,而不是直接觸發(fā)副作用函數(shù)執(zhí)行。從這個角度來看, scheduler 調(diào)度函數(shù)就相當(dāng)于是一個回調(diào)函數(shù),而 watch 的實現(xiàn)就是利用了這點。
watch 的函數(shù)簽名
偵聽多個源
偵聽的數(shù)據(jù)源可以 是一個數(shù)組,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個數(shù)組 // overload: array of multiple sources + cb export function watch< T extends MultiWatchSources, Immediate extends Readonly<boolean> = false >( sources: [...T], cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate> ): WatchStopHandle
也可以使用數(shù)組同時偵聽多個源,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 使用數(shù)組同時偵聽多個源 // overload: multiple sources w/ `as const` // watch([foo, bar] as const, () => {}) // somehow [...T] breaks when the type is readonly export function watch< T extends Readonly<MultiWatchSources>, Immediate extends Readonly<boolean> = false >( source: T, cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate> ): WatchStopHandle
偵聽單一源
偵聽的數(shù)據(jù)源是一個 ref 類型的數(shù)據(jù) 或者是一個具有返回值的 getter 函數(shù),如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個 ref 類型的數(shù)據(jù) 或者是一個具有返回值的 getter 函數(shù) // overload: single source + cb export function watch<T, Immediate extends Readonly<boolean> = false>( source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchOptions<Immediate> ): WatchStopHandle export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
偵聽的數(shù)據(jù)源是一個響應(yīng)式的 obj 對象,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個響應(yīng)式的 obj 對象 // overload: watching reactive object w/ cb export function watch< T extends object, Immediate extends Readonly<boolean> = false >( source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchOptions<Immediate> ): WatchStopHandle
watch 的實現(xiàn)
watch 函數(shù)
// packages/runtime-core/src/apiWatch.ts // implementation export function watch<T = any, Immediate extends Readonly<boolean> = false>( source: T | WatchSource<T>, cb: any, options?: WatchOptions<Immediate> ): WatchStopHandle { if (__DEV__ && !isFunction(cb)) { warn( `\`watch(fn, options?)\` signature has been moved to a separate API. ` + `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` + `supports \`watch(source, cb, options?) signature.` ) } return doWatch(source as any, cb, options) }
可以看到,watch 函數(shù)接收3個參數(shù),分別是:source 偵聽的數(shù)據(jù)源,cb 回調(diào)函數(shù),options 偵聽選項。
source 參數(shù)
從watch的函數(shù)重載中可以知道,當(dāng)偵聽的是單一源時,source 可以是一個 ref 類型的數(shù)據(jù) 或者是一個具有返回值的 getter 函數(shù),也可以是一個響應(yīng)式的 obj 對象。當(dāng)偵聽的是多個源時,source 可以是一個數(shù)組。
cb 參數(shù)
在 cb 回調(diào)函數(shù)中,給開發(fā)者提供了最新的value,舊的value以及onCleanup函數(shù)用與清除副作用。如下面的類型定義所示:
export type WatchCallback<V = any, OV = any> = ( value: V, oldValue: OV, onCleanup: OnCleanup ) => any
options 參數(shù)
options 選項可以控制 watch 的行為,例如通過options的選項參數(shù)immediate來控制watch的回調(diào)是否立即執(zhí)行,通過options的選項參數(shù)來控制watch的回調(diào)函數(shù)是同步執(zhí)行還是異步執(zhí)行。options 參數(shù)的類型定義如下:
export interface WatchOptionsBase extends DebuggerOptions { flush?: 'pre' | 'post' | 'sync' } export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase { immediate?: Immediate deep?: boolean }
可以看到 options 的類型定義 WatchOptions 繼承了 WatchOptionsBase。也就是說,watch 的 options 中除了 immediate 和 deep 這兩個特有的參數(shù)外,還可以傳遞 WatchOptionsBase 中的所有參數(shù)以控制副作用執(zhí)行的行為。
在 watch 的函數(shù)體中調(diào)用了 doWatch 函數(shù),我們來看看它的實現(xiàn)。
doWatch 函數(shù)
實際上,無論是watch函數(shù),還是 watchEffect 函數(shù),在執(zhí)行時最終調(diào)用的都是 doWatch 函數(shù)。
doWatch 函數(shù)簽名
function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle
doWatch 的函數(shù)簽名與 watch 的函數(shù)簽名基本一致,也是接收三個參數(shù)。在 doWatch 函數(shù)中,為了便于options 選項的使用,對 options 進行了解構(gòu)。
初始化變量
首先從 component 中獲取當(dāng)前的組件實例,然后分別定義三個變量。其中 getter 是一個函數(shù),她或作為副作用的函數(shù)參數(shù)傳入到副作用函數(shù)中。forceTrigger 變量是一個布爾值,用來標(biāo)識是否需要強制觸發(fā)副作用函數(shù)執(zhí)行。isMultiSource 變量同樣也是一個布爾值,用來標(biāo)記偵聽的數(shù)據(jù)源是單一源還是以數(shù)組形式傳入的多個源,初始值為 false,表示偵聽的是單一源。如下面的代碼所示:
const instance = currentInstance let getter: () => any // 是否需要強制觸發(fā)副作用函數(shù)執(zhí)行 let forceTrigger = false // 偵聽的是否是多個源 let isMultiSource = false
接下來根據(jù)偵聽的數(shù)據(jù)源來初始化這三個變量。
偵聽的數(shù)據(jù)源是一個 ref 類型的數(shù)據(jù)
當(dāng)偵聽的數(shù)據(jù)源是一個 ref 類型的數(shù)據(jù)時,通過返回 source.value 來初始化 getter,也就是說,當(dāng) getter 函數(shù)被觸發(fā)時,會通過source.value 獲取到實際偵聽的數(shù)據(jù)。然后通過 isShallow 函數(shù)來判斷偵聽的數(shù)據(jù)源是否是淺響應(yīng),并將其結(jié)果賦值給 forceTrigger,完成 forceTrigger 變量的初始化。如下面的代碼所示:
if (isRef(source)) { // 偵聽的數(shù)據(jù)源是 ref getter = () => source.value // 判斷數(shù)據(jù)源是否是淺響應(yīng) forceTrigger = isShallow(source) }
偵聽的數(shù)據(jù)源是一個響應(yīng)式數(shù)據(jù)
當(dāng)偵聽的數(shù)據(jù)源是一個響應(yīng)式數(shù)據(jù)時,直接返回 source 來初始化 getter ,即 getter 函數(shù)被觸發(fā)時直接返回 偵聽的數(shù)據(jù)源。由于響應(yīng)式數(shù)據(jù)中可能會是一個object 對象,因此將 deep 設(shè)置為 true,在觸發(fā) getter 函數(shù)時可以遞歸地讀取對象的屬性值。如下面的代碼所示:
else if (isReactive(source)) { // 偵聽的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù) getter = () => source deep = true }
偵聽的數(shù)據(jù)源是一個數(shù)組
當(dāng)偵聽的數(shù)據(jù)源是一個數(shù)組,即同時偵聽多個源。此時直接將 isMultiSource 變量設(shè)置為 true,表示偵聽的是多個源。接著通過數(shù)組的 some 方法來檢測偵聽的多個源中是否存在響應(yīng)式對象,將其結(jié)果賦值給 forceTrigger 。然后遍歷數(shù)組,判斷每個源的類型,從而完成 getter 函數(shù)的初始化。如下面的代碼所示:
else if (isArray(source)) { // 偵聽的數(shù)據(jù)源是一個數(shù)組,即同時偵聽多個源 isMultiSource = true forceTrigger = source.some(isReactive) getter = () => // 遍歷數(shù)組,判斷每個源的類型 source.map(s => { if (isRef(s)) { // 偵聽的數(shù)據(jù)源是 ref return s.value } else if (isReactive(s)) { // 偵聽的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù) return traverse(s) } else if (isFunction(s)) { // 偵聽的數(shù)據(jù)源是一個具有返回值的 getter 函數(shù) return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER) } else { __DEV__ && warnInvalidSource(s) } }) }
偵聽的數(shù)據(jù)源是一個函數(shù)
當(dāng)偵聽的數(shù)據(jù)源是一個具有返回值的 getter 函數(shù)時,判斷 doWatch 函數(shù)的第二個參數(shù) cb 是否有傳入。如果有傳入,則處理的是 watch 函數(shù)的場景,此時執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter 。如果沒有傳入,則處理的是 watchEffect 函數(shù)的場景。在該場景下,如果組件實例已經(jīng)卸載,則直接返回,不執(zhí)行 source 函數(shù)。否則就執(zhí)行 cleanup 清除依賴,然后執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter 。如下面的代碼所示:
else if (isFunction(source)) { // 處理 watch 和 watchEffect 的場景 // watch 的第二個參數(shù)可以是一個具有返回值的 getter 參數(shù),第二個參數(shù)是一個回調(diào)函數(shù) // watchEffect 的參數(shù)是一個 函數(shù) // 偵聽的數(shù)據(jù)源是一個具有返回值的 getter 函數(shù) if (cb) { // getter with cb // 處理的是 watch 的場景 // 執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) } else { // no cb -> simple effect // 沒有回調(diào),即為 watchEffect 的場景 getter = () => { // 件實例已經(jīng)卸載,則不執(zhí)行,直接返回 if (instance && instance.isUnmounted) { return } // 清除依賴 if (cleanup) { cleanup() } // 執(zhí)行 source 函數(shù) return callWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onCleanup] ) } } }
遞歸讀取響應(yīng)式數(shù)據(jù)
如果偵聽的數(shù)據(jù)源是一個響應(yīng)式數(shù)據(jù),需要遞歸讀取響應(yīng)式數(shù)據(jù)中的屬性值。如下面的代碼所示:
// 處理的是 watch 的場景 // 遞歸讀取對象的屬性值 if (cb && deep) { const baseGetter = getter getter = () => traverse(baseGetter()) }
在上面的代碼中,doWatch 函數(shù)的第二個參數(shù) cb 有傳入,說明處理的是 watch 中的場景。deep 變量為 true ,說明此時偵聽的數(shù)據(jù)源是一個響應(yīng)式數(shù)據(jù),因此需要調(diào)用 traverse 函數(shù)來遞歸讀取數(shù)據(jù)源中的每個屬性,對其進行監(jiān)聽,從而當(dāng)任意屬性發(fā)生變化時都能夠觸發(fā)回調(diào)函數(shù)執(zhí)行。
定義清除副作用函數(shù)
聲明 cleanup 和 onCleanup 函數(shù),并在 onCleanup 函數(shù)的執(zhí)行過程中給 cleanup 函數(shù)賦值,當(dāng)副作用函數(shù)執(zhí)行一些異步的副作用時,這些響應(yīng)需要在其失效是清除。如下面的代碼所示:
// 清除副作用函數(shù) let cleanup: () => void let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } }
封裝 scheduler 調(diào)度函數(shù)
為了便于控制 watch 的回調(diào)函數(shù) cb 的執(zhí)行時機,需要將 scheduler 調(diào)度函數(shù)封裝為一個獨立的 job 函數(shù),如下面的代碼所示:
// 將 scheduler 調(diào)度函數(shù)封裝為一個獨立的 job 函數(shù),便于在初始化和變更時執(zhí)行它 const job: SchedulerJob = () => { if (!effect.active) { return } if (cb) { // 處理 watch 的場景 // watch(source, cb) // 執(zhí)行副作用函數(shù)獲取新值 const newValue = effect.run() // 如果數(shù)據(jù)源是響應(yīng)式數(shù)據(jù)或者需要強制觸發(fā)副作用函數(shù)執(zhí)行或者新舊值發(fā)生了變化 // 則執(zhí)行回調(diào)函數(shù),并更新舊值 if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // 當(dāng)回調(diào)再次執(zhí)行前先清除副作用 // cleanup before running cb again if (cleanup) { cleanup() } // 執(zhí)行watch 函數(shù)的回調(diào)函數(shù) cb,將舊值和新值作為回調(diào)函數(shù)的參數(shù) callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // 首次調(diào)用時,將 oldValue 的值設(shè)置為 undefined // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onCleanup ]) // 更新舊值,不然下一次會得到錯誤的舊值 oldValue = newValue } } else { // watchEffect // 處理 watchEffect 的場景 effect.run() } }
在 job 函數(shù)中,判斷回調(diào)函數(shù) cb 是否傳入,如果有傳入,那么是 watch 函數(shù)被調(diào)用的場景,否則就是 watchEffect 函數(shù)被調(diào)用的場景。
如果是 watch 函數(shù)被調(diào)用的場景,首先執(zhí)行副作用函數(shù),將執(zhí)行結(jié)果賦值給 newValue 變量,作為最新的值。然后判斷需要執(zhí)行回調(diào)函數(shù) cb 的情況:
- 如果偵聽的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù),需要深度偵聽,即 deep 為 true
- 如果需要強制觸發(fā)副作用函數(shù)執(zhí)行,即 forceTrigger 為 true
- 如果新舊值發(fā)生了變化
只要滿足上面三種情況中的其中一種,就需要執(zhí)行 watch 函數(shù)的回調(diào)函數(shù) cb。如果回調(diào)函數(shù) cb 是再次執(zhí)行,在執(zhí)行之前需要先清除副作用。然后調(diào)用 callWithAsyncErrorHandling 函數(shù)執(zhí)行回調(diào)函數(shù)cb,并將新值newValue 和舊值 oldValue 傳入回調(diào)函數(shù)cb中。在回調(diào)函數(shù)cb執(zhí)行后,更新舊值oldValue,避免在下一次執(zhí)行回調(diào)函數(shù)cb時獲取到錯誤的舊值。
如果是 watchEffect 函數(shù)被調(diào)用的場景,則直接執(zhí)行副作用函數(shù)即可。
設(shè)置 job 的 allowRecurse 屬性
根據(jù)是否傳入回調(diào)函數(shù)cb,設(shè)置 job 函數(shù)的 allowRecurse 屬性。這個設(shè)置十分重要,它能夠讓 job 作為偵聽器的回調(diào),這樣調(diào)度器就能知道它允許調(diào)用自身。
// important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) // 重要:讓調(diào)度器任務(wù)作為偵聽器的回調(diào)以至于調(diào)度器能知道它可以被允許自己派發(fā)更新 job.allowRecurse = !!cb
flush 選項指定回調(diào)函數(shù)的執(zhí)行時機
在調(diào)用 watch 函數(shù)時,可以通過 options 的 flush 選項來指定回調(diào)函數(shù)的執(zhí)行時機:
當(dāng) flush 的值為 sync 時,代表調(diào)度器函數(shù)是同步執(zhí)行,此時直接將 job 賦值給 scheduler,這樣調(diào)度器函數(shù)就會直接執(zhí)行。
當(dāng) flush 的值為 post 時,代表調(diào)度函數(shù)需要將副作用函數(shù)放到一個微任務(wù)隊列中,并等待 DOM 更新結(jié)束后再執(zhí)行。
當(dāng) flush 的值為 pre 時,即調(diào)度器函數(shù)默認(rèn)的執(zhí)行方式,這時調(diào)度器會區(qū)分組件是否已經(jīng)掛載。如果組件未掛載,則先執(zhí)行一次調(diào)度函數(shù),即執(zhí)行回調(diào)函數(shù)cb。在組件掛載之后,將調(diào)度函數(shù)推入一個優(yōu)先執(zhí)行時機的隊列中。
// 這里處理的是回調(diào)函數(shù)的執(zhí)行時機
let scheduler: EffectScheduler if (flush === 'sync') { // 同步執(zhí)行,將 job 直接賦值給調(diào)度器 scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { // 將調(diào)度函數(shù) job 添加到微任務(wù)隊列中執(zhí)行 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' // 調(diào)度器函數(shù)默認(rèn)的執(zhí)行模式 scheduler = () => { if (!instance || instance.isMounted) { // 組件掛載后將 job 推入一個優(yōu)先執(zhí)行時機的隊列中 queuePreFlushCb(job) } else { // with 'pre' option, the first call must happen before // the component is mounted so it is called synchronously. // 在 pre 選型中,第一次調(diào)用必須發(fā)生在組件掛載之前 // 所以這次調(diào)用是同步的 job() } } }
創(chuàng)建副作用函數(shù)
初始化完 getter 函數(shù)和調(diào)度器函數(shù) scheduler 后,調(diào)用 ReactiveEffect 類來創(chuàng)建一個副作用函數(shù)
// 創(chuàng)建一個副作用函數(shù) const effect = new ReactiveEffect(getter, scheduler)
執(zhí)行副作用函數(shù)
在執(zhí)行副作用函數(shù)之前,首先判斷是否傳入了回調(diào)函數(shù)cb,如果有傳入,則根據(jù) options 的 immediate 選項來判斷是否需要立即執(zhí)行回調(diào)函數(shù)cb,如果指定了immediate 選項,則立即執(zhí)行 job 函數(shù),即 watch 的回調(diào)函數(shù)會在 watch 創(chuàng)建時立即執(zhí)行一次。否則就手動調(diào)用副作用函數(shù),并將返回值作為舊值,賦值給 oldValue。如下面的代碼所示:
if (cb) { // 選項參數(shù) immediate 來指定回調(diào)是否需要立即執(zhí)行 if (immediate) { // 回調(diào)函數(shù)會在 watch 創(chuàng)建時立即執(zhí)行一次 job() } else { // 手動調(diào)用副作用函數(shù),拿到的就是舊值 oldValue = effect.run() } }
如果 options 的 flush 選項的值為 post ,需要將副作用函數(shù)放入到微任務(wù)隊列中,等待組件掛載完成后再執(zhí)行副作用函數(shù)。如下面的代碼所示:
else if (flush === 'post') { // 在調(diào)度器函數(shù)中判斷 flush 是否為 'post',如果是,將其放到微任務(wù)隊列中執(zhí)行 queuePostRenderEffect( effect.run.bind(effect), instance && instance.suspense ) }
其余情況都是立即執(zhí)行副作用函數(shù)。如下面的代碼所示:
else { // 其余情況立即首次執(zhí)行副作用 effect.run() }
返回匿名函數(shù),停止偵聽
doWatch 函數(shù)最后返回了一個匿名函數(shù),該函數(shù)用以結(jié)束數(shù)據(jù)源的偵聽。因此在調(diào)用 watch 或者 watchEffect 時,可以調(diào)用其返回值類結(jié)束偵聽。
return () => { effect.stop() if (instance && instance.scope) { // 返回一個函數(shù),用以顯式的結(jié)束偵聽 remove(instance.scope.effects!, effect) } }
總結(jié)
watch 的本質(zhì)就是觀測一個響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時通知并執(zhí)行相應(yīng)的回調(diào)函數(shù)。watch的實現(xiàn)利用了effect 和 options.scheduler 選項。
watch 可以偵聽單一源,也可以偵聽多個源。偵聽單一源時數(shù)據(jù)源可以是一個具有返回值的getter 函數(shù),或者是一個 ref 對象,也可以是一個響應(yīng)式的 object 對象。偵聽多個源時,其數(shù)據(jù)源是一個數(shù)組。
在watch的實現(xiàn)中,根據(jù)偵聽的數(shù)據(jù)源的類型來初始化getter 函數(shù)和 scheduler 調(diào)度函數(shù),根據(jù)這兩個函數(shù)創(chuàng)建一個副作用函數(shù),并根據(jù) options 的 immediate 選項以及 flush 選項來指定回調(diào)函數(shù)和副作用函數(shù)的執(zhí)行時機。當(dāng) immediate 為 true 時,在watch 創(chuàng)建時會立即執(zhí)行一次回調(diào)函數(shù)。當(dāng) flush 的值為 post 時,scheduler 調(diào)度函數(shù)和副作用函數(shù)都會被添加到微任務(wù)隊列中,會等待 DOM 更新結(jié)束后再執(zhí)行。
到此這篇關(guān)于Vue3源碼分析偵聽器watch的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Vue3偵聽器watch內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Vue新搭檔TypeScript快速入門實踐
這篇文章主要介紹了關(guān)于Vue新搭檔TypeScript快速入門實踐,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決
這篇文章主要介紹了Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01Vue?element-ui中表格過長內(nèi)容隱藏顯示的實現(xiàn)方式
在Vue項目中,使用ElementUI渲染表格數(shù)據(jù)時,如果某一個列數(shù)值長度超過列寬,會默認(rèn)換行,造成顯示不友好,下面這篇文章主要給大家介紹了關(guān)于Vue?element-ui中表格過長內(nèi)容隱藏顯示的實現(xiàn)方式,需要的朋友可以參考下2022-09-09vue router動態(tài)路由設(shè)置參數(shù)可選問題
這篇文章主要介紹了vue-router動態(tài)路由設(shè)置參數(shù)可選,文中給大家提到了vue-router 動態(tài)添加 路由的方法,需要的朋友可以參考下2019-08-08VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀
這篇文章主要介紹了VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08