vue3中虛擬dom的介紹與使用詳解
Vue 是如何將一份模板轉(zhuǎn)換為真實(shí)的 DOM 節(jié)點(diǎn)的,又是如何高效地更新這些節(jié)點(diǎn)的呢?這些都離不開(kāi)虛擬dom
這個(gè)概念,接下來(lái)我們就先了解下虛擬dom
這個(gè)概念以及它是什么。
什么是虛擬dom
在使用前端框架的時(shí)候,我們經(jīng)常會(huì)聽(tīng)到虛擬dom
這個(gè)概念。那么到底什么是虛擬dom
呢?
虛擬 DOM (Virtual DOM,簡(jiǎn)稱 VDOM) 是一種編程概念,意為將目標(biāo)所需的 UI 通過(guò)數(shù)據(jù)結(jié)構(gòu)“虛擬”地表示出來(lái),保存在內(nèi)存中,然后將真實(shí)的 DOM 與之保持同步。這個(gè)概念是由 React 率先開(kāi)拓,隨后被許多不同的框架采用,當(dāng)然也包括 Vue。簡(jiǎn)單來(lái)說(shuō),虛擬 DOM 就是一個(gè)用 JavaScript 對(duì)象表示真實(shí) DOM 的概念。
在vue3
中,虛擬dom
,也就是代碼里面的vnode
,他就是一個(gè)對(duì)象,定義位于 packages/runtime-core/src/vnode.ts
文件中。VNode
類的結(jié)構(gòu)如下,具體字段是怎么使用的我們后續(xù)章節(jié)一個(gè)個(gè)講解。
export interface VNode< HostNode = RendererNode, HostElement = RendererElement, ExtraProps = { [key: string]: any } > { __v_isVNode: true [ReactiveFlags.SKIP]: true type: VNodeTypes props: (VNodeProps & ExtraProps) | null key: string | number | symbol | null ref: VNodeNormalizedRef | null scopeId: string | null slotScopeIds: string[] | null children: VNodeNormalizedChildren component: ComponentInternalInstance | null dirs: DirectiveBinding[] | null transition: TransitionHooks<HostElement> | null // DOM el: HostNode | null anchor: HostNode | null // fragment anchor target: HostElement | null // teleport target targetAnchor: HostNode | null // teleport target anchor staticCount: number suspense: SuspenseBoundary | null ssContent: VNode | null ssFallback: VNode | null shapeFlag: number patchFlag: number dynamicProps: string[] | null dynamicChildren: VNode[] | null appContext: AppContext | null ctx: ComponentInternalInstance | null memo?: any[] isCompatRoot?: true ce?: (instance: ComponentInternalInstance) => void }
與其說(shuō)虛擬 DOM 是一種具體的技術(shù),不如說(shuō)是一種模式,所以并沒(méi)有一個(gè)標(biāo)準(zhǔn)的實(shí)現(xiàn)。我們可以用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明:
const vnode = { type: 'div', props: { id: 'hello' }, children: [ /* 更多 vnode */ ] }
這里所說(shuō)的 vnode
即一個(gè)純 JavaScript 的對(duì)象 (一個(gè)“虛擬節(jié)點(diǎn)”),它代表著一個(gè) <div>
元素。它包含我們創(chuàng)建實(shí)際元素所需的所有信息。它還包含更多的子節(jié)點(diǎn),這使它成為虛擬 DOM 樹(shù)的根節(jié)點(diǎn)。
一個(gè)運(yùn)行時(shí)渲染器將會(huì)遍歷整個(gè)虛擬 DOM 樹(shù),并據(jù)此構(gòu)建真實(shí)的 DOM 樹(shù)。這個(gè)過(guò)程被稱為掛載 (mount)。
如果我們有兩份虛擬 DOM 樹(shù),渲染器將會(huì)有比較地遍歷它們,找出它們之間的區(qū)別,并應(yīng)用這其中的變化到真實(shí)的 DOM 上。這個(gè)過(guò)程被稱為更新 (patch),又被稱為“比對(duì)”(diffing) 或“協(xié)調(diào)”(reconciliation)。
虛擬 DOM 帶來(lái)的主要收益是它讓開(kāi)發(fā)者能夠靈活、聲明式地創(chuàng)建、檢查和組合所需 UI 的結(jié)構(gòu),同時(shí)只需把具體的 DOM 操作留給渲染器去處理。
為什么使用虛擬dom
在前端開(kāi)發(fā)中,頻繁地操作 DOM 是非常耗時(shí)的,因?yàn)槊看尾僮鞫紩?huì)引發(fā)瀏覽器的重排和重繪。為了解決這個(gè)問(wèn)題,虛擬 DOM 應(yīng)運(yùn)而生。通過(guò)在內(nèi)存中模擬 DOM,我們可以批量地計(jì)算出 DOM 的變化,從而減少對(duì)真實(shí) DOM 的操作次數(shù)。這樣可以提高應(yīng)用的性能,特別是在大型應(yīng)用中。
例如當(dāng)一次操作中有100次更新DOM的動(dòng)作時(shí),虛擬DOM不會(huì)立即操作DOM,而是和原本的DOM進(jìn)行對(duì)比,將這100次更新的變化部分內(nèi)容保存到內(nèi)存中,最終一次性地應(yīng)用在DOM樹(shù)上,再進(jìn)行后續(xù)操作,避免大量無(wú)謂的計(jì)算量。
虛擬DOM實(shí)際上就是采用JavaScript對(duì)象來(lái)存儲(chǔ)DOM節(jié)點(diǎn)的信息,將DOM的更新變成對(duì)象的修改,并且這些修改計(jì)算在內(nèi)存中發(fā)生,當(dāng)修改完成后,再將JavaScript轉(zhuǎn)換成真實(shí)的DOM節(jié)點(diǎn),交給瀏覽器,從而達(dá)到性能的提升。
虛擬dom的好處
使用虛擬 DOM 的好處主要有以下幾點(diǎn):
- 性能優(yōu)化:通過(guò)減少直接操作真實(shí) DOM 的次數(shù),可以降低瀏覽器的重繪和回流成本,從而提高頁(yè)面性能。
- 跨平臺(tái):虛擬 DOM 不依賴于瀏覽器環(huán)境,可以方便地在不同平臺(tái)(如服務(wù)器端渲染、移動(dòng)端應(yīng)用等)使用。
- 易于測(cè)試:因?yàn)樘摂M DOM 是 JavaScript 對(duì)象,我們可以直接對(duì)其進(jìn)行操作和斷言,而不需要依賴瀏覽器環(huán)境。
同時(shí)vue3
的設(shè)計(jì)也是這樣的,我們可以看到源碼的組織結(jié)構(gòu)將各個(gè)目錄拆開(kāi)來(lái),runtime-core
的實(shí)現(xiàn)就是與平臺(tái)無(wú)關(guān)的。在瀏覽器環(huán)境下使用runtime-dom
的dom
操作,在其他平臺(tái)使用各個(gè)平臺(tái)的dom
操作,實(shí)現(xiàn)了跨平臺(tái)。
vue中vnode是如何運(yùn)行的
從高層面的視角看,Vue 組件掛載時(shí)會(huì)發(fā)生如下幾件事:
- 編譯:Vue 模板被編譯為渲染函數(shù):即用來(lái)返回虛擬 DOM 樹(shù)的函數(shù)。這一步驟可以通過(guò)構(gòu)建步驟提前完成,也可以通過(guò)使用運(yùn)行時(shí)編譯器即時(shí)完成。
- 掛載:運(yùn)行時(shí)渲染器調(diào)用渲染函數(shù),遍歷返回的虛擬 DOM 樹(shù),并基于它創(chuàng)建實(shí)際的 DOM 節(jié)點(diǎn)。這一步會(huì)作為響應(yīng)式副作用執(zhí)行,因此它會(huì)追蹤其中所用到的所有響應(yīng)式依賴。
- 更新:當(dāng)一個(gè)依賴發(fā)生變化后,副作用會(huì)重新運(yùn)行,這時(shí)候會(huì)創(chuàng)建一個(gè)更新后的虛擬 DOM 樹(shù)。運(yùn)行時(shí)渲染器遍歷這棵新樹(shù),將它與舊樹(shù)進(jìn)行比較,然后將必要的更新應(yīng)用到真實(shí) DOM 上去。
可以看到我們一切操作都是針對(duì)于虛擬DOM的,只有在最后提交的時(shí)候才會(huì)將虛擬DOM掛載到瀏覽器上面
怎么創(chuàng)建虛擬dom
在vue3
中,我們?cè)?code>template模版中編寫(xiě)的代碼最終都會(huì)通過(guò)compiler
模塊進(jìn)行編譯,編譯之后會(huì)返回一個(gè)render
字符串,后面在應(yīng)用運(yùn)行的時(shí)候會(huì)調(diào)用這個(gè)render
生成render
函數(shù)。在函數(shù)里面會(huì)調(diào)用不同的方法進(jìn)行虛擬dom
的創(chuàng)建。
例如下面的模版:
可以去template-explorer.vuejs.org/ 網(wǎng)站體驗(yàn)?zāi)0婢幾g結(jié)果
<template> <hello-world :msg="msg" :info="info"></hello-world> <div> <button @click="addAge">Add age</button> <button @click="toggleMsg">Toggle Msg</button> </div> </template>
經(jīng)過(guò)編譯之后會(huì)生成如下函數(shù):
import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" ? const _hoisted_1 = ["onClick"] const _hoisted_2 = ["onClick"] ? export function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_hello_world = _resolveComponent("hello-world") ? return (_openBlock(), _createElementBlock(_Fragment, null, [ _createVNode(_component_hello_world, { msg: _ctx.msg, info: _ctx.info }, null, 8 /* PROPS */, ["msg", "info"]), _createElementVNode("div", null, [ _createElementVNode("button", { onClick: _ctx.addAge }, "Add age", 8 /* PROPS */, _hoisted_1), _createElementVNode("button", { onClick: _ctx.toggleMsg }, "Toggle Msg", 8 /* PROPS */, _hoisted_2) ]) ], 64 /* STABLE_FRAGMENT */)) }
現(xiàn)在我們不用理解這個(gè)代碼的所有內(nèi)容,我們只用關(guān)注里面創(chuàng)建節(jié)點(diǎn)的代碼,例如createVNode
,createElementVNode
這些就是用來(lái)創(chuàng)建虛擬dom
的。調(diào)用完之后會(huì)生成虛擬dom
樹(shù)。由于樹(shù)結(jié)構(gòu)過(guò)大, 我們這里不做展示,整個(gè)結(jié)構(gòu)和我們上面定義的虛擬dom
結(jié)構(gòu)一致。
接下來(lái)我們可以看下創(chuàng)建虛擬dom
的具體實(shí)現(xiàn),我們只挑常用的創(chuàng)建虛擬dom
的方法。所有和虛擬dom
有關(guān)的內(nèi)容都位于源碼packages/runtime-core/src/vnode.ts
中
createVNode
function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // 如果是組件,type就是組件內(nèi)容。如果是普通類型就是一個(gè)字符串,比方說(shuō)div, HelloWorld之類的 props: (Data & VNodeProps) | null = null, children: unknown = null, //子組件 patchFlag: number = 0, dynamicProps: string[] | null = null, isBlockNode = false ): VNode { if (!type || type === NULL_DYNAMIC_COMPONENT) { type = Comment } ? if (isVNode(type)) { // 是Vnode的處理,例如用戶通過(guò)h函數(shù)編寫(xiě)的代碼 const cloned = cloneVNode(type, props, true /* mergeRef: true */) if (children) { normalizeChildren(cloned, children) // 標(biāo)準(zhǔn)化孩子 } if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) { if (cloned.shapeFlag & ShapeFlags.COMPONENT) { currentBlock[currentBlock.indexOf(type)] = cloned } else { currentBlock.push(cloned) } } cloned.patchFlag |= PatchFlags.BAIL return cloned } ? // 標(biāo)準(zhǔn)化用class寫(xiě)的組件 if (isClassComponent(type)) { type = type.__vccOpts } ? // 標(biāo)準(zhǔn)化異步組件和函數(shù)式組件 if (__COMPAT__) { type = convertLegacyComponent(type, currentRenderingInstance) } ? // 標(biāo)準(zhǔn)化class props style //對(duì)于代理過(guò)的對(duì)象,我們需要克隆來(lái)使用他們 //因?yàn)橹苯有薷臅?huì)導(dǎo)致觸發(fā)響應(yīng)式 if (props) { props = guardReactiveProps(props)! // 防止props是響應(yīng)式的 let { class: klass, style } = props if (klass && !isString(klass)) { props.class = normalizeClass(klass) // 標(biāo)準(zhǔn)話class } if (isObject(style)) { // 標(biāo)準(zhǔn)化style if (isProxy(style) && !isArray(style)) { style = extend({}, style) } props.style = normalizeStyle(style) } } ? // encode the vnode type information into a bitmap const shapeFlag = isString(type) // 根據(jù)type不同生成不同的shapeFlag,方便后面對(duì)不同的類型做處理 ? ShapeFlags.ELEMENT : __FEATURE_SUSPENSE__ && isSuspense(type) ? ShapeFlags.SUSPENSE : isTeleport(type) ? ShapeFlags.TELEPORT : isObject(type) ? ShapeFlags.STATEFUL_COMPONENT // 有狀態(tài)組件 : isFunction(type) ? ShapeFlags.FUNCTIONAL_COMPONENT // 函數(shù)組件 : 0 ? return createBaseVNode( type, props, children, patchFlag, // 更新類型 dynamicProps, shapeFlag, isBlockNode, true ) }
createVNode
主要是對(duì)傳遞的type
做出判斷,通過(guò)賦值shapeFlag
來(lái)標(biāo)明當(dāng)前的虛擬節(jié)點(diǎn)的類型。
如果props
含有style
或者class
要進(jìn)行標(biāo)準(zhǔn)化。
例如<div :style="['background:red',{color:'red'}]"></div>
其中第一個(gè)是cssText
形式、第二個(gè)是對(duì)象形式,他們應(yīng)該被轉(zhuǎn)化為對(duì)象類型所以轉(zhuǎn)化后應(yīng)該為<div style={color:'red',background:'red'}></div>
。當(dāng)然對(duì)于class
也需要標(biāo)準(zhǔn)化:class={hello:true,world:false} => :class="hello"
。但是這里處理的其實(shí)是用戶自己寫(xiě)了render
函數(shù),而對(duì)于使用了Vue
自帶的編譯系統(tǒng)之后,是不需要做這一層處理的。
最后會(huì)調(diào)用createBaseVNode
進(jìn)行虛擬dom
的創(chuàng)建
function createBaseVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag = 0, dynamicProps: string[] | null = null, // 動(dòng)態(tài)屬性 shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT, isBlockNode = false, needFullChildrenNormalization = false ) { const vnode = { // 創(chuàng)建一個(gè)vnode __v_isVNode: true, // 是不是vnode __v_skip: true, type, // type對(duì)于組件來(lái)說(shuō)就是組件內(nèi)容 props, key: props && normalizeKey(props), // 標(biāo)準(zhǔn)化key ref: props && normalizeRef(props), // 標(biāo)準(zhǔn)化 props scopeId: currentScopeId, slotScopeIds: null, children, // 孩子的vnode component: null, // 如果是一個(gè)組件的話就會(huì)有組件的實(shí)例,組件實(shí)例的subtree上面掛載了這個(gè)組件下面所有的vnode suspense: null, ssContent: null, ssFallback: null, dirs: null, // 指令相關(guān) transition: null, // transition相關(guān) el: null, // 真實(shí)dom anchor: null, // 插入的位置 target: null, targetAnchor: null, staticCount: 0, shapeFlag, // 組件類型 patchFlag, // 靶向更新標(biāo)記 dynamicProps, // 動(dòng)態(tài)props dynamicChildren: null, // 動(dòng)態(tài)孩子 appContext: null, // 應(yīng)用根context ctx: currentRenderingInstance } as VNode ? if (needFullChildrenNormalization) { normalizeChildren(vnode, children) // 標(biāo)準(zhǔn)化子組件。例如插槽啥的。添加children并且更改vnode的shapFlag if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).normalize(vnode) // 標(biāo)準(zhǔn)化異步組件 } } else if (children) { vnode.shapeFlag |= isString(children) // 判斷ShapeFlags,可以是多個(gè)類型的組合 ? ShapeFlags.TEXT_CHILDREN : ShapeFlags.ARRAY_CHILDREN } ? // 編譯優(yōu)化相關(guān)?,F(xiàn)在不必了解 if ( isBlockTreeEnabled > 0 && // avoid a block node from tracking itself !isBlockNode && // has current parent block currentBlock && // presence of a patch flag indicates this node needs patching on updates. // 補(bǔ)丁標(biāo)志的存在表明此節(jié)點(diǎn)需要在更新時(shí)進(jìn)行補(bǔ)丁。 // component nodes also should always be patched, because even if the // 組件節(jié)點(diǎn)應(yīng)該是中有patchFlag // component doesn't need to update, it needs to persist the instance on to // 組件不需要更新,它需要將實(shí)例持久化到 // the next vnode so that it can be properly unmounted later. 下一個(gè)vnode,以便以后可以正確地卸載它。 (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) && // 有patchFlag說(shuō)明是動(dòng)態(tài)節(jié)點(diǎn) vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS ) { currentBlock.push(vnode) // 放入到父親的區(qū)塊中去,說(shuō)明現(xiàn)在的是一個(gè)動(dòng)態(tài)節(jié)點(diǎn) } ? if (__COMPAT__) { convertLegacyVModelProps(vnode) defineLegacyVNodeProperties(vnode) } ? return vnode }
可以看到我們創(chuàng)建了一個(gè)VNode
類型的對(duì)象,也就是一個(gè)虛擬dom
。同時(shí)對(duì)key、ref、chidren(needFullChildrenNormalization為true)
進(jìn)行標(biāo)準(zhǔn)化
。
至此我們就知道vue3
中虛擬dom
究竟是怎么產(chǎn)生的啦。
createElementVNode
用于創(chuàng)建普通tag
的虛擬節(jié)點(diǎn)如<div></div>
export { createBaseVNode as createElementVNode }
可以看到createElementVNode
其實(shí)就是createBaseVNode
,用來(lái)創(chuàng)建虛擬dom
。
項(xiàng)目中使用
上面我們提到,Vue 模板會(huì)被預(yù)編譯成虛擬 DOM 渲染函數(shù)。Vue 也提供了 API(h函數(shù))
使我們可以不使用模板編譯,直接手寫(xiě)渲染函數(shù)。在處理高度動(dòng)態(tài)的邏輯時(shí),渲染函數(shù)相比于模板更加靈活,因?yàn)槟憧梢酝耆厥褂?JavaScript 來(lái)構(gòu)造你想要的 vnode。
但是官方推薦的是使用模版而不是渲染函數(shù)。那么為什么 Vue 默認(rèn)推薦使用模板呢?有以下幾點(diǎn)原因:
- 模板更貼近實(shí)際的 HTML。這使得我們能夠更方便地重用一些已有的 HTML 代碼片段,能夠帶來(lái)更好的可訪問(wèn)性體驗(yàn)、能更方便地使用 CSS 應(yīng)用樣式,并且更容易使設(shè)計(jì)師理解和修改。
- 由于其確定的語(yǔ)法,更容易對(duì)模板做靜態(tài)分析。這使得 Vue 的模板編譯器能夠應(yīng)用許多編譯時(shí)優(yōu)化來(lái)提升虛擬 DOM 的性能表現(xiàn)(例如靜態(tài)提升,靶向更新等等)
在絕大多數(shù)情況下,Vue 推薦使用模板語(yǔ)法來(lái)創(chuàng)建應(yīng)用。然而在某些使用場(chǎng)景下,我們真的需要用到 JavaScript 完全的編程能力。這時(shí)渲染函數(shù)就派上用場(chǎng)了。Vue 提供了一個(gè) h() 函數(shù)用于創(chuàng)建 vnodes。下面我們來(lái)看看h
函數(shù)的用法:
import { h } from 'vue' ? const vnode = h( 'div', // type { id: 'foo', class: 'bar' }, // props [ /* children */ ] )
h()
是 hyperscript 的簡(jiǎn)稱——意思是“能生成 HTML (超文本標(biāo)記語(yǔ)言) 的 JavaScript”。這個(gè)名字來(lái)源于許多虛擬 DOM 實(shí)現(xiàn)默認(rèn)形成的約定。一個(gè)更準(zhǔn)確的名稱應(yīng)該是 createVnode()
,但當(dāng)你需要多次使用渲染函數(shù)時(shí),一個(gè)簡(jiǎn)短的名字會(huì)更省力。
我們可以在組件中使用h
函數(shù)來(lái)執(zhí)行渲染而使用模版
import { h } from 'vue' export default { setup() { // 需要確保返回的是一個(gè)函數(shù)而不是一個(gè)值,這個(gè)函數(shù)最后會(huì)被用作渲染函數(shù)render,這個(gè)render在后面會(huì)被重復(fù)調(diào)用的 // 使用數(shù)組返回多個(gè)根節(jié)點(diǎn) return () => [ h('div'), h('div'), h('div') ] } }
以上代碼等價(jià)于
<template> <div></div> <div></div> <div></div> </template> export default { setup() {} }
然后我們可以看下h
函數(shù)的源碼:
export function h(type: any, propsOrChildren?: any, children?: any): VNode { const l = arguments.length if (l === 2) { // 只有兩個(gè)參數(shù),要么是只有孩子,要么是只有props if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { if (isVNode(propsOrChildren)) { // 如果第二個(gè)參數(shù)是一個(gè)vnode說(shuō)明是子節(jié)點(diǎn) return createVNode(type, null, [propsOrChildren]) } return createVNode(type, propsOrChildren) // 沒(méi)有孩子節(jié)點(diǎn)的情況 } else { // 如果propsOrChildren是數(shù)組那么一定是孩子節(jié)點(diǎn) return createVNode(type, null, propsOrChildren) } } else { // prop和孩子節(jié)點(diǎn)都有的情況 if (l > 3) { // 第三個(gè)參數(shù)之后都是孩子 children = Array.prototype.slice.call(arguments, 2) } else if (l === 3 && isVNode(children)) { children = [children] } return createVNode(type, propsOrChildren, children) } }
可以看到h
函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)是類型,第二個(gè)是props
參數(shù)或者孩子節(jié)點(diǎn)(如果有第三個(gè)參數(shù)就是props
,沒(méi)有第三個(gè)參數(shù)就要判斷下),三個(gè)參數(shù)是孩子節(jié)點(diǎn)。除了類型必填以外,其他的參數(shù)都是可選的。h
函數(shù)只是對(duì)參數(shù)做了一下判斷,然后底層還是調(diào)用的createVNode
進(jìn)行虛擬DOM的創(chuàng)建。
具體判斷邏輯代碼中都已經(jīng)注釋,大家可以結(jié)合著看。
總結(jié)
現(xiàn)在相信大家已經(jīng)了解了虛擬dom
這個(gè)東東,后續(xù)我們所有的操作都是通過(guò)操作虛擬dom
來(lái)實(shí)現(xiàn)的,最后掛載的時(shí)候通過(guò)原生的dom
操作來(lái)將虛擬dom
掛載到頁(yè)面上。
以上就是vue3中虛擬dom的介紹與使用詳解的詳細(xì)內(nèi)容,更多關(guān)于vue3虛擬dom的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue數(shù)據(jù)雙向綁定底層實(shí)現(xiàn)原理
這篇文章主要為大家詳細(xì)介紹了Vue數(shù)據(jù)雙向綁定底層實(shí)現(xiàn)原理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11vue項(xiàng)目是如何運(yùn)行起來(lái)的
這篇文章主要介紹了vue項(xiàng)目是如何運(yùn)行起來(lái)的,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue里如何主動(dòng)銷毀keep-alive緩存的組件
這篇文章主要介紹了vue里如何主動(dòng)銷毀keep-alive緩存的組件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03electron實(shí)現(xiàn)打印功能支持靜默打印、無(wú)感打印
使用electron開(kāi)發(fā)應(yīng)用遇到了打印小票的功能,實(shí)現(xiàn)途中還是幾經(jīng)波折,下面這篇文章主要給大家介紹了關(guān)于electron實(shí)現(xiàn)打印功能支持靜默打印、無(wú)感打印的相關(guān)資料,需要的朋友可以參考下2023-12-12vue-cli腳手架的.babelrc文件用法說(shuō)明
這篇文章主要介紹了vue-cli腳手架的.babelrc文件用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09vue中導(dǎo)出Excel表格的實(shí)現(xiàn)代碼
項(xiàng)目中我們可能會(huì)碰到導(dǎo)出Excel文件的需求,這篇文章主要介紹了vue中導(dǎo)出Excel表格的實(shí)現(xiàn)代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-10-10關(guān)于element?ui中的el-scrollbar橫向滾動(dòng)問(wèn)題
這篇文章主要介紹了關(guān)于element?ui中的el-scrollbar橫向滾動(dòng)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08