一文读懂web标准的基石:web IDL

理解Vue.js 3:Web IDL在声明式UI中的应用
本文深入探讨Vue.js 3的设计,特别是其声明式UI的实现。通过Web IDL,我们理解组件、模板和渲染器如何协同工作,构建虚拟DOM并将其转换为实际DOM。Vue.js 3的组件是声明式UI的核心,编译器和渲染器共同作用,形成框架的有机整体。

一、前言

我们从全局视角了解Vue.js 3 的设计思路、工作机制及其重要的组成部分。我们可以把这些组成部分当作独立的功能模块,看看它们之间是如何相互配合的。

  • UI的两种形式:模板字符串和虚拟DOM
  • Vue.js框架的两个重要组成部分:编译器和渲染器

二、本章内容

2.1 声明式地描述UI

Vue.js 3 是一个声明式的UI框架。设计一个这样的框架,我们需要了解编写前端页面都涉及哪些内容?具体如下:

  • DOM元素:是div标签还是a标签。
  • 属性:如a标签的href属性,再如idclass等通用属性
  • 事件:如clickkeydown等。
  • 元素的层级结构:DOM树的层级结构,既有子节点,又有父节点。

那么,如何声明式地描述上述内容呢? 在Vue.js 3 中的解决方案是:

  • 使用与HTML标签一致的方式来描述DOM元素、属性与层级结构等,例如 <div id="app"><span>趋动科技</span></div>;
  • 使用:或v-bind来描述动态绑定的属性,使用@或v-on来描述事件例如 <div :name="name" @click="handleClick"></div>; 这样,用户不需要手写任何命令式代码,就可以实现声明式地描述UI。当然除了使用这种模板形式描述UI外,还可以用JS对象来描述:
const title = {tag: 'div',props: { onClick: handleClick},children: [{tag: 'span'}]
} 

对应到Vue.js模板是:

<div @click="handleClick"><span></span></div> 

使用JS对象来描述比模板来说的优势是更加灵活。假如我们根据变量level的取值分别渲染h1~h6标签:

//模板
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h3>
<h4 v-else-if="level === 4"></h4>
<h5 v-else-if="level === 5"></h5>
<h6 v-else-if="level === 6"></h6>

//JS对象描述,只需要一个变量来代表h标签即可
let level = 1; //h标签的级别
const title = {tag:`h${level}`//h1标签
} 

我们在Vue.js组件中通过手写渲染函数就是使用虚拟DOM来描述UI, h函数的返回值就是一个对象,让我们编写虚拟DOM变动更加轻松:

import { h } from 'vue'
export default {render(){return h('h1',{ onClick:handler }) //虚拟DOM}
} 

组件的渲染函数:一个组件要渲染的内容是通过渲染函数来描述的,也就是代码中的render函数,Vue.js会根据组件的render函数的返回值拿到虚拟DOM,然后将组件的内容渲染出来。

2.2 初识渲染器

我们可以使用JS对象来描述真实的DOM结构。那么虚拟DOM又是如何通过渲染函数转为真实DOM后,渲染到页面中的呢? 渲染器的作用就是把虚拟DOM渲染为真实DOM

假如我们有如下虚拟DOM:

const vNode = {tag:'div',props: {onClick: () => alert('hello')},children: 'click me'
} 

接下来,我们需要编写一个渲染器,把上面这段虚拟DOM渲染为真实DOM,renderer函数接收两个参数:vnode虚拟DOM对象与container真实DOM挂载点,渲染器会把虚拟DOM渲染到该挂载点下。

function renderer(vnode,container){// 使用 vnode.tag 作为标签名选创建DOM元素const el = document.createElement(vnode.tag)// 遍历vnode.props,将属性、事件添加到DOM元素for(const key in vnode.props){if(/^on/.test(key)){// 如果 key 以 on 开头,说明它是事件el.addEventListener(key.substr(2).toLowerCase(), //事件名称 onClick --> clickvnode.props[key] //事件处理函数)}}// 处理 childrenif(typeof vnode.children === 'string'){// 如果children 是字符串,说明它是元素的文本子节点el.appendChild(document.createTextNode(vnode.children))}else if(Array.isArray(vnode.children)){//递归调用 renderer 函数渲染子节点,使用当前元素el作为挂载点vnode.children.forEach(child => renderer(child,el))}//将元素添加到挂载点下container.appendChild(el)
} 

渲染器renderer的实现思路:

  • 创建元素
  • 为元素添加属性和事件
  • 递归遍历children创建节点

2.3 组件的本质

组件就是一组DOM元素的封装,这组DOM元素就是组件要渲染的内容,因此可以定义函数来描述组件本身的内容。

const MyComponent = function(){return {tag:'div',props:{onClick:() => alert('hello')},children:'click me'}} 

这时候的虚拟DOM就是这样的:

const vnode = {tag: MyComponent
} 

对于处理函数和字符串的时候,render 函数也会有所不同:

// 字符串 渲染函数
function mountElement(vnode, container) {// 使用 vnode.tag 作为标签名创建 DOM 元素const el = document.createElement(vnode.tag)// 遍历 vnode.props 将属性、事件添加到 DOM 元素for(const key in vnode.props) {if(/^on/.test(key)) {// 如果 key 以 on 开头,说明它是事件el.addEventListener(key.substr(2).toLowerCase(), // 事件名称 onClick --> clickvnode.props[key] // 事件处理函数)}}// 处理 childrenif(typeof vnode.children === 'string') {// 如果 children 是字符串,说明它是元素的文本子节点el.appendChild(document.createTextNode(vnode.children))} else if(Array.isArray(vnode.children)) {// 递归调用 renderer 渲染子节点,使用当前 el 作为挂载点vnode.children.forEach(child => renderer(child, el))}// 将元素添加到挂载点下container.appendChild(el)
}

//函数渲染函数
function mountComponent(vnode, container) {// 调用组件函数,获取组件要渲染的内容(虚拟DOM)const subtree = vnode.tag()// 递归调用 renderer 渲染 subtreerenderer(subtree, container)
}

//主 render 函数
function renderer(vnode, container) {if (typeof vnode.tag === 'string') {// 说明 vnode 描述的是标签元素mountElement(vnode, container)} else if (typeof vnode.tag === 'function') {// 说明 vnode 描述的是组件mountComponent(vnode, container)}
} 

2.4 模板的工作原理

无论是手写虚拟 DOM还是使用模板,都是属于声明式 UI,上文中讲过,需要将虚拟 DOM 转换为真实 DOM,这一过程需要的就是编译器。 编译器和渲染器一样,就是一个程序,编译器的作用就是将模板编译成渲染函数,如下模板:

<div @click="handler">click me</div> 

最终通过编译器编译,会将其转化为渲染函数:

export default {render() {return h('div', { onClick: handler }, 'click me')}
} 

对于一个组件来说,最终都是通过渲染函数产生的,然后渲染器把渲染函数返回的虚拟 DOM,渲染为真实 DOM,这就是模板的工作原理,也是Vue.js 渲染页面的流程。

2.5 Vue.js是各个模块组成的有机整体

在Vue.js框架设计中,组件的实现依赖于渲染器和编译器,渲染器和编译器之间是互相关联、互相制约的,它们共同构成一个有机整体,不同模块之间互相配合,进一步提升框架性能。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值