一文詳解Vue3中簡單diff算法的實現(xiàn)
簡單Diff算法
核心Diff只關(guān)心新舊虛擬節(jié)點都存在一組子節(jié)點的情況
減少DOM操作
例子
// 舊節(jié)點 const oldVNode = { type: 'div', children: [ { type: 'p', children: '1' }, { type: 'p', children: '2' }, { type: 'p', children: '3' } ] } // 新節(jié)點 const newVNode = { type: 'div', children: [ { type: 'p', children: '4' }, { type: 'p', children: '5' }, { type: 'p', children: '6' } ] }
如果直接去操作DOM,那么上面的更新需要6次DOM操作,卸載所有舊子節(jié)點,掛載所有新子節(jié)點。
但是觀察上面新舊vNode的子節(jié)點可以發(fā)現(xiàn):
- 更新前后所有子節(jié)點都是 p 標簽,即標簽元素步變
- 只有p標簽的子節(jié)點發(fā)生變化了
所以最理想的更新方式是直接更新這個p標簽的文本節(jié)點的內(nèi)容,這樣只需要一次DOM操作,即可完成一個p標簽的更新。更新完所有節(jié)點只需要3次DOM操作就可以完成全部節(jié)點的更新。
上面的做法可以減少DOM操作次數(shù),但問題也很明顯,只有節(jié)點數(shù)量相同這個做法才能正常工作。但新舊兩組子節(jié)點數(shù)量未必相同。
新的一組子節(jié)點數(shù)量少于舊的一組子節(jié)點的數(shù)量時,意味著有節(jié)點在更新后應該被卸載。(圖二)
新的一組子節(jié)點數(shù)量多余舊的一組子節(jié)點的數(shù)量時,意味著有節(jié)點在更新后應該被新增并掛載。(圖三)
結(jié)論
通過上面分析得出,進行新舊兩組子節(jié)點的更新時,不應該總是遍歷舊的一組子節(jié)點或新的一組子節(jié)點,而是應該遍歷其中較短的一組。這樣才能盡可能多的調(diào)用patch進行更新。接著對比新舊兩組子節(jié)點的長度,如果新的一組子節(jié)點更長,說明有新節(jié)點需要掛載,否則說明有舊的子節(jié)點需要卸載。
實現(xiàn)
function easyDiff (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 獲取新舊子節(jié)點列表的長度 const oldLen = oldChildren.length const newLen = newChildren.length // 取得較小的一個(可以理解為兩組子節(jié)點的公共長度) const commonLength = Math.min(oldLen, newLen) // 遍歷 commonLength 次 for (let i = 0; i < commonLength; i++) { patch(oldChildren[i], newChildren[i], container) } // 如果 newLen > oldLen,說明有新子節(jié)點需要掛載 if (newLen > oldLen) { for (let i = commonLength; i < newLen; i++) { patch(null, newChildren[i], container) } } // 如果 oldLen > newLen,說明有舊節(jié)點需要卸載 if (oldLen > newLen) { for (let i = commonLength; i < oldLen; i++) { unmount(oldChildren[i]) } } }
DOM復用與key的作用
例子
上面通過減少DOM操作次數(shù)提升了更新性能,但還存在可優(yōu)化空間
const KEY = { oldVNode: [ { type: 'p' }, { type: 'div' }, { type: 'span' } ], newVNode: [ { type: 'span' }, { type: 'p' }, { type: 'div' } ] }
針對這個例子,如果還使用上面的算法,則需要6次DOM操作。
調(diào)用 patch 在 p標簽和span標簽之間打補丁,由于不是相同標簽,所以p標簽被卸載,然后掛載span標簽,需要兩步操作,div - p,span - div同理。
很容易發(fā)現(xiàn)新舊兩組子節(jié)點只是順序不同。所以最優(yōu)的處理方式是,通過DOM的移動來完成子節(jié)點的更新,這比不斷執(zhí)行卸載和掛載性能好得多。但是要通過移動DOM來完成更新,必須要保證新舊兩組子節(jié)點的確存在可復用的節(jié)點。(如果新的子節(jié)點沒有在舊的子節(jié)點中出現(xiàn),則無法通過移動節(jié)點的方式完成更新操作。)
用上面的例子來說,怎么確定新的一組節(jié)點中的第三個節(jié)點 { type: 'div' } 與舊的一組子節(jié)點中的第二個節(jié)點相同呢?可以通過vNode.type
判斷,但這種方式并不可靠。
oldChildren: [ { type: 'p', children: '1' }, { type: 'p', children: '2' }, { type: 'p', children: '3' } ], newChildren: [ { type: 'p', children: '3' }, { type: 'p', children: '1' }, { type: 'p', children: '2' } ]
觀察上面節(jié)點,可以發(fā)現(xiàn),這個案例可以通過移動DOM的方式來完成更新,但是vNode.type的值都相同,導致無法確定新舊節(jié)點中的對應關(guān)系,就不能確定怎么移動DOM完成更新。
虛擬節(jié)點的key
因此,需要引入額外的 key
作為vNode的標識。
const KEY = { oldChildren: [ { type: 'p', children: '1', key: '1' }, { type: 'p', children: '2', key: '2' }, { type: 'p', children: '3', key: '3' } ], newChildren: [ { type: 'p', children: '3', key: '3' }, { type: 'p', children: '1', key: '1' }, { type: 'p', children: '2', key: '2' } ] }
key 屬性就像虛擬DOM的 身份證號,只要兩個虛擬節(jié)點的type和key屬性都相同,那么就可以認為它們是相同的;即可以進行DOM的復用。
但是DOM可復用并不意味著不需要更新
oldVNode: { type: 'p', children: 'text - 1', key: '1' } newVNode: { type: 'p', children: 'text - 2', key: '1' }
兩個節(jié)點有相同的key可type,但它們的文本內(nèi)容不同,還是需要通過patch進行打補丁操作。
實現(xiàn)
function easyDiffV2 (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 遍歷新的children for (let i = 0; i < newChildren.length; i++) { const newVNode = newChildren[i] for (let j = 0; j < oldChildren.length; j++) { const oldVNode = oldChildren[i] // 如果找到可復用的兩個節(jié)點 if (newVNode.key === oldVNode.key) { // 對可復用的兩個節(jié)點打補丁 patch(oldVNode, newVNode, container) // 一個新節(jié)點處理完后開始下一個新節(jié)點 break } } } }
外層循環(huán)遍歷新的一組子節(jié)點,內(nèi)層循環(huán)遍歷舊的一組子節(jié)點。內(nèi)層循環(huán)中對比新舊子節(jié)點的key值,在舊的子節(jié)點中找到可以復用的節(jié)點;一旦找到則調(diào)用 patch 打補丁。
找到需要移動的元素
現(xiàn)在已經(jīng)可以通過key找到可復用的節(jié)點了,接下來要做的是判斷一個節(jié)點是否需要移動
探索節(jié)點順序關(guān)系
節(jié)點順序不變 - 查找過程:
第一步:取新的一組子節(jié)點中的第一個節(jié)點 p - 1,它的key為1,在舊的一組子節(jié)點中找到具有相同key值的可復用節(jié)點,能夠找到,并且該節(jié)點在舊的一組子節(jié)點中索引為0;p - 2、p = 3同理。
- key 為 1 的節(jié)點在 舊節(jié)點列表中的索引為0
- key 為 2 的節(jié)點在 舊節(jié)點列表中的索引為1
- key 為 3 的節(jié)點在 舊節(jié)點列表中的索引為2
每一次查找可復用節(jié)點都會記錄該可復用節(jié)點在舊的一組子節(jié)點中的位置索引,如果按照先后順序排列,則可以得到一個序列:0、1、2,是一個遞增序列。
節(jié)點順序變化 - 查找過程
第一步:取新的一組子節(jié)點中的第一個節(jié)點 p - 3,它的key為3,在舊的一組子節(jié)點中找到具有相同key值的可復用節(jié)點,能夠找到,并且該節(jié)點在舊的一組子節(jié)點中索引為2;
第二步:取新的一組子節(jié)點中的第一個節(jié)點 p - 1,它的key為1,在舊的一組子節(jié)點中找到具有相同key值的可復用節(jié)點,能夠找到,并且該節(jié)點在舊的一組子節(jié)點中索引為0;
到了這一步發(fā)現(xiàn)遞增的順序被打破了。節(jié)點 p - 1 在舊的一組children 的索引為0,它小于 p - 3 在舊children中的索引2.這說明節(jié)點 p - 1 在舊children中排在 p - 3前面,但在新的children中,它排在節(jié)點 p - 3后面。因此得出:節(jié)點p - 1對應的真實DOM需要移動
第三步:取新的一組子節(jié)點中的第一個節(jié)點 p - 2,它的key為2,在舊的一組子節(jié)點中找到具有相同key值的可復用節(jié)點,能夠找到,并且該節(jié)點在舊的一組子節(jié)點中索引為1;
節(jié)點 p - 2 在舊的一組children 的索引為0,它小于 p - 3 在舊children中的索引2.這說明節(jié)點 p - 2 在舊children中排在 p - 3前面,但在新的children中,它排在節(jié)點 p - 3后面。因此得出:**節(jié)點p - 2對應的真實DOM需要移動
可以將節(jié)點 p - 3 在舊children中的索引定義為:在舊children中尋找具有相同key值節(jié)點的過程中,遇到的最大索引值
如果后續(xù)尋找過程中,存在比當前遇到的最大索引值還要小的節(jié)點,則意味著該節(jié)點需要移動。
實現(xiàn)
function easyBigIndex (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 用來存儲尋找過程中遇到的最大索引值 let lastIndex = 0 for (let i = 0; i < newChildren.length; i++) { const newVNode = newChildren[i] for (let j = 0; j < oldChildren; j++) { const oldVNode = oldChildren[j] if (newVNode.key === oldVNode.key) { patch(oldVNode, newVNode, container) if (j < lastIndex) { // 需要移動 } else { // 更新lastIndex的值(lastIndex要保持當前已查找的索引中的最大值) lastIndex = j } break } } } }
如何移動元素
移動節(jié)點指的是,移動一個虛擬節(jié)點所對應的真實DOM節(jié)點,并不是移動虛擬節(jié)點本身。既然移動的是真實DOM節(jié)點,就需要取得它的引用,其對應的真實DOM節(jié)點會存儲到它的vNode.el
屬性中
例子
引用上面的案例:
取新的一組子節(jié)點中的第一個節(jié)點 p - 3,它的key 為3,在舊的虛擬節(jié)點列表中找到具有相同 key 值的可復用節(jié)點。發(fā)現(xiàn)能夠找到,并且該節(jié)點在舊的一組子節(jié)點中的素引為2。此時變量 lastIndex 的值為 0,索引2 不小于0,所以節(jié)點 p - 3對應的真實DOM 不需要移動,但需要更新變量 lastIndex 的值為 2。
第二步:取新的一組子節(jié)點中第二個節(jié)點 p - 1,它的key 為1,在舊的一組子節(jié)點中找到具有相同 key 值的可復用節(jié)點。發(fā)現(xiàn)能夠找到,并且該節(jié)點在日的一組子節(jié)點中的索引為0。此時變量 lastIndex 的值為 2,索引0小于 2,所以節(jié)點p-1對應的真實 DOM需要移動
到了這一步,我們發(fā)現(xiàn),節(jié)點p - 1對應的真實 DOM 需要移動,但應該移動到哪里呢?新children 的順序其實就是更新后真實 DOM 節(jié)點應有的順序。所以節(jié)點 p-1在新 children 中的位置就代表了真實 DOM 更新后的位置。由于節(jié)點 p - 1在新 children 中排在節(jié)點p - 3后面,所以我們應該把節(jié)點p - 1所對應的真實 DOM移動到節(jié)點p - 3所對應的真實 DOM 后面。這樣操作之后,此時真實 DOM 的順序為 p-2、p-3、p-1。
第三步:取新的一組子節(jié)點中第三個節(jié)點 p-2,它的key 為2。嘗試在舊的一組子節(jié)點中找到具有相同 key 值的可復用節(jié)點。發(fā)現(xiàn)能夠找到,并且該節(jié)點在舊的一組子節(jié)點中的素引為1。此時變量 lastIndex 的值為 2,索引1小于2,所以節(jié)點p-2對應的真實 DOM需要移動
第二步操作完成后 新 / 舊 / 虛擬 節(jié)點之間的對應關(guān)系
實現(xiàn)
function easyMove (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 用來存儲尋找過程中遇到的最大索引值 let lastIndex = 0 for (let i = 0; i < newChildren.length; i++) { const newVNode = newChildren[i] for (let j = 0; j < oldChildren; j++) { const oldVNode = oldChildren[j] if (newVNode.key === oldVNode.key) { patch(oldVNode, newVNode, container) if (j < lastIndex) { // 需要移動 // 獲取當前vNode的前一個vNode const prevVNode = newChildren[i - 1] // 如果 prevVNode 不存在,說明當前vNode是第一個節(jié)點,它不需要移動 if (prevVNode) { // 由于要將newVNode對用的真實DOM移動到prevVNode對應的真實DOM后面, // 所以需要獲取prevVNode對應的真實節(jié)點的下一個兄弟節(jié)點,并將其作為錨點 const anchor = prevVNode.el.nextSibling // 調(diào)用insert將newVNode對應真實DOM插入到錨點元素前面 // insert 是通過 el.insertBefore 插入元素的 insert(newVNode.el, container, anchor) } } else { // 更新lastIndex的值(lastIndex要保持當前已查找的索引中的最大值) lastIndex = j } break } } } }
添加新元素
例子
在新的一組子節(jié)點中,多出來一個 p - 4,它的key值為4,該節(jié)點在舊的一組字節(jié)點中不存在,因此應該將其視為新增節(jié)點。對于新增節(jié)點,更新時應該正確地將其掛載:
- 找到新增節(jié)點
- 將新增節(jié)點掛載到正確位置
第一步:取新的一組子節(jié)點中第一個節(jié)點p - 3,它的key值為3,在舊的一組子節(jié)及中找到可復用的節(jié)點。發(fā)現(xiàn)能找到,并且該節(jié)點在舊的一組子節(jié)點中的索引值為2。此時,變量lastIndex的值為0,所以節(jié)點 p - 3 對應的真實 DOM 不需要移動,但是需要將變量 lastIndex 的值更新為 2。
第二步:取新的一組子節(jié)點中第一個節(jié)點p - 1,它的key值為1,在舊的一組子節(jié)及中找到可復用的節(jié)點。發(fā)現(xiàn)能找到,并且該節(jié)點在舊的一組子節(jié)點中的索引值為1。此時變量lastIndex的值為2,所以節(jié)點 p - 1對應的真實DOM需要移動,并且應該移動到節(jié)點 p - 3對應的真實DOM后面。
第三步:取新的一組子節(jié)點中第一個節(jié)點p - 4,它的key值為4,在舊的一組子節(jié)及中找到可復用的節(jié)點。沒有key值為4的節(jié)點,因此渲染器會把節(jié)點 p - 4 看作新增節(jié)點并掛載它。應該掛載到什么地方呢?觀察p - 4在新的一組子節(jié)點中的位置。由于 p - 4出現(xiàn)在節(jié)點 p - 1后面,所以應該把 p - 4 掛載到節(jié)點 p - 1 對應的真實DOM后面。
第四步:取新的一組子節(jié)點中第一個節(jié)點p - 2,它的key值為2,在舊的一組子節(jié)及中找到可復用的節(jié)點。發(fā)現(xiàn)能找到,并且該節(jié)點在舊的一組子節(jié)點中的索引值為1。此時,變量lastIndex的值為2,索引值1小于lastIndex的值2,所以節(jié)點 p - 2對應的真實DOM需要移動,并且應該移動到節(jié)點 p - 4對應的真實DOM后面。
第二步操作完成后的節(jié)點對應關(guān)系
第三步操作完成后的節(jié)點對應關(guān)系
實現(xiàn)
function easyMount (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 用來存儲尋找過程中遇到的最大索引值 let lastIndex = 0 for (let i = 0; i < newChildren.length; i++) { const newVNode = newChildren[i] // 定義變量 find,代表是否在舊的一組子節(jié)點中找到可復用的節(jié)點,初始值為false - 沒找到 let find = false for (let j = 0; j < oldChildren; j++) { const oldVNode = oldChildren[j] if (newVNode.key === oldVNode.key) { // 一旦找到可復用的節(jié)點,將變量find設(shè)置為true find = true patch(oldVNode, newVNode, container) if (j < lastIndex) { // 需要移動 // 獲取當前vNode的前一個vNode const prevVNode = newChildren[i - 1] // 如果 prevVNode 不存在,說明當前vNode是第一個節(jié)點,它不需要移動 if (prevVNode) { // 由于要將newVNode對用的真實DOM移動到prevVNode對應的真實DOM后面, // 所以需要獲取prevVNode對應的真實節(jié)點的下一個兄弟節(jié)點,并將其作為錨點 const anchor = prevVNode.el.nextSibling // 調(diào)用insert將newVNode對應真實DOM插入到錨點元素前面 // insert 是通過 el.insertBefore 插入元素的 insert(newVNode.el, container, anchor) } } else { // 更新lastIndex的值(lastIndex要保持當前已查找的索引中的最大值) lastIndex = j } break } } // 這里find如果還是false,說明當前newVNode沒有在舊的一組子節(jié)點中找到可復用的節(jié)點 // 也就是說當前 newVNode 是新增節(jié)點,需要掛載 if (!find) { // 為了將節(jié)點掛載到正確位置,需要先獲取錨點元素 // 首先獲取當前newVNode的前一個vNode節(jié)點 const prevVNode = newChildren[i - 1] let anchor = null if (prevVNode) { // 如果有前一個vNode節(jié)點,則使用它的下一個兄弟節(jié)點作為錨點元素 anchor = prevVNode.el.nextSibling } else { // 如果沒有前一個vNode節(jié)點,說明即將掛載的新節(jié)點是第一個子節(jié)點 // 這是使用容器元素的firstChild作為錨點 anchor = container.firstChild } // 掛載 newVNode patch(null, newVNode, container, anchor) } } }
移除不存在的元素
例子
在新的一組節(jié)點中,節(jié)點 p - 2 不存在了,說明該節(jié)點被刪除,渲染器應該能找到那些需要刪除的節(jié)點并正確地將其刪除。
找到需要刪除的節(jié)點 - 步驟:
- 第一步:取新的一組子節(jié)點中的第一個節(jié)點p - 3,它的key 值為3,在舊的一組子節(jié)點中尋找可復用的節(jié)點。發(fā)現(xiàn)能夠找到,并且該節(jié)點在舊的一組子節(jié)點中的索引值為2。此時變量 lastIndex 的值為0,索引2不小于lastIndex 的值0,所以節(jié)點p - 3對應的真實 DOM 不需要移動,但需要更新變量 lastIndex 的值為 2。
- 第二步:取新的一組子節(jié)點中的第二個節(jié)點 p - 1,它的key 值為1。嘗試在舊的一組子節(jié)點中尋找可復用的節(jié)點。發(fā)現(xiàn)能夠找到,并且該節(jié)點在舊的一組子節(jié)點中的索引值為0。此時變量 lastIndex 的值為2,索引0小于 lastIndex 的值 2, 所以節(jié)點p-1對應的真實 DOM需要移動,并且應該移動到節(jié)點p-3對應的真實 DOM 后面經(jīng)過這一步的移動操作后,發(fā)現(xiàn) p - 3和p - 1都有了對應的真實DOM節(jié)點。
- 至此更新結(jié)束,但 p - 2 對應的真實DOM仍然存在,所以需要增加額外的邏輯來刪除遺留節(jié)點。當基本的更新結(jié)束時,需要遍歷舊的一組子節(jié)點,然后去新的一組子節(jié)點中尋找具有相同key值的節(jié)點。如果找不到,說明應該刪除該節(jié)點。
p - 2與任何newVNode沒有對應關(guān)系
實現(xiàn)
function easyUnmount (n1, n2, container) { // 取出新舊子節(jié)點列表 const oldChildren = n1.children const newChildren = n2.children // 用來存儲尋找過程中遇到的最大索引值 let lastIndex = 0 for (let i = 0; i < newChildren.length; i++) { const newVNode = newChildren[i] // 定義變量 find,代表是否在舊的一組子節(jié)點中找到可復用的節(jié)點,初始值為false - 沒找到 let find = false for (let j = 0; j < oldChildren; j++) { const oldVNode = oldChildren[j] if (newVNode.key === oldVNode.key) { // 一旦找到可復用的節(jié)點,將變量find設(shè)置為true find = true patch(oldVNode, newVNode, container) if (j < lastIndex) { // 需要移動 // 獲取當前vNode的前一個vNode const prevVNode = newChildren[i - 1] // 如果 prevVNode 不存在,說明當前vNode是第一個節(jié)點,它不需要移動 if (prevVNode) { // 由于要將newVNode對用的真實DOM移動到prevVNode對應的真實DOM后面, // 所以需要獲取prevVNode對應的真實節(jié)點的下一個兄弟節(jié)點,并將其作為錨點 const anchor = prevVNode.el.nextSibling // 調(diào)用insert將newVNode對應真實DOM插入到錨點元素前面 // insert 是通過 el.insertBefore 插入元素的 insert(newVNode.el, container, anchor) } } else { // 更新lastIndex的值(lastIndex要保持當前已查找的索引中的最大值) lastIndex = j } break } } // 這里find如果還是false,說明當前newVNode沒有在舊的一組子節(jié)點中找到可復用的節(jié)點 // 也就是說當前 newVNode 是新增節(jié)點,需要掛載 if (!find) { // 為了將節(jié)點掛載到正確位置,需要先獲取錨點元素 // 首先獲取當前newVNode的前一個vNode節(jié)點 const prevVNode = newChildren[i - 1] let anchor = null if (prevVNode) { // 如果有前一個vNode節(jié)點,則使用它的下一個兄弟節(jié)點作為錨點元素 anchor = prevVNode.el.nextSibling } else { // 如果沒有前一個vNode節(jié)點,說明即將掛載的新節(jié)點是第一個子節(jié)點 // 這是使用容器元素的firstChild作為錨點 anchor = container.firstChild } // 掛載 newVNode patch(null, newVNode, anchor) } } // 更新操作完成后,遍歷舊的一組子節(jié)點 for (let i = 0; i < oldChildren.length; i++) { const oldVNode = oldChildren[i] // 拿舊子節(jié)點oldVNode去新的一組子節(jié)點中尋找具有相同key值的節(jié)點 const has = newChildren.find( vNode => vNode.key === oldVNode.key ) if (has) { // 如果沒找到具有相同key值的節(jié)點,則說明需要刪除該節(jié)點,調(diào)用unmount函數(shù)將其卸載 unmount(oldVNode) } } }
總結(jié)
遍歷新舊子結(jié)點中較少的一組,逐個調(diào)用patch進行打補丁,然后比較新舊兩組子節(jié)點的數(shù)量,如果新的一組子節(jié)點數(shù)量更多,說明有新節(jié)點需要掛載;否則說明在舊的一組子節(jié)點中,有節(jié)點需要卸載。
引入key屬性,就像虛擬節(jié)點的身份證號,通過key找到可復用的節(jié)點,然后盡可能通過DOM移動操作來完成更新,避免過多地對DOM元素進行銷毀和重建。
簡單Diff算法地核心邏輯是,拿新的一組子節(jié)點中地節(jié)點去舊的一組子節(jié)點中尋找可復用地節(jié)點,如果找到了,則記錄該節(jié)點地位置索引。在整個更新過程中,如果一個節(jié)點地索引值小于最大索引,則說明該節(jié)點對應地真實DOM元素需要移動。
以上就是一文詳解Vue3中簡單diff算法的實現(xiàn)的詳細內(nèi)容,更多關(guān)于Vue簡單diff算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
elementplus?中?DatePicker?日期選擇器樣式修改無效的問題及解決方案
這篇文章主要介紹了elementplus中DatePicker日期選擇器樣式修改無效的問題,DatePicker日期選擇器彈出面板默認掛載在body上,所以在組件中添加了?scoped?屬性的?style?標簽下是修改不到其樣式的,講解了datepicker的使用方法,及常見的配置項和對應的值,需要的朋友可以參考下2024-01-01Vuex子模塊調(diào)用子模塊的actions或mutations實現(xiàn)方式
這篇文章主要介紹了Vuex子模塊調(diào)用子模塊的actions或mutations實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10在Vue中實現(xiàn)網(wǎng)頁截圖與截屏功能詳解
在Web開發(fā)中,有時候需要對網(wǎng)頁進行截圖或截屏,Vue作為一個流行的JavaScript框架,提供了一些工具和庫,可以方便地實現(xiàn)網(wǎng)頁截圖和截屏功能,本文將介紹如何在Vue中進行網(wǎng)頁截圖和截屏,需要的朋友可以參考下2023-06-06vue2.0 與 bootstrap datetimepicker的結(jié)合使用實例
本篇文章主要介紹了vue2.0 與 bootstrap datetimepicker的結(jié)合使用實例,非常具有實用價值,需要的朋友可以參考下2017-05-05Vue金融數(shù)字格式化(并保留小數(shù))數(shù)字滾動效果實現(xiàn)
這篇文章主要介紹了Vue金融數(shù)字格式化(并保留小數(shù)) 數(shù)字滾動效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04