创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。
用于编程式地创建组件虚拟 DOM 树的函数。
创建虚拟 DOM 节点 (vnode)。
先实现基本功能,不考虑细节
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<div id="app">div>
<script src="./runtime-dom.global.js">script>
<script>
const app = document.getElementById('app');
const { createRenderer, h, render } = VueRuntimeDOM;
// // 自定义渲染器,渲染虚拟DOM
// const renderer = createRenderer({
// createElement(el) {
// return document.createElement(el);
// },
// setElementText(el, text) {
// el.innerHTML = text;
// },
// insert(el, container) {
// container.appendChild(el);
// },
// patchProp(el, key, prevValue, nextValue) {
// el[key] = nextValue;
// },
// });
// // h > 创建虚拟 DOM 节点 (vnode)
// renderer.render(h('h1', { style: { color: 'pink' } }, 'hello world'), app);
render(h('h1', { style: { color: 'pink' } }, 'hello world'), app);
script>
body>
html>
渲染器可以用不同的方法将虚拟DOM创建成真实DOM,那么我们只需要提供平台特定的节点创建以及更改 API就行了。
新建目录:packages/runtime-dom,目录结构与之前实现的reactivity模块是一样的,此处省略很多行。。。
将当前调试模块更改为runtime-dom
"scripts": {
"dev": "node scripts/dev.js runtime-dom -f global"
},
npm run dev
新建文件:packages/runtime-dom/src/nodeOps.ts
export const nodeOps = {
// 插入
insert(child, parent, anchor = null) {
parent.insertBefore(child, anchor);
},
createElement(tagName) {
return document.createElement(tagName);
},
createText(text) {
return document.createTextNode(text);
},
// 移除
remove(child) {
const parentNode = child.parentNode;
if (parentNode) {
parentNode.removeChild(child);
}
},
// 设置元素文本内容
setElementText(el, text) {
el.textContent = text;
},
setText(node, text) {
node.nodeValue = text;
},
// 查询
querySelector(selector) {
return document.querySelector(selector);
},
parentNode(node) {
return node.parentNode;
},
nextSibling(node) {
return node.nextSibling;
},
};
packages/shared/src/index.ts
const onRE = /^on[^a-z]/;
export const isOn = (key: string) => onRE.test(key);
新建文件:packages/runtime-dom/src/patchProp.ts
import { isOn } from '@vue/shared';
import { patchAttr } from './modules/attrs';
import { patchClass } from './modules/class';
import { patchEvent } from './modules/events';
import { patchStyle } from './modules/style';
export function patchProp(el, key, prevValue, nextValue) {
if (key === 'class') {
// 类名
patchClass(el, nextValue);
} else if (key === 'style') {
// 样式
patchStyle(el, prevValue, nextValue);
} else if (isOn(key)) {
// 事件
patchEvent(el, key, nextValue);
} else {
// 属性
patchAttr();
}
}
新建文件:packages/runtime-dom/src/modules/class.ts
export function patchClass(el, nextValue) {
if (nextValue === null) {
el.removeAttribute('class');
} else {
el.className = nextValue;
}
}
新建文件:packages/runtime-dom/src/modules/style.ts
export function patchStyle(el, prevValue, nextValue = {}) {
// 用新值覆盖
for (const key in nextValue) {
el.style[key] = nextValue[key];
}
// 移除不存在于新值中的旧值
if (prevValue) {
for (const key in prevValue) {
if (!nextValue[key]) {
el.style[key] = null;
}
}
}
}
事件操作,做了缓存,避免频繁卸载和添加事件,属于性能优化
新建文件:packages/runtime-dom/src/modules/events.ts
export function patchEvent(el, eventName, nextValue) {
// vei = vue event invokers = vue事件调用
const invokers = el._vei || (el._vei = {});
// 是否绑定过事件
const existingInvoker = invokers[eventName];
if (nextValue && existingInvoker) {
// 修改
existingInvoker.value = nextValue;
} else {
let event = eventName.slice(2).toLowerCase();
if (nextValue) {
// 添加
const invoker = (invokers[eventName] = createInvoker(nextValue));
el.addEventListener(event, invoker);
} else if (existingInvoker) {
// 移除
el.removeEventListener(event, existingInvoker);
invokers[eventName] = undefined;
}
}
}
// 创建自定义事件
function createInvoker(initialValue) {
const invoker = (e) => invoker.value(e);
invoker.value = initialValue;
return invoker;
}
Vue模块划分中,将createRenderer、h方法放在了runtime-core模块中
这里现将基本方法及文件创建出来,下次实现具体方法
创建模块runtime-core,与其他模块一样的目录结构,package.json可以参考Vue源码,这里省略很多行。。
创建:packages/runtime-core/src/renderer.ts
export function createRenderer(renderOptions) {
const render = (vnode, container) => {};
return { render };
}
创建:packages/runtime-core/src/h.ts
export function h() {
}
创建:packages/runtime-core/src/index.ts
export { createRenderer } from './renderer';
export { h } from './h';
packages/runtime-dom/src/index.ts
import { nodeOps } from './nodeOps';
import { patchProp } from './patchProp';
import { createRenderer } from '@vue/runtime-core';
const rendererOptions = Object.assign({ patchProp }, nodeOps);
export function render(vnode, container) {
createRenderer(rendererOptions).render(vnode, container);
}
export * from '@vue/runtime-core';