MVVM框架中的渲染函数是会通过视图模板的编译建立的, 简单的说就是对视图模板进行解析并生成渲染函数。
目录
- 什么是虚拟Dom
- 什么是渲染函数
- DomDiff高效更新视图
- 【渲染函数】
渲染模块使用渲染函数根据初始化数据生成虚拟Dom->利用虚拟Dom创建视图页面Html->数据模型一旦变化渲染函数将再次被调用生成新的虚拟Dom,然后做Dom Diff更新视图Html - 【渲染函数】
初始化数据->render(初始化数据)->初始化虚拟DOM->初始视图HTML->数据变化->render(变化数据)->新的虚拟DOM->DOM Diff->更新视图HTML - 【渲染函数】视图模板(template) -> 编译为渲染函数(render function) -> 转化为虚拟Dom
-
- 【渲染】template -> 渲染函数render -> 虚拟Dom
一、 什么是虚拟Dom
1) dom
Dom中节点众多,直接查询和更新Dom性能较差
2) 虚拟Dom
A way of representing the actual DOM with JavaScript Objects. 用JS对象重新表示实际的Dom
二、 什么是渲染函数
在Vue中我们通过将视图模板(template)编译为渲染函数(render function)再转化为虚拟Dom
三、 通过DomDiff高效更新视图
四、 实现渲染函数【关键】
在Vue中我们通过将视图模板(template)编译为渲染函数(render function)再转化为虚拟Dom
4.1) 渲染流程通常会分为三各部分:
- RenderPhase : 渲染模块使用渲染函数根据初始化数据生成虚拟Dom
- MountPhase : 利用虚拟Dom创建视图页面Html
- PatchPhase:数据模型一旦变化渲染函数将再次被调用生成新的虚拟Dom,然后做Dom Diff更新视图Html
mount: function (container) {
const dom = document.querySelector(container);
const setupResult = config.setup();
const render = config.render(setupResult);
let isMounted = false;
let prevSubTree;
watchEffect(() => {
if (!isMounted) {
dom.innerHTML = "";
// mount
isMounted = true;
const subTree = config.render(setupResult);
prevSubTree = subTree;
mountElement(subTree, dom);
} else {
// update
const subTree = config.render(setupResult);
diff(prevSubTree, subTree);
prevSubTree = subTree;
}
});
}
4.1.1) 1 Render Phase
渲染模块使用渲染函数根据初始化数据生成虚拟Dom
render(content) {
return h("div", null, [
h("div", null, String(content.state.message)),
h(
"button",
{
onClick: content.click,
},
"click"
),
]);
},
4.1.2) 2 Mount Phase
利用虚拟Dom创建视图页面Html
function mountElement(vnode, container) {
// 渲染成真实的 dom 节点
const el = (vnode.el = createElement(vnode.type));
// 处理 props
if (vnode.props) {
for (const key in vnode.props) {
const val = vnode.props[key];
patchProp(vnode.el, key, null, val);
}
}
// 要处理 children
if (Array.isArray(vnode.children)) {
vnode.children.forEach((v) => {
mountElement(v, el);
});
} else {
insert(createText(vnode.children), el);
}
// 插入到视图内
insert(el, container);
}
4.1.3) 3 Patch Phase(Dom diff)
数据模型一旦变化渲染函数将再次被调用生成新的虚拟Dom,然后做Dom Diff更新视图Html
function patchProp(el, key, prevValue, nextValue) {
// onClick
// 1. 如果前面2个值是 on 的话
// 2. 就认为它是一个事件
// 3. on 后面的就是对应的事件名
if (key.startsWith("on")) {
const eventName = key.slice(2).toLocaleLowerCase();
el.addEventListener(eventName, nextValue);
} else {
if (nextValue === null) {
el.removeAttribute(key, nextValue);
} else {
el.setAttribute(key, nextValue);
}
}
}
通过DomDiff - 高效更新视图
function diff(v1, v2) {
// 1. 如果 tag 都不一样的话,直接替换
// 2. 如果 tag 一样的话
// 1. 要检测 props 哪些有变化
// 2. 要检测 children -》 特别复杂的
const { props: oldProps, children: oldChildren = [] } = v1;
const { props: newProps, children: newChildren = [] } = v2;
if (v1.tag !== v2.tag) {
v1.replaceWith(createElement(v2.tag));
} else {
const el = (v2.el = v1.el);
// 对比 props
// 1. 新的节点不等于老节点的值 -> 直接赋值
// 2. 把老节点里面新节点不存在的 key 都删除掉
if (newProps) {
Object.keys(newProps).forEach((key) => {
if (newProps[key] !== oldProps[key]) {
patchProp(el, key, oldProps[key], newProps[key]);
}
});
// 遍历老节点 -》 新节点里面没有的话,那么都删除掉
Object.keys(oldProps).forEach((key) => {
if (!newProps[key]) {
patchProp(el, key, oldProps[key], null);
}
});
}
// 对比 children
// newChildren -> string
// oldChildren -> string oldChildren -> array
// newChildren -> array
// oldChildren -> string oldChildren -> array
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
setText(el, newChildren);
}
} else if (Array.isArray(oldChildren)) {
// 把之前的元素都替换掉
v1.el.textContent = newChildren;
}
} else if (Array.isArray(newChildren)) {
if (typeof oldChildren === "string") {
// 清空之前的数据
n1.el.innerHTML = "";
// 把所有的 children mount 出来
newChildren.forEach((vnode) => {
mountElement(vnode, el);
});
} else if (Array.isArray(oldChildren)) {
// a, b, c, d, e -> new
// a1,b1,c1,d1 -> old
// 如果 new 的多的话,那么创建一个新的
// a, b, c -> new
// a1,b1,c1,d1 -> old
// 如果 old 的多的话,那么把多的都删除掉
const length = Math.min(newChildren.length, oldChildren.length);
for (let i = 0; i < length; i++) {
const oldVnode = oldChildren[i];
const newVnode = newChildren[i];
// 可以十分复杂
diff(oldVnode, newVnode);
}
if (oldChildren.length > length) {
// 说明老的节点多
// 都删除掉
for (let i = length; i < oldChildren.length; i++) {
remove(oldChildren[i], el);
}
} else if (newChildren.length > length) {
// 说明 new 的节点多
// 那么需要创建对应的节点
for (let i = length; i < newChildren.length; i++) {
mountElement(newChildren[i], el);
}
}
}
}
}
}
参考
总结
-
虚拟Dom - 用JS对象重新表示实际的Dom
-
【渲染函数过程】
渲染模块使用渲染函数根据初始化数据生成虚拟Dom->利用虚拟Dom创建视图页面Html->数据模型一旦变化渲染函数将再次被调用生成新的虚拟Dom,然后做Dom Diff更新视图Html -
【渲染函数过程】
初始化数据->render(初始化数据)->初始化虚拟DOM->初始视图HTML->数据变化->render(变化数据)->新的虚拟DOM->DOM Diff->更新视图HTML -
【渲染函数】视图模板(template) -> 编译为渲染函数(render function) -> 转化为虚拟Dom
-
【渲染】template -> 渲染函数render -> 虚拟Dom
-
MVVM框架中的渲染函数是会通过视图模板的编译建立的, 简单的说就是对视图模板进行解析并生成渲染函数。