ue3
Version:
ue3 build your own vue for learning.
291 lines (278 loc) • 10 kB
text/typescript
import { Fragment, RendererAdapter, VTypeText } from "../types";
import { VNode } from "../types/vnode";
import { unmount } from "./unmount";
export function patch(
oldVNode: VNode | null,
newVNode: VNode,
container: HTMLElement | null,
anchor: ChildNode | null,
adapter: RendererAdapter
) {
if (oldVNode && oldVNode.type !== newVNode.type) {
// 类型不同直接卸载
unmount(oldVNode);
oldVNode = null;
}
const { type } = newVNode;
if (typeof type === "string") {
if (!oldVNode) {
// 挂载
console.log("~~~~~~~~~~~~~~~~~~~~ 新节点", oldVNode, newVNode);
mountElement(newVNode, container, anchor, adapter);
} else {
// 更新
patchElement(oldVNode, newVNode, adapter);
}
} else if (typeof type === "object") {
// 组件
} else if (type === VTypeText) {
if (!oldVNode) {
// @ts-ignore
const el = (newVNode.el = adapter.createText(
newVNode.children as string
));
container && adapter.insert(el, container, anchor);
} else {
const el = (newVNode.el = oldVNode.el);
if (newVNode.children !== oldVNode.children) {
adapter.setText(el, newVNode.children as string);
}
}
} else if (type === Fragment) {
if (!oldVNode) {
(newVNode.children as VNode[]).forEach(child => {
patch(null, child, container, null, adapter);
});
} else {
patchChildren(oldVNode, newVNode, container, adapter);
}
} else {
// 其他
}
}
function patchElement(
oldVNode: VNode,
newVNode: VNode,
adapter: RendererAdapter
) {
const el = (newVNode.el = oldVNode.el);
const oldProps = oldVNode.props || {};
const newProps = newVNode.props || {};
// 更新props
// 更新不等值
for (const key in newProps) {
if (oldProps[key] !== newProps[key]) {
adapter.patchProps(el, key, oldProps[key], newProps[key]);
}
}
// 更新去掉的值
for (const key in oldProps) {
if (!(key in newProps)) {
adapter.patchProps(el, key, oldProps[key], null);
}
}
// 更新children
patchChildren(oldVNode, newVNode, el, adapter);
}
function patchChildren(
oldVNode: VNode,
newVNode: VNode,
container: HTMLElement | null,
adapter: RendererAdapter
) {
// 没有节点、文本节点、一组节点
if (typeof newVNode.children === "string") {
if (oldVNode.children instanceof Array) {
oldVNode.children.forEach(child => unmount(child));
container && adapter.setElementText(container, newVNode.children);
}
if (
(typeof oldVNode.children === "string" &&
oldVNode.children !== newVNode.children) ||
!newVNode.children
) {
container && adapter.setElementText(container, newVNode.children);
}
} else if (Array.isArray(newVNode.children)) {
if (Array.isArray(oldVNode.children)) {
// 核心diff
// const oldChildren = oldVNode.children;
// const newChildren = newVNode.children;
patchKeyedChildren(oldVNode, newVNode, container, adapter);
} else {
container && adapter.setElementText(container, "");
newVNode.children.forEach(child => {
patch(null, child, container, null, adapter);
});
}
} else {
if (oldVNode.children instanceof Array) {
oldVNode.children.forEach(child => unmount(child));
} else if (typeof oldVNode.children === "string") {
container && adapter.setElementText(container, "");
}
}
}
function mountElement(
vnode: VNode,
container: HTMLElement | null,
anchor: ChildNode | null,
adapter: RendererAdapter
) {
const el = adapter.createElement(vnode.type as string);
vnode.el = el;
// handle props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
adapter.patchProps(el, key, null, value);
}
}
// handle children
if (typeof vnode.children === "string") {
adapter.setElementText(el, vnode.children);
} else {
vnode.children &&
vnode.children.forEach(child => {
// TODO
patch(null, child, el, null, adapter);
});
}
container && adapter.insert(el, container, anchor);
return el;
}
function simpleDiff(
oldChildren: VNode[],
newChildren: VNode[],
container: HTMLElement | null,
adapter: RendererAdapter
) {
let lastIndex = 0;
for (let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i];
let j = 0;
let find = false;
for (; j < oldChildren.length; j++) {
const oldChild = oldChildren[j];
if (oldChild.key === newChild.key) {
find = true;
// 更新节点
patch(oldChild, newChild, container, null, adapter);
if (j < lastIndex) {
// 移动节点
const prevVNode = newChildren[i - 1];
if (prevVNode) {
const anchor = prevVNode.el.nextSibling;
container &&
adapter.insert(newChild.el, container, anchor);
}
} else {
lastIndex = j;
}
break;
}
}
// 新增节点
if (!find) {
const prevVNode = newChildren[i - 1];
let anchor = null;
if (prevVNode) {
anchor = prevVNode.el.nextSibling;
} else {
anchor = container?.firstChild;
}
!anchor && (anchor = null);
patch(null, newChild, container, anchor, adapter);
}
// 移除节点
for (let index = 0; index < oldChildren.length; index++) {
const element = oldChildren[index];
const has = newChildren.find(({ key }) => element.key === key);
if (!has) {
unmount(element);
}
}
}
}
/**
* 双端diff
* @param oldVNode 旧节点
* @param newVNode 新节点
* @param container
* @param adapter
*/
function patchKeyedChildren(
oldVNode: VNode,
newVNode: VNode,
container: HTMLElement | null,
adapter: RendererAdapter
) {
const oldChildren = oldVNode.children as Array<VNode | undefined>;
const newChildren = newVNode.children as Array<VNode>;
let oldStartIndex = 0;
let oldEndIndex = oldChildren.length - 1;
let newStartIndex = 0;
let newEndIndex = newChildren.length - 1;
let oldStartVNode = oldChildren[oldStartIndex];
let oldEndVNode = oldChildren[oldEndIndex];
let newStartVNode = newChildren[newStartIndex];
let newEndVNode = newChildren[newEndIndex];
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
if (!oldStartVNode) {
oldStartVNode = oldChildren[++oldStartIndex];
} else if (!oldEndVNode) {
oldEndVNode = oldChildren[--oldEndIndex];
} else if (oldStartVNode.key === newStartVNode.key) {
patch(oldStartVNode, newStartVNode, container, null, adapter);
oldStartVNode = oldChildren[++oldStartIndex];
newStartVNode = newChildren[++newStartIndex];
} else if (oldEndVNode.key === newEndVNode.key) {
patch(oldEndVNode, newEndVNode, container, null, adapter);
oldEndVNode = oldChildren[--oldEndIndex];
newEndVNode = newChildren[--newEndIndex];
} else if (oldStartVNode.key === newEndVNode.key) {
patch(oldStartVNode, newEndVNode, container, null, adapter);
container &&
adapter.insert(
oldStartVNode.el,
container,
oldEndVNode.el.nextSibling
);
oldStartVNode = oldChildren[++oldStartIndex];
newEndVNode = newChildren[--newEndIndex];
} else if (oldEndVNode.key === newStartVNode.key) {
patch(oldEndVNode, newEndVNode, container, null, adapter);
container &&
adapter.insert(oldEndVNode.el, container, oldStartVNode.el);
oldEndVNode = oldChildren[--oldEndIndex];
newStartVNode = newChildren[++newStartIndex];
} else {
const idxInOld = oldChildren.findIndex((element) => {
if (element) {
return element.key === newStartVNode.key;
}
return false;
});
if (idxInOld) {
const idxInOldVNode = oldChildren[idxInOld] as VNode;
patch(idxInOldVNode, newStartVNode, container, null, adapter);
container && adapter.insert(idxInOldVNode.el, container, oldStartVNode.el);
oldChildren[idxInOld] = undefined;
} else {
patch(null, newStartVNode, container, oldStartVNode.el, adapter);
}
newStartVNode = newChildren[++newStartIndex];
}
}
if (oldEndIndex < oldStartIndex && newStartIndex <= newEndIndex) {
for (let index = newStartIndex; index <= newEndIndex; index++) {
const element = newChildren[index];
patch(null, element, container, oldStartVNode ? (oldStartVNode as VNode).el : null, adapter);
}
} else if (newEndIndex < newStartIndex && oldStartIndex <= oldEndIndex) {
for (let index = oldStartIndex; index <= oldEndIndex; index++) {
const element = oldChildren[index];
unmount(element as VNode);
}
}
}