What does key do
Key is the unique ID given to each Vnode. You can rely on the key to get the corresponding VNode in oldVnode more accurately and faster.
A more accurate
In vue/patch.js, the sameVnode function needs to make a judgment: A. key === B. key. In the case that there is no key, both A.key and B.key are undefined. For list rendering, it can already be judged as the same node, and then call patchVnode. In the case of key, the situation of local reuse can be avoided in the comparison of A. key === B. Key, so it is more accurate.
faster
Both VUE and React use the diff algorithm to compare old and new virtual nodes to update nodes.
The uniqueness of key is used to generate map objects to obtain corresponding nodes, which is faster than traversal. In the updateChild function of vue/patch.js, the new node and the old node are cross-compared. If the new node and the old node are not cross-compared, the key in the old node array will be compared according to the key of the new node. To find the corresponding old node (in this case, a map map of key => index). If it is not found, it is considered a new node. If there is no key, a traversal lookup is used to find the corresponding old node. One is a map, and the other is a traversal search. By comparison. Map enables faster mapping.
// Vue project SRC /core/vdom/ patch.js-488 line
// Here is the code formatted for readability
// oldCh is an old virtual node array
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
// Map
idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
// Get by traversal
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}
Copy the code
Creating a map function
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
Copy the code
Traversal search
// sameVnode is a function that compares whether old and new nodes are the same
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
Copy the code
Update 2021/7/21
In ve3. X, branch keys for V-if/V-ELSE/V-else -if are no longer necessary, because Vue now automatically generates unique keys.
Supplement knowledge
The diff algorithm
Diff flow chart: When data changes, the set method will call dep. notify to notify all subscribers Watcher, and the subscribers will call Patch to patch the real DOM and update the corresponding view.
patch
Core code of Patch:
function patch (oldVnode, vnode) {
// some code
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
const oEl = oldVnode.el // The real element node corresponding to the current oldVnode
let parentEle = api.parentNode(oEl) / / the parent element
createEle(vnode) // Generate new elements based on Vnode
if(parentEle ! = =null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // Add the new element to the parent element
api.removeChild(parentEle, oldVnode.el) // Remove the old element node
oldVnode = null}}// some code
return vnode
}
Copy the code
The patch function receives two arguments oldVnode and Vnode representing the new node and the previous old node respectively.
Determine whether the two nodes are worthy of comparison, and then execute patchVnode.
function sameVnode (a, b) {
return (
a.key === b.key && / / key values
a.tag === b.tag && / / tag name
a.isComment === b.isComment && // Whether it is a comment node
// Whether data is defined, data contains some specific information, such as onclick, style
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b) // When the tag is , the type must be the same)}Copy the code
If not, replace oldVnode with Vnode
If both nodes are the same, drill down on their children. If the two nodes are different then Vnode has been completely changed and oldVnode can be replaced directly.
What if these two nodes are different but their children are the same? Remember, diff is a layer by layer comparison, if the first layer is not the same then you don’t go into the second layer comparison.
patchVnode
When we determine that the two nodes are worth comparing, we will specify the patchVnode method for both nodes. So what does this method do?
patchVnode (oldVnode, vnode) {
const el = vnode.el = oldVnode.el
let i, oldCh = oldVnode.children, ch = vnode.children
if (oldVnode === vnode) return
if(oldVnode.text ! = =null&& vnode.text ! = =null&& oldVnode.text ! == vnode.text) { api.setTextContent(el, vnode.text) }else {
updateEle(el, vnode, oldVnode)
if(oldCh && ch && oldCh ! == ch) { updateChildren(el, oldCh, ch) }else if (ch){
createEle(vnode) //create el's children dom
}else if (oldCh){
api.removeChildren(el)
}
}
}
Copy the code
This function does the following:
- Find the corresponding real DOM, called EL
- Check whether Vnode and oldVnode refer to the same object. If so, return
- If they both have text nodes and are not equal, set the text node for EL to the text node for Vnode.
- If oldVnode has children and Vnode does not, the children of EL are removed
- If oldVnode does not have children and Vnode does, add Vnode’s children to el after they are materialized
- If both have child nodes, it is important to perform the updateChildren function to compare the children
The other points are easy to understand, so let’s talk about updateChild in detail
updateChildren
updateChildren (parentElm, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) { // For vnode.key comparisons, oldVnode = null
oldStartVnode = oldCh[++oldStartIdx]
}else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
}else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
}else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode)
api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}else {
// The comparison with key
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // Create index table with key
}
idxInOld = oldKeyToIdx[newStartVnode.key]
if(! idxInOld) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) newStartVnode = newCh[++newStartIdx] }else {
elmToMove = oldCh[idxInOld]
if(elmToMove.sel ! == newStartVnode.sel) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) }else {
patchVnode(elmToMove, newStartVnode)
oldCh[idxInOld] = null
api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
}
newStartVnode = newCh[++newStartIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] = =null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
So what does this function do
- will
Vnode
The child nodes of theVch
andoldVnode
The child nodes of theoldCh
extracted oldCh
andvCh
Each variable has two heads and two tailsStartIdx
andEndIdx
, their two variables are compared with each other, and there are altogether four ways of comparison. If none of the four comparisons match, the comparison will be performed by key with key set. During the comparison, the variable will move towards the middle, and the comparison will end as soon as StartIdx>EndIdx indicates that at least one of the oldCh and vCh has been traversed.
There are four ways to compare samevNodes in oldS, oldE, S, and E. When two of them match, the corresponding node in the real DOM will be moved to the corresponding position in Vnode. This sentence is a bit twisted, for example
- If it is
oldS
andE
Once matched, the first node in the real DOM moves to the end - If it is
oldE
andS
Once matched, the last node in the real DOM moves to the front, and the two matched Pointers move to the center - If none of the four matches are successful, there are two cases
- If both old and new child nodesThere is the key, then will be based on
oldChild
Generates a key ofThe hash tablewithS
Is matched with the hash table, and determines whether S and the matching node aresameNode
, if so, move the successful node to the front in the real DOM; otherwise, insert the corresponding node generated by S into the DOMoldS
The S pointer moves to the center and is set to null by the node in the matching old. - If there is no key, then S generates a new node and inserts it into the real DOM.
- If both old and new child nodesThere is the key, then will be based on
In situ reuse
Official explanation:
If the order of data items is changed, Vue will not move DOM elements to match the order of data items, but simply reuse each element there
Explain this in code:
<div v-for='item in list'>Text {{}}<input/><button @click="Move the item down the list.">
</div>
Copy the code
Demo address: JsBin
In In-place reuse, click the button and the input box does not move down with the text. This is because the input box is not data-bound, so vue defaults to using the already rendered DOM. However, the text is bound to the data, so the text is re-rendered. This is the default list rendering strategy in Vue or Angular because it is efficient.
There is no problem with this “in-place reuse” generic list presentation, so why recommend a key? Because this mode is only good for rendering simple stateless components. For most scenarios, the list component has its own state.
Official explanation:
The “reuse in place” mode is efficient, but only for list rendering output that does not depend on child component state or temporary DOM state (for example, form input values).
For example: a list of news items, you can click on the list item to mark it as visited, and you can TAB to “entertainment News” or “Social News.”
Without the key attribute, select the second item under entertainment News and switch to Social News. The second item in Social News will also be selected because it has reused the component and retains the previous state. To solve this problem, you can attach a news ID to the list item as a unique key, so that every time the list is rendered, all components are completely replaced with the correct state.
This is a simple example, but the practical application is more complicated. Wearing a unique key adds overhead, but the difference is almost invisible to the user and ensures that the component state is correct, which is why it is recommended to use a unique ID as the key. As to specific how to use, be about to choose according to actual situation.
Therefore, the suggestion is that if the list element has a user interaction scenario (such as form or reorder, etc.), you need to set the key parameter for the V-for directive, which only wants the unique value of each element in the list.
Refer to the article
According to Yang – Daily – Interview – Question
Windlany — Elaborate on vue’s diff algorithm
What is the “local reuse” strategy in v-for in Vue2.0?