-
通过记录移动的旧vnode中最大排序值,小于排序值的进行移动, 大于vnode则更新最大排序值
-
新vnode的顺序就是想要更新的顺序
-
移动DOM、加载DOM、删除DOM
-
遍历过程, 只有新vnode当前节点上个节点是已经排好序,是移动和删除的基线
-
key的作用, 进行diff比较的时候, key相当于虚拟节点的身份证, 是新旧节点之间的映射关系, 减少真实dom的操作
if(Array.isArray(n2.children)) { // 2、新节点是数组节点
if(Array.isArray(n1.children)) { // 2.1、节点是数组节点 // 这里是diff算法, 暂时全部卸载后再更新
const oldChildren = n1.children;
const newChildren = n2.children;
let lastIndex = 0;
for(let i = 0 ; i < newChildren.length; i++) {
let newVnode = newChildren[i];
let j = 0
let find = false;
for(j; j < oldChildren.length; j++) {
let oldVnode = oldChildren[j];
// 移动DOM
if (newVnode.key === oldVnode.key) {
find = true;
patch(oldVnode, newVnode, container);
if (j < lastIndex) {
let prevVnode = newChildren[i - 1];
if(prevVnode) {
let anchor = prevVnode.el.nextSibling();
insert(newVnode.el, container, anchor);
}
} else {
lastIndex = j;
}
break;
}
}
if(!find) {
// 新增DOM
let anchor = null;
let prevVnode = newChildren[i - 1];
if(prevVnode) {
anchor = prevVnode.el.nextSibling();
} else {
anchor = container.firstChild;
}
patch(null, newVnode, container, anchor )
}
}
// 删除DOM
for(let i = 0; i < oldChildren.length; i++) {
let oldVnode = oldChildren[i];
let find = newChildren.find(c => c.key === oldVnode.key);
if(!find) {
unmount(oldVnode)
}
}
} else { // 2.2、节点为空 不处理后挂载,2.3、文本节点 设置为空后挂载
setElementText(container, "");
n2.children.forEach(c => patch(null, c, container))
}
}
总结代码
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、节点是数组节点
const oldChildren = n1.children;
const newChildren = n2.children;
let lastIndex = 0;
for(let i = 0 ; i < newChildren.length; i++) {
let newVnode = newChildren[i];
let j = 0
let find = false;
for(j; j < oldChildren.length; j++) {
let oldVnode = oldChildren[j];
// 移动DOM
if (newVnode.key === oldVnode.key) {
find = true;
patch(oldVnode, newVnode, container);
if (j < lastIndex) {
let prevVnode = newChildren[i - 1];
if(prevVnode) {
let anchor = prevVnode.el.nextSibling;
insert(newVnode.el, container, anchor);
}
} else {
lastIndex = j;
}
break;
}
}
if(!find) {
// 新增DOM
let anchor = null;
let prevVnode = newChildren[i - 1];
if(prevVnode) {
anchor = prevVnode.el.nextSibling();
} else {
anchor = container.firstChild;
}
patch(null, newVnode, container, anchor )
}
}
// 删除DOM
for(let i = 0; i < oldChildren.length; i++) {
let oldVnode = oldChildren[i];
let find = newChildren.find(c => c.key === oldVnode.key);
if(!find) {
unmount(oldVnode)
}
}
} 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
});
类比算法题目: 移动数组1变成数组2, 在尽可能删除不数据的情况下
arr1: [6,7, 8, 10, 5]
arr2: [10, 5,8, 9]
let arr1 = [6,7, 8, 10, 5]
let arr2 = [10, 5,8, 9]
function move(n1, n2) {
let lastIndex = 0;
for(let i = 0; i < n2.length; i ++) {
let has = false;
let j = 0;
let length = n1.length;
for(j; j < length; j ++) {
if(n2[i] === n1[j]) {
has = true;
if(j < lastIndex) {
let newValue = n1.slice(j, j + 1)[0]
n1 = [...n1.slice(0, j) , ...n1.slice(j + 1), newValue]
console.log("移动元素"+ n1[j], n1)
} else {
console.log("不移动元素" + n1[j], n1)
lastIndex = j;
}
}
}
if(!has) {
n1.splice(j, 0, n2[i])
console.log("新增元素"+ n2[i], n1)
}
}
let resultArr = [];
for(let j = 0 ; j < n1.length; j++) {
let has = n2.findIndex(newValue => newValue === n1[j]) !== -1;
if(!has) {
console.log("删除元素", n1[j])
} else {
resultArr.push(n1[j]);
}
}
return resultArr;
}
move(arr1, arr2)