vue2中规定模板中必须放一个标签
vue3中可以放文本,因为在最外层常见了一个根节点
1、如果标签名称不一样,直接删掉老的换成新的即可
// 如果标签名称不一样 直接删掉老的换成新的即可
if (oldVnode.tag !== vnode.tag) {
// 可以通过vnode.el属性。获取现在真实的dom元素
return oldVnode.el.parentNode.replaceChild(createElm(vnode), oldVnode.el);
}
2、 如果两个虚拟节点是文本节点,比较文本内容
if (vnode.tag == undefined) { // 新老都是文本
if (oldVnode.text !== vnode.text) {
el.textContent = vnode.text;
}
return;
}
3、如果标签一样,比较属性
- 直接用新的属性生成到元素上
- 如果老的有属性,新的没有直接删除
- 删除样式中老的有,新的没有的
function patchProps(vnode, oldProps = {}) { // 初次渲染时可以调用此方法,后续更新也可以调用此方法
let newProps = vnode.data || {};
let el = vnode.el;
// 如果老的属性有,新的没有直接删除
let newStyle = newProps.style || {};
let oldStyle = oldProps.style || {};
for (let key in oldStyle) {
if (!newStyle[key]) { // 新的里面不存在这个样式
el.style[key] = '';
}
}
for (let key in oldProps) {
if (!newProps[key]) {
el.removeAttribute(key);
}
}
// 直接用新的生成到元素上
for (let key in newProps) {
if (key === 'style') {
for (let styleName in newProps.style) {
el.style[styleName] = newProps.style[styleName];
}
} else {
el.setAttribute(key, newProps[key]);
}
}
}
4、一方有儿子,一方没儿子
- 老的没儿子,新的有儿子
for (let i = 0; i < newChildren.length; i++) {
let child = createElm(newChildren[i]);
el.appendChild(child); // 循环创建新节点
}
- 老的有儿子,新的没儿子
el.innerHTML = ``; // 直接删除老节点
5、双方都有儿子
- 节点相同,只是属性变化,同时循环新的节点和 老的节点,有一方循环完毕就结束了
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
// 同时循环新的节点和 老的节点,有一方循环完毕就结束了
if (isSameVnode(oldStartVnode, newStartVnode)) { // 头头比较,发现标签一致,
patch(oldStartVnode, newStartVnode);
oldStartVnode = oldChildren[++oldStartIndex];
newStartVnode = newChildren[++newStartIndex];
}
}
- 追加元素,直接插入到元素中
// 如果用户追加了一个怎么办?
// 这里是没有比对完的
if (newStartIndex <= newEndIndex) {
for (let i = newStartIndex; i <= newEndIndex; i++) {
el.appendChild(createElm(newChildren[i]))
}
}
- 头部插入新元素,倒序比较
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
// 同时循环新的节点和 老的节点,有一方循环完毕就结束了
if (isSameVnode(oldStartVnode, newStartVnode)) { // 头头比较,发现标签一致,
patch(oldStartVnode, newStartVnode);
oldStartVnode = oldChildren[++oldStartIndex];
newStartVnode = newChildren[++newStartIndex];
}else if(isSameVnode(oldEndVnode,newEndVnode)){ // 从尾部开始比较
patch(oldEndVnode,newEndVnode);
oldEndVnode = oldChildren[--oldEndIndex];
newEndVnode = newChildren[--newEndIndex];
}
}
// 这里是没有比对完的
if (newStartIndex <= newEndIndex) {
for (let i = newStartIndex; i <= newEndIndex; i++) {
// el.appendChild(createElm(newChildren[i]))
// insertBefore方法 他可以appendChild功能 insertBefore(节点,null) dom api
// 看一下伪指针的下一个元素是否存在
let anchor = newChildren[newEndIndex + 1] == null? null :newChildren[newEndIndex + 1].el
el.insertBefore(createElm(newChildren[i]),anchor);
}
}
- 删除尾部节点
if(oldStartIndex <= oldEndIndex){
for (let i = oldStartIndex; i <= oldEndIndex; i++) {
el.removeChild(oldChildren[i].el);
}
}
- 节点反转,用key比较,交叉比对
- 先做头尾比较
头和头比不一样,尾和尾比不一样。老A和新A比较,发现一样,放到D的后面,头部老指针向后移到B,尾部新指针向前移到B。
头和头比不一样,尾和尾比不一样。老B和新B一样,把B移到D的后面,头部老指针向后移,尾部新指针向前移
头和头比不一样,尾和尾比不一样。老C和新C一样,把C移到D的后面,头部老指针向后移,尾部新指针向前移。
头和头比较,一样结束
// 头尾比较 =》 reverse
else if(isSameVnode(oldStartVnode,newEndVnode)){
patch(oldStartVnode,newEndVnode);
el.insertBefore(oldStartVnode.el,oldEndVnode.el.nextSibling); // 移动老的元素,老的元素就被移动走了,不用删除
oldStartVnode = oldChildren[++oldStartIndex];
newEndVnode = newChildren[--newEndIndex];
}
- 再做尾头比较
头头比较不一样,尾尾比较不一样,头尾比较不一样,尾头比较一样 ,把D插入到A前面
else if(isSameVnode(oldEndVnode,newStartVnode)){ // 尾头比较
patch(oldEndVnode,newStartVnode);
el.insertBefore(oldEndVnode.el,oldStartVnode.el);
oldEndVnode = oldChildren[--oldEndIndex];
newStartVnode = newChildren[++newStartIndex];
}
6、乱序比较
根据key和对应的索引将老的内容生成映射表
将第一个元素B去映射表中去查找, 如果找到,把这个元素放到前面,原位置置NULL
相同之后指针同时往后移
头尾比较发现一样,把A放到D的后面并且指针后移,发现是NULL跳过