引言
<<往期回顾>>
1.vue3源码分析——rollup打包monorepo
2.vue3源码分析——实现组件的挂载流程
3.vue3源码分析——实现props,emit,事件处理等
4.vue3源码分析——实现slots
5.vue3源码分析——实现组件通信provide,inject
本期来实现, vue3的自定义渲染器,增加runtime-test子包,所有的源码请查看
正文
createRenderer的作用是: 实现vue3的runtime-core的核心,不只是仅仅的渲染到dom上,还可以渲染到canvas,webview等指定的平台
请思考🤔🤔🤔,createRenderer是怎么做到的呢?
设计createRenderer函数
createRenderer顾名思义就是创造一个render(可以直接导出一个render函数),现在咱们的是直接在render.ts中对外导render函数出提供给createApp中使用
对于createApp而言,需要render函数,那么咱们可以通过函数的参数穿进来,那就变成这个样子的形式
编码
// 通过上面的分析,先把createApp给改造一下,需要一个新的函数来包裹,并且传入render函数 export function createAppApi(render){ return function createApp(rootComponent){ // ……原有的逻辑不变 } } // 在createAppApi里面需要render,那就在createRenderer里面调用并且给他, // 返回一个新的createApp export function createRenderer(){ function render(vnode, container) { // 调用patch patch(vnode, container, null) } // ……省略其他所有的函数 return { // 这样设计是不是对外导出了一个新的createAPP哇 createApp: createAppApi(render) } }
渲染平台
既然是自定义渲染平台,那肯定是需要修改元素的挂载逻辑,并且把需要挂载的平台给传入进来
分析
目前代码里面默认是渲染到dom,在mountElement里面使用了document.createElement, dom.setAttribute, dom.innerHtml等逻辑都是用来处理dom操作,其他的平台挂载元素的方式是不一样的,那么怎么解决这个问题呢?
需要解决这个问题,也是非常简单的,既然咱们不知道是挂载到哪里,那直接通过createRenderer里面传入进来就ok啦😄😄😄 目前用到的主要是四个地方涉及到dom操作,把这四个地方统统封装成函数,然后通过createRenderer里面作为options里面传入即可
编码
在createRenderer里面加入参数options,并且结构出四个函数 export function createRenderer(options) { const { // 创建元素 createElement, // 绑定key patchProps, // 插入操作 insert, // 设置文本 setElementText } = options function mountElement(vnode: any, container: any, parentComponent) { const el = createElement(vnode.type) // 设置vnode的el vnode.el = el // 设置属性 const { props } = vnode for (let key in props) { patchProps(el, key, props[key]) } // 处理子元素 const children = vnode.children if (vnode.shapeflag & ShapeFlags.ARRAY_CHILDREN) { // 数组 mountChildren(children, el, parentComponent) } else if (vnode.shapeflag & ShapeFlags.TEXT_CHILDREN) { // 自定义插入文本 setElementText(el, String(children)) } // 挂载元素 insert(el, container) } }
这么改造,目前createRenderer的功能实现了,但是会发现所有用的createApp的测试用例都不行了,由于咱们没目前没有对外导出createApp。
runtime-test
从目前来说,本块的内容可以说是 runtime-dom,因为runtime-test对外提供的确实是dom环境的测试,方便用于runtime-core的测试
新建子包的过程不在这里描述哈,有兴趣的可以查看
runtime-test需要的依赖是:
"dependencies": { "shared":"workspace:shared@*", "runtime-core":"workspace:runtime-core@*" }
分析
runtime-test的作用是对外提供一个createApp函数,那就需要调用createRender来创建一个customRender,customRender里面有createApp函数。 调用createRender又需要传入一个options,options是我们当前对应平台的4个函数,分别是:
- createElement: 创建dom
- patchProps: 处理属性
- insert: 将某个元素插入到哪里
- setElementText: 设置文本
编码
function createElement(type) { return document.createElement(type); } function patchProps(el, key, value) { if (isOn(key)) { // 注册事件 el.addEventListener(key.slice(2).toLowerCase(), value) } el.setAttribute(key, value) } function insert(el, container) { container.append(el) } function setElementText(el, text) { el.textContent = text; } const render: any = createRenderer({ createElement, patchProps, insert, setElementText }); // 对外导出createApp export function createApp(...args) { return render.createApp(...args); } // 需要使用runtime-core里面的所有内容,因为里面有的变量是在闭包中进行使用的 export * from 'runtime-core'
思考🤔🤗🤔: 处理完runtime-test就需要在runtime-core中进行引用,直接在runtime-core中引用么?
那肯定是不行的,runtime-test里面引用runtime-core,如果runtime-core在引用runtime-test的话,那就是循环引用了,𝒮ℴ, 𝒽ℴ𝓌 𝓉ℴ 𝓇ℯ𝓈ℴ𝓁𝓋ℯ 𝒾𝓉 ?
解决方式: **在上一级的package.json上加入runtime-test这个包,那么在runtime-core中就能引用啦!**😝😝😝
测试效果
总结
本期主要实现了createRenderer函数,改造createApp等函数,通过这些函数,可以看到vu3在设计方面的用心良苦,尽量让vue3满足更多的人。增加了runtime-test,方便用于测试dom环境下面的情况!