-
比较头部
-
比较尾部
-
比较新尾旧头
-
比较新头旧尾
-
找不到遍历旧vnode寻找新头
- 找到, 插入Dom头部, 原位置置为undefined;
- 找不到, 挂载插入Dom头部
-
旧节点比对完, 但是新节点还没有遍历完, 需要加载节点
-
新节点比对完, 但是旧节点还没有遍历完, 需要删除节点
if(Array.isArray(n2.children)) { // 2、新节点是数组节点
if(Array.isArray(n1.children)) { // 2.1、节点是数组节点 // 这里是diff算法, 暂时全部卸载后再更新
let oldChildren = n1.children;
let newChildren = n2.children;
let oldStartIdx = 0;
let oldEndIdx = oldChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
let oldStartVnode = oldChildren[oldStartIdx];
let oldEndVnode = oldChildren[oldEndIdx];
let newStartVnode = newChildren[newStartIdx];
let newEndVnode = newChildren[newEndIdx];
while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) {{
if(!oldStartVnode) {
oldStartVnode = oldChildren[++oldStartIdx];
} else if(!oldEndVnode) {
oldEndVnode = oldChildren[--oldEndIdx];
} else if(oldStartVnode.key === newStartVnode.key) { // 如果旧始节点和新始节点相同
patch(oldStartVnode, newStartVnode, container);
oldStartVnode = oldChildren[++oldStartIdx];
newStartVnode = newChildren[++newStartIdx]
} else if(oldEndVnode.key === newEndVnode.key) { // 如果旧末节点和新末节点相同
patch(oldEndVnode, oldEndVnode, container);
oldEndVnode = oldChildren[--oldEndIdx];
newEndVnode = newChildren[--newEndIdx]
} else if(oldStartVnode.key === newEndVnode.key) { // 如果新末节点和旧始节点相同
patch(oldStartVnode, newEndVnode, container);
insert(oldStartVnode.el, container, oldEndVnode.el.nextSibling);
oldStartVnode = oldChildren[++oldStartIdx];
newEndVnode = newChildren[--newEndIdx];
} else if(oldEndVnode.key === newStartVnode.key) { // 如果旧末节点和新始节点相同
patch(oldEndVnode, newStartVnode, container);
insert(oldEndVnode.el, container, oldStartVnode.el);
oldEndVnode = oldChildren[--oldEndIdx];
newStartVnode = newChildren[++newStartIdx];
} else {
// 双端对比没有结果, 则寻找新始节点在旧DOM中的位置
let idxInOld = oldChildren.findIndex(item => {
return item.key === newStartVnode.key
});
// 如果能找到
if(idxInOld !== -1) {
let vnodeToMove = oldChildren[idxInOld];
patch(vnodeToMove, newStartVnode, container);
insert(vnodeToMove.el, container, oldStartVnode.el);
oldChildren[idxInOld] = null;
// 找不到则进行挂载
} else {
patch(null, newStartVnode, container, oldStartVnode.el);
}
newStartVnode = newChildren[++newStartIdx]
}
}}
// 旧节点比对完, 但是新节点还没有遍历完, 需要加载节点
if(oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
for(let i = newStartIdx; i <= newEndIdx; i++) {
patch(null, newChildren[i], container, oldStartVnode.el);
}
// 新节点比对完, 但是旧节点还没有遍历完, 需要删除节点
} else if(newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
for(let i = oldStartIdx; i < oldEndIdx; i++) {
unmount(oldChildren[i])
}
}
}
总结
const { effect, ref} = VueReactivity;
const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();
function createRenderer(options) {
const{ createElement, setElementText, insert, patchProps, unmount, setText, createText } = options;
function mountElement(vnode, container, anchor) {
const el = vnode.el = createElement(vnode.type);
if(typeof vnode.children === "string") {
setElementText(el, vnode.children)
} else if(Array.isArray(vnode.children)) {
vnode.children.forEach(child => {
patch(null, child, el, anchor)
})
}
if(vnode.props) {
for(let key in vnode.props) {
patchProps(el, key, null, vnode.props[key])
}
}
insert(el, container);
}
function patchElement(n1, n2) {
const el = n2.el = n1.el;
const oldProps = n1.props;
const newProps = n1.props;
for(const key in newProps) {
if(newProps[key] !== oldProps[key]){
patchProps(el, key, oldProps[key], newProps[key])
}
}
for(const key in oldProps) {
if(!(key in newProps)) {
patchProps(el, key, oldProps[key], null)
}
}
patchChildren(n1, n2, el)
}
function patchChildren(n1, n2, container) {
if(typeof n2.children === "string"){ // 1、新节点是文本节点, 1.1、没有节点 直接设置 1.2、文本节点 直接设置 1.3、数组节点 卸载新增
if(Array.isArray(n1.children)){
n1.children.forEach(child => unmount(child))
}
setElementText(container, n2.children);
} else if(Array.isArray(n2.children)) { // 2、新节点是数组节点
if(Array.isArray(n1.children)) { // 2.1、节点是数组节点 // 这里是diff算法, 暂时全部卸载后再更新
let oldChildren = n1.children;
let newChildren = n2.children;
let oldStartIdx = 0;
let oldEndIdx = oldChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
let oldStartVnode = oldChildren[oldStartIdx];
let oldEndVnode = oldChildren[oldEndIdx];
let newStartVnode = newChildren[newStartIdx];
let newEndVnode = newChildren[newEndIdx];
while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) {{
if(!oldStartVnode) {
oldStartVnode = oldChildren[++oldStartIdx];
} else if(!oldEndVnode) {
oldEndVnode = oldChildren[--oldEndIdx];
} else if(oldStartVnode.key === newStartVnode.key) { // 如果旧始节点和新始节点相同
patch(oldStartVnode, newStartVnode, container);
oldStartVnode = oldChildren[++oldStartIdx];
newStartVnode = newChildren[++newStartIdx]
} else if(oldEndVnode.key === newEndVnode.key) { // 如果旧末节点和新末节点相同
patch(oldEndVnode, oldEndVnode, container);
oldEndVnode = oldChildren[--oldEndIdx];
newEndVnode = newChildren[--newEndIdx]
} else if(oldStartVnode.key === newEndVnode.key) { // 如果新末节点和旧始节点相同
patch(oldStartVnode, newEndVnode, container);
insert(oldStartVnode.el, container, oldEndVnode.el.nextSibling);
oldStartVnode = oldChildren[++oldStartIdx];
newEndVnode = newChildren[--newEndIdx];
} else if(oldEndVnode.key === newStartVnode.key) { // 如果旧末节点和新始节点相同
patch(oldEndVnode, newStartVnode, container);
insert(oldEndVnode.el, container, oldStartVnode.el);
oldEndVnode = oldChildren[--oldEndIdx];
newStartVnode = newChildren[++newStartIdx];
} else {
// 双端对比没有结果, 则寻找新始节点在旧DOM中的位置
let idxInOld = oldChildren.findIndex(item => {
return item.key === newStartVnode.key
});
// 如果能找到
if(idxInOld !== -1) {
let vnodeToMove = oldChildren[idxInOld];
patch(vnodeToMove, newStartVnode, container);
insert(vnodeToMove.el, container, oldStartVnode.el);
oldChildren[idxInOld] = null;
// 找不到则进行挂载
} else {
patch(null, newStartVnode, container, oldStartVnode.el);
}
newStartVnode = newChildren[++newStartIdx]
}
}}
// 旧节点比对完, 但是新节点还没有遍历完, 需要加载节点
if(oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
for(let i = newStartIdx; i <= newEndIdx; i++) {
patch(null, newChildren[i], container, oldStartVnode.el);
}
// 新节点比对完, 但是旧节点还没有遍历完, 需要删除节点
} else if(newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
for(let i = oldStartIdx; i < oldEndIdx; i++) {
unmount(oldChildren[i])
}
}
} else { // 2.2、节点为空 不处理后挂载,2.3、文本节点 设置为空后挂载
setElementText(container, "");
n2.children.forEach(c => patch(null, c, container))
}
} else { // 3、新节点为空, 3.1、没有节点 不用处理 3.2、文本节点 设置为空, 3.3、数组节点 直接卸载
if(Array.isArray(n1.children)) {
n1.children.forEach(child => unmount(child))
} else if(typeof n1.children === "string") {
setElementText(container, "");
}
}
}
// n1旧vnode n2新vnode, container容器
function patch(n1, n2, container, anchor) {
if(n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
let { type } = n2;
if(typeof type === "string") { // 节点是普通标签元素
if(!n1) {
mountElement(n2, container, anchor); //挂载节点
} else {
patchElement(n1, n2); // 更新节点
}
} else if(typeof type === "object"){ // 节点是组件
} else if(type === Text){
// 前面有判断,能进入到这个判断分支的, 要不节点为空, 要不同为Text节点
if(!n1) {
const el = n2.el = createText(n2.children);
insert(el, container, null);
} else {
const el = n2.el = n1.el;
if(n1.children !== n2.children) {
setText(el, n2.children)
}
}
} else if(type === Fragment) {
if(!n1) {
n2.children.forEach(c => patch(null, c, container))
} else {
patchChildren(n1, n2, container)
}
} else {
// 省略了其他类型的vnode
}
}
function render(vnode, container) {
if(vnode){
// 打补丁(挂载也是一种特殊的打补丁)
patch(container._vnode, vnode, container)
} else {
if(container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
}
}
function createElement(tag){
return document.createElement(tag);
}
function setElementText(el, text){
el.textContent = text;
}
function insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor) // parent父节点 el需要插入的节点 anchor插入时需要插入在这个节点前面
}
function patchProps(el,key, oldValue, newValue) {
// 暂时放在这里
function shouldSetAsProps(el, key, value) {
if(el.tagName === "INPUT" && key === "form") return false;
return key in el;
}
if(/^on/.test(key)) {
const name = key.slice(2).toLowerCase();
let invokers = el._vei || (el._vei = {});
let invoker = invokers[name];
if(newValue) {
if(!invoker) {
invoker = el._vei = function(e) {
if(e.timeStamp < invoker.attached) return;
if(Array.isArray(invoker.value)) {
invoker.value.forEach(fn => fn(e))
} else {
invoker.value(e);
}
}
invoker.value = newValue;
invoker.attached = performance.now();
el.addEventListener(name, invoker) ; // 在之前没有绑定过数据的情况下进行数据的监听
} else {
invoker.value = newValue;
}
// 为什么不在这里添加addEventListener, 因为每监听一次, 就多一次事件触发
} else if(invoker) {
el.removeEventListener(name, invoker)
}
} else if(key === "class") {
el.className = newValue || ""
} else if(shouldSetAsProps(el, key, newValue)){
let type = typeof el[key];
if(type === "boolean" && newValue === "") {
el[key] = true;
} else {
el[key] = newValue;
}
} else {
el.setAttribute(key, newValue)
}
}
function unmount(vnode) {
if(vnode.type === Fragment) {
vnode.children.forEach(c => unmount(c))
}
const parent = vnode.el.parent;
if(parent) parent.removeChild(vnode.el);
}
function createText(text) {
return document.createTextNode(text)
}
function setText(el, text) {
el.nodeValue = text;
}
const renderer = createRenderer({
createElement,
setElementText,
insert,
patchProps,
createText,
setText
});