|
一、渲染器
渲染器用于完成渲染操作,比如在浏览器平台上渲染器可以将虚拟DOM转换为真实DOM。
二、一个简单例子理解Vue中渲染器的工作过程
- const { effect, ref } = VueReactivity
- function renderer(domString, container) {
- container.innerHTML = domString
- }
- const count = ref(1)
- effect(() => {
- renderer(`<h1>${count.value}</h1>`, document.getElementById('app'))
- })
- count.value++
复制代码 我们通过引用vue的响应式API,使用其中的(副作用函数)和(生成响应式数据)使得和建立联系,这样当变化时,会调用处理渲染,将标签挂载到id为的元素上。这就是一个简易的渲染器实现。
三、渲染器涉及的几种操作: 挂载、更新、卸载
假设我们已经有了一个渲染器:- const renderer = createRenderer()
复制代码 有一个用于描述元素节点的数据:类似下面的结构- // type为类型,children为子节点
- const vnode = {
- type: 'h1',
- children: 'hello'
- }
复制代码 可以看到这是一个内部有hello文本的节点
那么在渲染器实际工作过程中会有如下的几种情况:- renderer.render(vnode, document.querySelector('#app'))
复制代码 此时涉及到的操作是挂载,只需要将vnode变为DOM元素放在app元素内部即可- renderer.render(newVNode, document.querySelector('#app'))
复制代码 由于已经有旧的节点存在,所以不能简单的直接挂载,需要对新旧节点进行对比,找到需要更新的部分再改变,此时的操作就是更新,为了处理更新,渲染器中需要有对应的逻辑。- renderer.render(null, document.querySelector('#app'))
复制代码 新的节点为null则意味这渲染器需要清空元素内的内容,此时涉及的操作是卸载
根据对以上三种情况的处理,可以将渲染器写为:- function createRenderer() {
- function patch(n1, n2, container) {
- }
- function render(vnode, container) {
- if (vnode) {
- // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
- patch(container._vnode, vnode, container)
- } else {
- if (container._vnode) {
- // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
- // 只需要将 container 内的 DOM 清空即可
- container.innerHTML = ''
- }
- }
- // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
- container._vnode = vnode
- }
-
- return {
- render
- }
- }
复制代码 使用createRenderer重复上面三次渲染,分析过程:- // 首次渲染
- renderer.render(vnode, document.querySelector('#app'))
- // 第二次渲染
- renderer.render(newVNode, document.querySelector('#app'))
- //第三次渲染
- renderer.render(null, document.querySelector('#app'))
复制代码
- 首次渲染:被渲染,并存在中;
- 第二次渲染: 新旧均存在,需要在函数中进行更新;
- 第三次渲染: 新的为判断旧节点是否存在,若旧的存在,则处理为卸载;
四、如何实现一个与平台无关的渲染器:
一个通用的渲染器应该是是与平台无关的,即渲染器的渲染功能不能只在某一个平台中有效。 为了实现这个目标,我们首先来实现一个基于浏览器平台的渲染器,观察在哪些步骤中使用到了和浏览器相关的API,之后可以考虑将这些API抽离,作为配置项,这样就可以实现一个通用的渲染器。
1.实现一个依赖浏览器API的渲染器
以之前的的为例:- const vnode = {
- type: 'h1',
- children: 'hello'
- }
复制代码
- 为标签类型、
- 为子元素。
在以上实现的基础上我们首先完善函数,用于处理节点的挂载和更新情况
- patch(container._vnode, vnode, container)
复制代码 通过之前处理的代码,可以看到,函数会接受三个参数,分别是 旧的节点、新的节点、以及容器, 我们在其中对旧节点参数进行判断,从而处理不同的操作:目前只分析挂载的情况:- function patch(n1, n2, container) {
- if (!n1) {
- mountElement(n2, container)
- } else {
- //
- }
- }
复制代码 如上代码所示: 当即旧节点不存在时证明是挂载操作,则直接调用对新的节点进行挂载处理- function mountElement(vnode, container) {
- const el = createElement(vnode.type)
- if (typeof vnode.children === 'string') {
- el.textContent = vnode.children
- }
- container.appendChild(el)
- }
复制代码 在中我们对挂载的逻辑进行了简单的处理:
1. 使用根据vnode中的的值去创建对应的DOM类型: 如则el为2. 对vnode.children进行判断: 如果为字符串类型则证明子节点是一个文本节点,直接使用元素的属性设置元素。
3. 调用appendChild将处理好的元素添加到目标容器中。由此挂载完成。
2.实现一个通用的渲染器
以上实现的渲染器对于浏览器的API是有依赖的,其中的、、都是需要浏览器环境才能执行的API,如果要让渲染器能够通用,就需要去除对这些API的依赖。
解决办法:将这些API作为配置项传入渲染器创建函数,这样我们可以自己定义使用哪些API去执行渲染操作,从而使得渲染器的工作不再依赖于某一平台。- const renderer2 = createRenderer({
- //创建元素
- createElement(tag) {
- return { tag }
- },
- //设置元素文本内容
- setElementText(el, text) {
- console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`)
- el.text = text
- },
- //将元素插入目标节点
- insert(el, parent, anchor = null) {
- parent.children = el
- }
- })
复制代码 我们将创建元素的逻辑和API放入中,将设置文本内容的API放入中, 将元素挂载到容器的API过程放入中。 再使用传入的配置去调用- function mountElement(vnode, container) {
- const el = createElement(vnode.type)
- if (typeof vnode.children === 'string') {
- setElementText(el, vnode.children)
- }
- insert(el, container)
- }
复制代码 这样我们可以通过传入的函数的配置,不同平台中使用什么样的方法去执行元素的创建、元素内容的处理以及元素挂载等操作。
由此整个渲染函数如下:- function createRenderer(options) { const { createElement, insert, setElementText } = options function mountElement(vnode, container) {
- const el = createElement(vnode.type)
- if (typeof vnode.children === 'string') {
- setElementText(el, vnode.children)
- }
- insert(el, container)
- } function patch(n1, n2, container) {
- if (!n1) {
- mountElement(n2, container)
- } else {
- //
- }
- } function render(vnode, container) { if (vnode) { // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁 patch(container._vnode, vnode, container) } else { if (container._vnode) { // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作 // 只需要将 container 内的 DOM 清空即可 container.innerHTML = '' } } // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode container._vnode = vnode } return { render }}
复制代码 由此一个简单的渲染器已经实现,但在实际情况下元素的挂载和更新还有更多的细节现需要处理,如元素上的属性、class、事件如何被正确的挂载,如何提升渲染器更新的效率(diff算法)等,这些将在后续文章中进行讨论。
以上就是一文详解Vue中渲染器的简单实现的详细内容,更多关于Vue实现渲染器的资料请关注脚本之家其它相关文章!
来源:https://www.jb51.net/javascript/321008lur.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|