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)}
}