禁止转载,侵权必究!
Vue3 渲染器的作用
渲染器的作用是把虚拟 DOM 渲染为特定平台上的真实元素。在浏览器中,渲染器会把虚拟 DOM 渲染成真实 DOM 元素。
1.createRenderer.html script
import {
reactive,
effect,
computed,
watch,
createRenderer,
h,
} from "/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js";
const renderer = createRenderer({
createElement(element) {
return document.createElement(element);
},
setElementText(el, text) {
el.innerHTML = text;
},
insert(el, container) {
container.appendChild(el);
},
});
renderer.render(h("h1", "hello world"), document.getElementById("app"));
创建 runtime-dom 包
runtime-dom 针对浏览器运行时,包括 DOM API 、属性、事件处理等
runtime-dom/package.json
{
"name": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
"unpkg": "dist/runtime-dom.global.js",
"buildOptions": {
"name": "VueRuntimeDOM",
"formats": ["esm-bundler", "cjs", "global"]
}
}
pnpm install @vue/shared@workspace --filter @vue/runtime-dom
实现节点常用操作
runtime-dom/src/nodeOps 这里存放常见 DOM 操作 API,不同运行时提供的具体实现不一样,最终将操作方法传递到 runtime-core 中,所以 runtime-core 不需要关心平台相关代码~
nodeOps.ts
export const nodeOps = {
insert: (child, parent, anchor) => {
// 添加节点
parent.insertBefore(child, anchor || null);
},
remove: (child) => {
// 节点删除
const parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
},
createElement: (tag) => document.createElement(tag), // 创建节点
createText: (text) => document.createTextNode(text), // 创建文本
setText: (node, text) => (node.nodeValue = text), // 设置文本节点内容
setElementText: (el, text) => (el.textContent = text), // 设置文本元素中的内容
parentNode: (node) => node.parentNode, // 父亲节点
nextSibling: (node) => node.nextSibling, // 下一个节点
querySelector: (selector) => document.querySelector(selector), // 搜索元素
};
比对属性方法
pathProps.ts
export const patchProp = (el, key, prevValue, nextValue) => {
if (key === "class") {
// class
patchClass(el, nextValue);
} else if (key === "style") {
// {color:'red',background:red} {color:'red'}
// onXxxx on1
patchStyle(el, prevValue, nextValue);
} else if (/^on[^a-z]/.test(key)) {
// event ,vue 可以绑定多个 onclick1 onclick2
// 事件操作? addEventListener removeEventListener
// @click="fn1" @click="fn2" ?
// invoker.fn = fn1 invoker.fn = fn2
// @click="()=>fn1()"
// @click="()=>invoker.fn" @click="()=> invoker.fn" ?
patchEvent(el, key, nextValue);
} else {
//attr
patchAttr(el, key, nextValue);
}
};
操作类名
./modules/class.ts
export const patchClass = (el, value) => {
if (value == null) {
// 如果没有类名 就是移除
el.removeAttribute("class");
} else {
el.className = value;
}
};
操作样式
./modules/style.ts
export const patchStyle = (el, prev, next) => {
const style = el.style; // 稍后更新 el.style属性
for (let key in next) {
style[key] = next[key];
}
// 老的有新的没有要移除掉
for (let key in prev) {
if (next[key] == null) {
// 老的有新的没有
style[key] = null; // el[style][key] = 'xxx'
}
}
};
操作事件
./modules/event.ts
function createInvoker(initialValue) {
const invoker = (e) => invoker.value(e);
invoker.value = initialValue; // 后续更新的时候 只需要更新invoker的value属性
return invoker;
}
function patchEvent(el, rawName, nextValue) {
// 更新事件
const invokers = el._vei || (el._vei = {});
const exisitingInvoker = invokers[rawName]; // 是否缓存过
if (nextValue && exisitingInvoker) {
exisitingInvoker.value = nextValue; // 更新事件
} else {
const name = rawName.slice(2).toLowerCase(); // 转化事件是小写的
if (nextValue) {
// 缓存函数
const invoker = (invokers[rawName] = createInvoker(nextValue));
el.addEventListener(name, invoker);
} else if (exisitingInvoker) {
el.removeEventListener(name, exisitingInvoker);
invokers[rawName] = undefined;
}
}
}
在绑定事件的时候,绑定一个伪造的事件处理函数 invoker,把真正的事件处理函数设置为 invoker.value 属性的值
操作属性
./modules/attr.ts
function patchAttr(el, key, value) {
// 更新属性
if (value == null) {
el.removeAttribute(key);
} else {
el.setAttribute(key, value);
}
}