1、从入口文件看实现
项目入口文件
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
文件位置core\packages\runtime-dom\src\index.ts
保证了render
的唯一性
// // rendererOptions 是patchProp 和nodeOps的合集,包含了dom操作和节点对比方法
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
createApp
中主要调用了ensureRenderer
创建返回的App,ensureRenderer
中返回createRenderer
createRenderer
核心方法baseCreateRenderer
主要包含了创建渲染器和更新和渲染的一系列方法,接下来开始从使用到实现吧~
2、两种渲染器的使用
import { createRenderer ,render,h} from "/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js";
// 1、createRenderer 我们可以自己创建渲染器(自己提供渲染方式)
// 2、render 内置的渲染器 (渲染dom元素)
// 3、h 方法可以创建一个虚拟dom (type,propsOrChildren,children)
console.log(createRenderer,render,h);
let ele = h("h1","hello render")
// render(ele,app) 用提供的api去渲染
// 自定义渲染器
const renderer = createRenderer({
// 创建一个节点
createElement(type){
return document.createElement(type)
},
// 节点的展示
setElementText(el,text){
el.textContent = text
},
// 节点内容
insert(el,container){
container.appendChild(el)
}
})
renderer.render(ele,app)
// runtime-dom 提供一系列操作dom的API
// @vue/runtime-dom是针对浏览器的 --> @vue/runtime-core 是跨平台的 --> @vue/reactivity
3、renderOptions
实现
renderOptions
主要包含两个部分:
dom
节点的操作- 属性的操作
const renderOptions = Object.assign({ patchProp }, nodeOps);
export { renderOptions };
3.1 nodeOps
export const nodeOps = {
insert: (child, parent, anchor) => {
// 添加节点
parent.insertBefore(child, anchor || null);
},
remove: (child) => {
// 节点删除
const parent = child.parentNode;
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), // 搜索元素
};
3.2 patchProp
export default function patchProp(el, key, prevValue, nextValue) {
if (key === "class") {
return patchClass(el, nextValue);
} else if (key === "style") {
return patchStyle(el, prevValue, nextValue);
} else if (/^on[^a-z]/.test(key)) {
return patchEvent(el, key, nextValue);
}else{
return patchAttr(el, key, nextValue)
}
}
3.2.1 patchClass
export default function patchClass(el, value) {
if (value) {
el.className = value;
} else {
el.removeAttribute("class");
}
}
3.2.2 patchStyle
export default function patchStyle(el, prevValue, nextValue) {
let style = el.style;
// 把新的加进来
for (let key in nextValue) {
style[key] = nextValue[key];
}
// 把多余的删掉
if (prevValue) {
for (let key in prevValue) {
if (nextValue[key] == null) {
style[key] = null;
}
}
}
}
3.2.3 patchEvent
// 创建一个调用器,执行value值,value可以改变
function createInvoker(value) {
const invoker = (e) => invoker.value();
invoker.value = value; // 修改value可以修改方法对应的调用函数
return invoker;
}
export default function patchEvent(el, name, nextValue) {
const invokers = el._vei || (el._vei = {});
const eventName = name.slice(2).toLowerCase();
const existingInvoker = invokers[name];
// 如果之前存在同名方法 则修改函数
if (nextValue && existingInvoker) {
return (existingInvoker.value = nextValue);
}
if (nextValue) {
// 如果之前没有同名方法, 则给方法赋值
const invoker = (invokers[name] = createInvoker(nextValue));
return el.addEventListener(eventName, invoker);
}
if (existingInvoker && !nextValue) {
el.removeEventListener(eventName);
invokers[name] = undefined;
}
}
3.2.4 patchAttr
export default function patchAttr(el,key, value) {
if (value) {
el.setAttribute(key, value);
} else {
el.removeAttribute(key)
}
}