Vue.js设计与实现之十一-渲染器的设计

开发 前端
在本文中,我们了解到渲染器的作用和实现,知道其是将虚拟DOM转为真实DOM挂载到节点上,在进行节点更新的时候是通过patch实现的。为了实现跨平台渲染,那么就需要用户通过自定义配置项渲染器,从而实现DOM的渲染挂载。

1、写在前面

在Vue.js框架很多功能依赖渲染器实现,也是框架性能的核心,能够直接影响框架性能。对此,Vue.js3的渲染器通过快捷路径更新的方式,利用编译器提供的信息提升性能。

2、渲染器和响应系统的结合

渲染器是用来执行渲染任务的,可以在浏览器平台来渲染真实DOM元素,它还能实现框架跨平台能力。

前面已经实现了reactivity的源码,为了后续方便讲解后续的操作,对此可以直接使用vue.js3的源码导入使用。

<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js" />

这样就可以通过暴露的API使用:

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++

渲染器把虚拟DOM节点渲染为真实DOM节点的过程叫做挂载,在Vue中mounted钩子函数会在挂载完成时触发,从而实现虚拟DOM的挂载。此外,渲染器还需要一个挂载点作为参数,用于指定具体的挂载位置,渲染器会将此DOM元素作为容器元素,可以实现内容的渲染。

在Vue.js3中是通过createRenderer函数来创建渲染器renderer,而renderer不仅包含渲染器函数render,还包括服务端渲染使用的hydrate函数。用于创建应用的createApp函数也是渲染器的一部分。

const renderer = createRenderer();
//首次渲染
renderer.render(oldVNode, document.querySelector("#app"));
//第二次渲染
renderer.render(newVNode, document.querySelector("#app"));

在上面代码中,首先调用createRenderer函数创建一个渲染器,通过调用渲染器的renderer.render函数创建新的DOM元素,而后将虚拟DOM节点vnode渲染到挂载点上,这就是挂载。

在同一个挂载DOM上,调用renderer.render函数进行渲染时,渲染器除了实现常规的DOM挂载外,还需要执行页面更新操作,也就是DOM更新。在渲染器会使用vnode与上次渲染的oldVNode进行比较,从而找到并更新变更点,此过程叫做"打补丁"(patch)即更新。

function createRenderer(){
function render(domString, container){
if(vnode){
// 新vnode存在,将其与旧vnode传递给patch函数,进行打补丁
patch(container._vnode, vnode, container);
}else{
if(container._vnode){
// 如果旧节点vnode存在,则需要清空挂载点的DOM元素
container.innerHTML = "";
}
}
// 将vnode存储到container._vnode中
container._vnode = vnode
}
return {
render
}
}

在上面代码中,是render函数的基本实现。patch函数是整个渲染器的核心入口,承载了最重要的渲染逻辑。

patch(n1, n2, container){
/***/
}
  • n1:旧VNode。
  • n2:新VNode。
  • container:挂载容器。

3、自定义渲染器

我们看看如何自定义实现渲染器,实现跨平台能力,那就从普通的h1标签开始。

const vnode = {
type:"h1",
children:"pingping"
}

在上面代码中实现了一个简单的vnode,使用type属性描述vnode类型,type属性是字符串类型值时是普通标签。

const vnode = {
type:"h1",
children:"pingping"
}
const renderer = createRenderer();
//首次渲染
renderer.render(vnode, document.querySelector("#app"));

这样,我们就可以渲染为<h1>pingping</h1>为了完成渲染工作,下面可以补充patch函数。

function createRenderer(){
function patch(n1, n2, container){
//如果n1不存在的时候,意味着挂载,需要调用mountElement函数完成挂载
if(!n1){
mountElement(n2, container);
}else{
//n1存在,即打补丁更新DOM节点
}
}
function render(domString, container){
if(vnode){
// 新vnode存在,将其与旧vnode传递给patch函数,进行打补丁
patch(container._vnode, vnode, container);
}else{
if(container._vnode){
// 如果旧节点vnode存在,则需要清空挂载点的DOM元素
container.innerHTML = "";
}
}
// 将vnode存储到container._vnode中
container._vnode = vnode
}
return {
render
}
}

在上面代码中,patch函数中参数n1不存在时,意味着没有旧VNode,此时只需要进行挂载即可。

function mountElement(vnode, container){
const el = document.createElement(vnode.type);
if(typeof vnode.children === "string"){
el.textContent = vnode.children;
}
container.appendChild(el);
}

在上面代码中,简单实现了一个普通标签元素的挂载工作。Vue.js3是个跨平台框架,得益于一个不依赖平台的通用渲染器,而在mountElement函数中依赖了大量浏览器API。想要设计通用渲染器,就必须剥离与平台相关的API,当然可以将这些操作DOM的API作为createRenderer的配置项。

const renderer = createRenderer({
//创建元素
createElement(tag){
return document.createElement(tag);
},
//设置元素的文本节点
setElementText(el, text){
el.textContent = text;
},
//用于给指定父节点添加指定元素
insert(el, parent, achor = null){
parent.insertBefore(el, anchor);
}
})

上面代码中,可以将操作DOM的API封装为一个对象,并把它传递到createRenderer函数,可以通过配置项来取得操作DOM的api。

function createRenderer(options){
const {
createElement,
setElementText,
insert
} = 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){
//...
}
function render(domString, container){
//...
}
return {
render
}
}

现在我们对自定义渲染器有个更深刻的认识,自定义渲染器不是什么黑盒子,只是通过代码抽象,让核心代码不再强依赖于平台特定API,通过自定义配置项实现跨平台。

4、写在最后

在本文中,我们了解到渲染器的作用和实现,知道其是将虚拟DOM转为真实DOM挂载到节点上,在进行节点更新的时候是通过patch实现的。为了实现跨平台渲染,那么就需要用户通过自定义配置项渲染器,从而实现DOM的渲染挂载。

责任编辑:姜华 来源: 前端一码平川
相关推荐

2022-04-19 23:01:54

Vue.jsDOM节点DOM树

2022-04-20 09:07:04

Vue.js的事件处理

2022-04-04 16:53:56

Vue.js设计框架

2022-04-01 08:08:27

Vue.js框架命令式

2022-04-12 08:08:57

watch函数options封装

2022-04-25 07:36:21

组件数据函数

2022-04-11 08:03:30

Vue.jscomputed计算属性

2022-04-05 16:44:59

系统Vue.js响应式

2022-04-14 09:35:03

Vue.js设计Reflect

2022-04-03 15:44:55

Vue.js框架设计设计与实现

2022-04-09 17:53:56

Vue.js分支切换嵌套的effect

2022-05-03 21:18:38

Vue.js组件KeepAlive

2022-04-17 09:18:11

响应式数据Vue.js

2022-04-26 05:55:06

Vue.js异步组件

2022-04-16 13:59:34

Vue.jsJavascript

2010-08-13 11:02:27

Flex渲染器

2009-07-15 13:48:26

Swing模型和渲染器

2016-11-01 19:10:33

vue.js前端前端框架

2009-07-16 10:11:06

渲染器RendererSwing组件

2009-07-16 10:26:49

渲染器接口Swing
点赞
收藏

51CTO技术栈公众号