function renderer(domString, container) {
container.innerHTML = domString
} let count = ref(1)
renderer(`<h1>${count.value}</h1>`, document.getElementById('app')) count.value++

  利用响应系统,可以让整个渲染过程自动化。我们通常使用英文 renderer 来表达渲染器。千万不要把 renderer 和 render 弄混了,前者代表渲染器,而后者是动词,表示“渲染”。渲染器的作用是把虚拟 DOM 渲染成特定平台上的真实元素。在浏览器平台上,渲染器会把虚拟 DOM 渲染成为真实 DOM 元素。

  虚拟 DOM 通常用英文 virtual DOM 来表达,有时会简写成 vdom.vnode 和 vdom 有时可以替换使用。渲染器把虚拟 DOM 节点渲染为真实 DOM 节点的过程叫做挂载,通常用英文 mount 来表达。vuejs 组件中的 mounted 钩子就会在挂载完成时触发。这就意味着,在 mounted 钩子中可以访问真实 DOM 元素。渲染器通常需要接收一个挂载点作为参数,用来指定具体的挂载位置。这里的“挂载点”其实就是一个 DOM 元素,渲染器会把该 DOM 元素作为容器元素,并把内容渲染到其中。

function createRenderer() {
function render(vnode, container) {
//...
} return render
}

  上面的代码,可能比较疑惑,为什么需要 createRenderer 函数呢。直接定义 render 不就好了。因为渲染器不仅可以用来渲染,还可以用来激活已有的 DOM 元素,这个过程通常发生在同构渲染的情况下。

function render(vnode, container) {
// ...
} function hydrate(vnode, container) {
// ...
} return {
render,
hydrate
}

  渲染器的内容非常广泛,而用来把 vnode 渲染为真实 DOM 的 render 函数只是其中一部分。实际上,在 vuejs3 中,甚至连创建应用的 createApp 函数也是渲染器的一部分。

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

  上面代码只涉及挂载。而当多次在一个同一个 container 上调用 renderer.render 函数进行渲染时,渲染器除了要执行挂载动作外,还要执行更新动作。由于首次已经把 oldVNode 渲染到 container 内了,所以再次调用 renderer.render 函数并尝试渲染 newVNode 时,就不能简单执行挂载动作了。在这种情况下,渲染器会使用 newVNode 与上一次渲染的 oldVNode 进行比较,试图找到并更新变更点。这个过程叫做“打补丁”,英文通常用 patch 来表达。但实际上,挂载动作本身也可以看作一种特殊的打补丁,它的特殊在于旧的 vnode 是不存在的。所以不必纠结挂载和打补丁这两个概念。

function createRenderer() {
function render(vnode, container) {
if (vnode) {
// 新vnode存在,将其与旧node一起传给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
}
} const renderer = createRenderer() // 首次渲染
renderer.render(vnode1, document.querySelector('#app'))
// 第二次渲染
renderer.render(vnode2, document.querySelector('#app'))
// 第三次渲染
renderer.render(null, document.querySelector('#app'))

  在首次渲染时,渲染器会将 vnode1 渲染为真实 DOM。渲染完成后,vnode1 会存储容器元素的 container._vnode 属性中,它会在后续渲染中作为旧 vnode 使用。在第二次渲染时,旧 vnode 存在,此时渲染器会把 vnode2 作为新 vnode,并将新旧 vnode 一同传递给 patch 函数进行打补丁。第三次渲染时,新 vnode 的值为 null,即说明都不渲染。但此时容器中渲染的是 vnode2 所描述的内容,所以渲染器要清空容器。从上面的代码中可以看出。我们使用 container.innerHTML=''来清空容器。这样清空容易是有问题的,不过这里我们暂时用它来达到目的。

  patch(container._vnode,vnode,container)。patch 函数是整个渲染器的的核心入口,它承载了最重要的渲染逻辑,patch 函数至少接收三个参数:n1:旧 vnode.n2:新 vnode,container:容器。在首次渲染时传递给 patch 函数的第一个参数 n1 是 undefined.这时,patch 函数会执行挂载动作,它会忽略 n1,并直接将 n2 所描述的内容渲染到容器中。所以,patch 函数不仅可以用来完成打补丁,也可以用来执行挂载。

const vnode = {
type: 'h1',
children: 'hello'
} // 创建一个渲染器
const renderer = createRenderer()
// 调用render函数渲染该vnode
renderer.render(vnode, document.querySelector('#app')) function patch(n1, n2, container) {
// 如果n1不存在,意味着挂载,则调用mountElement函数完成挂载
if (!n1) {
mountElement(n2, container)
} else {
// n1存在,意味着打补丁,暂时省略
}
} // 第一个参数n1代表旧node,第二个参数n2代表新vnode。当n1不存在,意味着没有旧vnode,直接进行挂载即可 function mountElement(vnode, container) {
//创建DOM元素
const el = document.createElement(vnode.type)
// 处理子节点,如果子节点是字符串,代表是文本节点
if (typeof vnode.childen === 'string') {
// 因此只需要设置元素的textContent属性即可
el.textContent = vnode.children
} // 将元素添加到容器中
container.appendChild(el)
}

  挂载一个普通标签元素的工作已经完成。接下来,我们分析这段代码存在的问题。我们的目标是设计一个不依赖于浏览器平台的通用渲染器。我们要将浏览器特有的 API 抽离。我们可以将这些操作 DOM 的 API 作为配置项,该配置项可以作为 createRenderer 函数的参数。

const renderer = createRenderer({
// 用于创建元素
createElement(tag) {
return document.createElement(tag)
},
// 用于设置元素的文本节点˝
setElementText(el, text) {
el.textContent = text
}
// 用于在给定的parent下添加指定元素
insert(el,parent,anchor=null){
parent.insertBefore(el,anchor)
}
}) function createRenderer(options){ // 通过options得到操作DOM的API
const {createELement,insert,setElementText}=options // 在这个作用域内定义的函数都可以访问那些API
function mountElement(vnode,container){
// 调用createElement 函数创建元素
const el = createElement(vnode.type)
if(typeof vnode.children === 'string'){
setElementText(el,vnode.children)
} // 调用insert 函数将元素插入到容器内
insert(el,container)
} function patch(n1,n2,container){
//...
} function render(vnode,container){
// ...
} return { render}
}

渲染器的实现(1)--《vue.js设计与实现》的更多相关文章

  1. 【Vue】转-Vue.js经典开源项目汇总

    版权声明:本文为EnweiTech原创文章,未经博主允许不得转载. https://blog.csdn.net/English0523/article/details/88694219 Vue是什么? ...

  2. 【前端】Vue.js经典开源项目汇总

    Vue.js经典开源项目汇总 原文链接:http://www.cnblogs.com/huyong/p/6517949.html Vue是什么? Vue.js(读音 /vjuː/, 类似于 view) ...

  3. Vue.js经典开源项目汇总

    Vue.js经典开源项目汇总 原文链接:http://www.cnblogs.com/huyong/p/6517949.html Vue是什么? Vue.js(读音 /vjuː/, 类似于 view) ...

  4. 公司内部技术分享之Vue.js和前端工程化

    今天主要的核心话题是Vue.js和前端工程化.我将结合我这两年多的工作学习经历来谈谈这个,主要侧重点是前端工程化,Vue.js侧重点相对前端工程化,比重不是特别大. Vue.js Vue.js和Rea ...

  5. Vue.js经典开源项目汇总-前端参考资源

    Vue.js经典开源项目汇总 原文链接:http://www.cnblogs.com/huyong/p/6517949.html Vue是什么? Vue.js(读音 /vjuː/, 类似于 view) ...

  6. Vue.js与React的全面对比

    Vue与React的对比 Vue.js与React.js从某些反面来说很相似,通过两个框架的学习,有时候对一些用法会有一点思考,为加深学习的思索,特翻阅了两个文档,从以下各方面进行了对比,加深了对这两 ...

  7. Vue.js 2.x笔记:路由Vue Router(6)

    1. Vue Router简介与安装 1.1 Vue Router简介 Vue Router 是 Vue.js 官方的路由管理器.它和 Vue.js 的核心深度集成,构建单页面应用. Vue Rout ...

  8. vue.js相关UI组件收集

    内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 ###UI组件 element ★9689 - 饿了么出品的Vue2的web UI工具套件 Vux ★6927 - 基于Vu ...

  9. Vue.js(六)

    路由(用 Vue.js + Vue Router 创建单页应用) <script src="https://unpkg.com/vue-router/dist/vue-router.j ...

  10. 一、Vue.js介绍

    一.介绍 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层,不仅易于上手 ...

随机推荐

  1. sql server 与mysql差异(innodb)

    MySQL SQL SERVER CHAR_LENGTH(str) LEN(character_expression) CONCAT(str1,str2,...) str1+str2 INSERT(s ...

  2. SATA硬盘的数据和电源接口定义(转)

    现在 SATA设备越来越普及,包括STAT硬盘和光驱基本都已经是 SATA接口的了,以前的老式电源输出接口一般都是20针供主板加上4针的电源供硬盘也就是说以前的电脑电源给硬盘供电没有设计15针 SAT ...

  3. Chtholly Tree 学习笔记

    前言 珂朵莉树 (Chtholly Tree) 是一种简单优美的数据结构,就像 Chtholly 一样可爱.暴力即优美. 适用于一些有区间赋值操作的序列操作题. Chtholly Tree 的本质是把 ...

  4. 2170. 使数组变成交替数组的最少操作数 (Medium)

    问题描述 2170. 使数组变成交替数组的最少操作数 (Medium) 给你一个下标从 0 开始的数组 nums ,该数组由 n 个正整数组成. 如果满足下述条件,则数组 nums 是一个 交替数组 ...

  5. 手写简单call、apply、bind

    1.call ~function(){ function call_1(context, ...args){ context = context == undefined ? window : con ...

  6. 递归分批次插入数据(An I/O error occurred while sending to the backend报错解决方案)

    //递归插入public void add(List<Object> all, long start, long limit){ //截取 List<Object> colle ...

  7. 工作三年的.NET程序员现状及其感悟

    算上实习,已经工作三年了.时间过的真的很快,我也从一开始的非标自动化行业成功转入了医疗器械行业,如今在苏州园区的BioBay工作,这里我每天都工作的挺开心的.也于11.6号第一次和如今的女朋友见面,并 ...

  8. 鼠标JS

    1.鼠标按住拖动 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> < ...

  9. [485] C2 Age Of Splendor Update 1 OpCodez

    [485] C2 Age Of Splendor Update 1 Client 00 SendProtocolVersion 01 MoveBackwardToLocation 02 Say 03  ...

  10. ZSTUOJ平台刷题③:Problem A.--打印金字塔

    Problem A: 打印金字塔 Time Limit: 1 Sec  Memory Limit: 64 MBSubmit: 10011  Solved: 6227 Description 请编写程序 ...