vue开发runtime core中的虚拟节点示例详解
ClyingDeng 人气:0引言
我们知道runtime-dom内部功能其实是 将渲染时所需要节点操作的 API (即rendererOptions) 传入到runtime-core中。
之前文章我们可以大概知道 runtime-dom 中节点操作属性API有哪些,并试着实现部分API功能。runtime-core 是实现与平台无关的运行时功能(即runtime-core中的节点渲染)。
本文讲述的内容是:实现 runtime-core 中的createAppAPI,完成虚拟节点的创建,以及render中的挂载所需参数的获取。
将API传入到runtime-core中
我们再来看下上篇文章的例子:
<div id="app"></div> <script src="./runtime-dom.global.js"></script> <script> let { createApp, h, ref } = VueRuntimeDOM function useCounter() { const count = ref(0) const add = () => { count.value++ } return { count, add } } let App = { props: { title: {} }, setup() { let { count, add } = useCounter() return { count, add } }, // 每次更新重新调用render方法 render(proxy) { return h('h2', { onClick: this.add, title: proxy.title }, 'hello dy' + this.count) } } let app = createApp(App, { title: 'dy' }) // 组件 组件参数 app.mount('#app') </script>
用户通过解析runtime-dom中的 createApp,在createApp中接收组件和组件传入的属性参数,对根节点进行初始化渲染。
那这样的话,我们在runtime-dom需要对外暴露一个createApp方法,内部实现根据相应参数实现真实节点的初始化工作。
/** * * @param component 组件 * @param rootProps 传入的属性 */ export const createApp = (component, rootProps = null) => { } // 将runtime-core中所有API导出 export * from '@vue/runtime-core'
createRenderer 初始化
此时,我们就需要创建一个渲染器createRenderer。在渲染器中实现一个mount挂载的方法。看下面的结构:
export function createRenderer(rendererOptions) { // 虚拟节点转化成真实节点 渲染到容器中 const render = (vnode, container) => { } // 创建节点相关的api mount use mixin const createAppAPI = (render) => { return (rootComponent, rootProps) => { let app = { mount(container) { }, use() { }, mixin() { }, component() { } } return app } } return { render, createApp: createAppAPI(render) } }
createAppAPI中是组件相关的API,比如mount、use、mixin等,在createAppAPI内部我们肯定还需要元素挂载实现的render方法。
我们可以看到官网中的createRenderer API,中对外暴露了 render 和 createApp两种方法,其实runtime-core中内部也是这样实现的。
当然我们只是先将空架子搭建起来,后面还需要一步步填充功能。
createApp 内部实现
把上面例子来出来:
let app = createApp(App, { title: 'dy' }) // 组件 组件参数 app.mount('#app')
继续回到createApp这个方法:
createRenderer实现一个渲染器,我们通过 createRenderer 可以解构其中对外暴露的方法,在 createApp 内部进行初始化,最后将其返回,用户再通过mount对元素进行挂载。
const { createApp } = createRenderer(rendererOptions) let app = createApp(component, rootProps) // 渲染器完成 将其返回
在初始化的时候我们通过用户传入的#app
,进行初始化。在执行用户mount前,我们在createApp中存在实际元素挂载功能的mount,我们就需要先将其保存下来,在执行用户mount时,在其内部进行实际的元素挂载(即app.mount内部执行我们实际mount功能)。
createApp内部:
let { mount } = app // 自己的 app.mount = (containerOrSelector) => { // 用户传入的 #app const container = nodeOps.querySelector(containerOrSelector) if (!container) return container.innerHTML = '' mount(container) }
在挂载的时候,我们需要清空当前节点的内部元素,再将其挂载。
完整的createApp:
export const createApp = (component, rootProps = null) => { const { createApp } = createRenderer(rendererOptions) // 将core中的createApp解构出来 // 创建一个渲染器 把功能传递给runtime-core let app = createApp(component, rootProps) let { mount } = app // 调用的是core中的mount // app.mount 用户初始化的mount ==> app.mount("#app"); // 在用户的app.mount中再去执行core中的mount ==> core中就会存在domAPI 需要渲染的组件 目标属性,最后再去挂载到容器中 /** * containerOrSelector 用户传入的容器 */ app.mount = (containerOrSelector) => { // 用户传入的 #app const container = nodeOps.querySelector(containerOrSelector) if (!container) return // 不为空直接返回 // 在挂载之前清空 container.innerHTML = '' // 调用的是core中的mount 处理节点后将container传入mount mount(container) } return app }
这样我们 createRenderer 就可以拿到节点操作的DOM API(rendererOptions
)、需要渲染的组件(component
)、目标属性(rootProps
)和需要挂载的位置-容器(container
)。createRenderer也就可以独立出来,放到runtime-core中,在core中我们单独去实现元素的渲染。
runtime-core
视角转到我们core中,在core中我们需要实现一个渲染器。目录结构如下:
我们主要实现createRenderer这个功能对外暴露的两个API,可以先来看看createApp中的 createAppAPI 。
createAppAPI
在 createAppAPI 中,我们目前需要实现真实元素的挂载。
说的通俗一点:挂载的核心就是根据传入的组件对象,创造一个组件的虚拟节点,再将这个虚拟节点渲染到容器中。
具体分两步走:
mount(container) { // 1:创建组件虚拟节点 const vnode = cerateVNode(rootComponent, rootProps)// h函数 // 2:虚拟节点渲染到容器中 render(vnode, container) }
cerateVNode 创建组件虚拟节点
我们首先要知道,虚拟节点是一个数据对象,用一个数据结构来对元素进行描述,当前元素是组件还是元素、它的字节点、携带的参数等有哪些。
类型表示
我们需要判断传过来的type是组件还是元素,改如何表示呢?
加载全部内容