create-vnode
Version:
一个简易的虚拟dom生成器和渲染器,包含diff算法
123 lines (111 loc) • 3.13 kB
JavaScript
import { isString, objForEach, aryForEach, isNotEmptyObj } from "../util/index.js";
import { REPLACE, PROPS, TEXT } from "../util/common.js";
import listDiff from "../util/list-dff.js";
/**
*
* @param {旧Dom树} oTree
* @param {新Dom树} nTree
* 返回差异记录
*/
function diff(oTree, nTree) {
// 节点位置
let index = 0;
// 差异记录
const patches = {};
dfsWalk(oTree, nTree, index, patches);
return patches;
}
function dfsWalk(oNode, nNode, index, patches) {
const currentPatch = [];
// 首次渲染
if (nNode === null) return;
// 都是字符串形式并且不相同直接替换文字
if (isString(oNode) && isString(nNode)) {
oNode !== nNode &&
currentPatch.push({
type: TEXT,
content: nNode
});
// 同种标签并且key相同
} else if (oNode.tagName === nNode.tagName && oNode.key === nNode.key) {
// 至少一方有值
if (isNotEmptyObj(oNode.props) || isNotEmptyObj(nNode.props)) {
// 计算props结果
const propsPatches = diffProps(oNode, nNode);
// 有差异则重新排序
propsPatches &&
currentPatch.push({
type: PROPS,
props: propsPatches
});
}
// children对比
if (
!(!isNotEmptyObj(nNode.props) && nNode.props.hasOwnProperty("ignore"))
) {
(oNode.children.length || nNode.children.length) &&
diffChildren(
oNode.children,
nNode.children,
index,
patches,
currentPatch
);
}
} else {
// 都不符合上面情况就直接替换
currentPatch.push({ type: REPLACE, node: nNode });
}
// 最终对比结果
currentPatch.length && (patches[index] = currentPatch);
}
/**
* 同级对比
* @param {*} oChildren
* @param {*} nChildren
* @param {*} index
* @param {*} patches
* @param {*} currentPatch
*/
function diffChildren(oChildren, nChildren, index, patches, currentPatch) {
// 得出相对简化移动路径
const diffs = listDiff(oChildren, nChildren, "key");
// 保留元素
nChildren = diffs.children;
// 记录排序位移
diffs.moves.length &&
currentPatch.push({ type: REORDER, moves: diffs.moves });
// 深度遍历
let leftNode = null;
let currentNodeIndex = index;
aryForEach(oChildren, (_item, _index) => {
const nChild = nChildren[_index];
currentNodeIndex =
leftNode && leftNode.count
? currentNodeIndex + leftNode.count + 1
: currentNodeIndex + 1;
_item !== nChild && dfsWalk(_item, nChild, currentNodeIndex, patches);
leftNode = _item;
});
}
/**
*
* @param {旧节点} oNode
* @param {新节点} nNode
*/
export function diffProps(oNode, nNode) {
let isChange = false;
const oProps = oNode.props;
const nProps = nNode.props;
// 节点属性记录
const propsPatched = {};
// 替换/新增属性
objForEach(oProps, key => {
if (nProps[key] !== oProps[key] || !oProps.hasOwnProperty(key)) {
!isChange && (isChange = true);
propsPatched[key] = nProps[key];
}
});
return !isChange ? null : propsPatched;
}
export default diff;