何时/使用 Vue3 render 函数的教程详解


Posted in Javascript onJuly 25, 2020

什么是 DOM?

何时/使用 Vue3 render 函数的教程详解

如果我们把这个 HTML 加载到浏览器中,浏览器创建这些节点,用来显示网页。所以这个HTML映射到一系列DOM节点,然后我们可以使用JavaScript进行操作。例如:

let item = document.getElementByTagName('h1')[0]
item.textContent = "New Heading"

VDOM

网页可以有很多DOM节点,这意味着DOM树可以有数千个节点。这就是为什么我们有像Vue这样的框架,帮我们干这些重活儿,并进行大量的JavaScript调用。

然而,搜索和更新数千个DOM节点很明显会变慢。这就是Vue和其他类似框架有一种叫做虚拟DOM的东西。虚拟DOM是表示DOM的一种方式。例如,这个HTML也可以通过一个虚拟节点来表示,看起来像这样。如您所见,它只是一个JavaScript对象。

<div>Hello</div>
{
 tag: 'div',
 children: [
 {
 text: 'Hello'
 }
 ]
}

Vue知道如何使用此虚拟节点并挂载到DOM上,它会更新我们在浏览器中看到的内容。实际上还有一个步骤其中,Vue基于我们的模板创建一个渲染函数,返回一个虚拟DOM节点。

何时/使用 Vue3 render 函数的教程详解

渲染函数可以是这样的:

render(h) {
 return h('div', 'hello')
}

当组件更改时,Render函数将重新运行,它将创建另一个虚拟节点。然后发送旧的 VNode 和新的 VNode 到Vue中进行比较并以最高效的方式在我们的网页上更新。

何时/使用 Vue3 render 函数的教程详解

我们可以将虚拟DOM和实际DOM的关系类比为蓝图和实际建筑的关系。假设我更改了29楼的一些数据。我改变了家具的布局还加了一些橱柜。我有两种方法可以改变。首先,我可以拆除29楼的一切从头开始重建。或者我可以创造新的蓝图,比较新旧蓝图并进行更新以尽可能减少工作量。这就是虚拟DOM的工作原理。Vue 3让这些更新更快并且更高效。

核心模块

Vue 的三个核心模块:

  • Reactivity Module 响应式模块
  • Compiler Module 编译器模块
  • Renderer Module 渲染模块

响应式模块允许我们创建 JavaScript 响应对象并可以观察其变化。当使用这些对象的代码运行时,它们会被跟踪,因此,它们可以在响应对象发生变化后运行。

编译器模块获取 HTML 模板并将它们编译成渲染函数。这可能在运行时在浏览器中发生,但在构建 Vue 项目时更常见。这样浏览器就可以只接收渲染函数。

渲染模块的代码包含在网页上渲染组件的三个不同阶段:

  • 渲染阶段
  • 挂载阶段
  • 补丁阶段

在渲染阶段,将调用 render 函数,它返回一个虚拟 DOM 节点。
在挂载阶段,使用虚拟DOM节点并调用 DOM API 来创建网页。
在补丁阶段,渲染器将旧的虚拟节点和新的虚拟节点进行比较并只更新网页变化的部分。

现在让我们来看一个例子,一个简单组件的执行。它有一个模板,以及在模板内部使用的响应对象。首先,模板编译器将 HTML 转换为一个渲染函数。然后初始化响应对象,使用响应式模块。接下来,在渲染模块中,我们进入渲染阶段。这将调用 render 函数,它引用了响应对象。我们现在监听这个响应对象的变化,render 函数返回一个虚拟 DOM 节点。接下来,在挂载阶段,调用 mount 函数使用虚拟 DOM 节点创建 web 页面。最后,如果我们的响应对象发生任何变化,正在被监视,渲染器再次调用render函数,创建一个新的虚拟DOM节点。新的和旧的虚拟DOM节点,发送到补丁函数中,然后根据需要更新我们的网页。

渲染器机制

拥有虚拟DOM层有一些好处,最重要的是它让组件的渲染逻辑完全从真实DOM中解耦,并让它更直接地重用框架的运行时在其他环境中。例如,Vue允许第三方开发人员创建自定义渲染解决方案目标,不仅仅是浏览器也包括IOS和Android等原生环境,也可以使用API创建自定义渲染器直接渲染到WebGL而不是DOM节点。在Vue 2中我们实际上已经有了这种能力但是,我们在Vue 2中提供的API没有正式记录并且需要分叉源代码。所以这给维护带来了很大的负担,对开发这些定制解决方案的开发人员在Vue 3中,我们让自定义渲染器API成为一等公民。因此开发人员可以直接拉取Vue运行时核心作为依赖项,然后利用自定义渲染器API构建自己的自定义渲染器。事实上,我们已经有了早期用户报告他们已经成功地构建了一个使用Vue 3 API关于虚拟DOM的WebGL渲染器。

另一个重要方面,它提供了能力以编程方式构造、检查、克隆以及操作所需的DOM结构,在实际返回渲染引擎之前你可以利用JavaScript的全部能力做到这些。这个能力很重要,因为总会有某些情况在UI编程中使用模板语法会有一些限制,你只需要一种有充分灵活性的合适的编程语言来表达潜在的逻辑。现在,这种情况实际上是相当罕见的在日常UI开发中。但当你在创作一个库的时候,这种情况更常见或编写UI组件套件,你打算上传供第三方开发者使用。让我们想象一下一个,像复杂类型的顶部框这样的组件或者一个与一堆文本相关联的输入框,这些类型的组件通常包含很少的标记,但它们将包含很多交互逻辑在这些情况下,模板语法有时候会限制你更容易地表达潜在的逻辑,或者有时候你会发现自己在模板中加入了很多逻辑,但你还是有很多逻辑在JavaScript 中而 render 函数允许你把这些逻辑组合在一个地方你通常不需要想太多关于这些情况下的标记。

所以我理解是模板会完成你要做的事在99%的情况下你只需要写出HTML就好了,但偶尔可能想做些更可控的事情在,你需要编写一个渲染函数。Vue 2中的渲染函数如下所示,

render(h) {
 return h (
 'div', {
 attrs: {
 id: foo
 },
 on: {
 click: this.onClick
 },
 'hello'
 })
}

所以这是组件定义中的一个选项,相对于提供一个 template 选项,在 Vue 2 中你可以为组件提供一个渲染函数,你会得到 h 参数,直接作为渲染函数的参数。你可以用它来创造我们称之为虚拟DOM节点,简称 vnode。

vnode 接受三个参数:

  • 第一个参数是类型,所以我们在这里创建一个 div。
  • 第二个参数是一个对象包含 vnode 上的所有数据或属性,API有点冗长从某种意义上说,你必须指明传递给节点的绑定类型。例如,如果要绑定属性你必须把它嵌套在attrs对象下如果要绑定事件侦听器你得把它列在 on 下面。
  • 第三个参数是这个 vnode 的子节点。所以直接传递一个字符串是一个方便的 API,表明此节点只包含文本子节点,但它也可以是包含更多子节点的数组。所以你可以在这里有一个数组并且嵌套了更多的嵌套 h 调用。

在Vue 3中我们改变了API,目标是简化它。

import { h } from 'vue'
 
render () {
 return h(
 'div', 
 {
 id: 'foo',
 onClick: this.onClick
 },
 'hello'
 })
}

第一个显著的变化是我们现在有了一个扁平的 props 结构。当你调用 h 时,第二个参数现在总是一个扁平的对象。你可以直接给它传递一个属性,这里我们只是给它一个 ID。按惯例监听器以 on 开头,所以任何带 on 的都会自动绑定为一个监听器所以你不必考虑太多嵌套的问题。

在大多数情况下,你也不需要思考是应将其作为 attribute 绑定还是DOM属性绑定,因为 Vue 将智能地找出为你做这件事的最好方法。我们检查这个 key 是否作为属性存在在原生 DOM 中。如果存在,我们会将其设置为 property,如果它不存在,我们将它设置为一个attribute。

render API 的另一项改动是 h helper 现在是直接从 Vue 本身全局导入的。一些用户在 Vue 2 中因为 h 在这里传递而在这里面 h 又很特别,因为它绑定到当前组件实例。当你想拆分一个大的渲染函数时,你必须把这个 h 函数一路传递给这些分割函数。所以,这有点困难,但有了全局引入的 h 你导入一次就可以分割你的渲染函数,在同一个文件里分割多少个都行。

渲染函数不再有 h 参数了,在内部它确实接收参数,但这只是编译器使用的用来生成代码。当用户直接使用时,他们不需要这个参数。所以,如果你用 TypeScript 使用定义的组件 API 你也会得到 this 的完整类型推断。

Q&A

1.我知道原始的那种虚拟 Dom 的实现得到了启发来自其他项目对吗?

是的有一个库叫snabbdomVue 2基本上就是从这个库中分离出来的。

2.好的然后是Vue 3,你在这里的编码方式只是改进了Vue 2的模式吗?

好吧,Vue 3是一个彻底的重写,几乎从头开始一切都是定制的显然,有现有的算法看起来像没有变化,因为这些是我们看到社区在做广泛研究的领域所以这是建立在所有这些以前的实现的基础上的但代码本身现在是从头开始。

3.都是用TypeScript写的,对吧?

是的,都是 TypeScript 写的。

何时/如何使用 render 函数

看看渲染函数在 Vue 中是什么样子。在 Vue 2 中,一个传统的 Vue 组件,有一个 template 选项,但是为了重用渲染函数我们可以用一个名为 render 的函数来代替它,我们会通过参数得到这个称为 h(hyperscript)。但在这里,我们只是示范一下我们如何在 Vue 3 中使用它。我们会从 vue 导入 h,我们可以用它来返回 h。

import { h } from 'vue'
 
const App = {
 render () {
 return h('div') 
 }
}
 
// 等效模板中的普通 div

1.所以它返回 div 的 JavaScript 对象表示?

完全正确。

2.那么,你的虚拟dom就像…编译器?是编译器接收它吗?

是渲染器,渲染器接收它。

3.然后它实际上进行 dom 调用将其带入浏览器?

完全正确。

所以我们可以给这个虚拟节点一些 props,

import { h } from 'vue'
 
const App = {
 render () {
 return h(
 'div',
 {
 id: 'hello'
 },
 [
 h('span','world')
 ]
 ) 
 }
}
 
// <div id="hello"><span>world</span></div>

现在,我们知道如何生成静态结构。但是当人们第一次使用 render 函数会问 “我该怎么写,比如说,v-if 或者 v-for”?我们没有像 v-if 或者类似的东西。相反,您可以直接使用 JavaScript。

import { h } from 'vue'
 
const App = {
 render () {
 return this.ok
 ? h('div',{ id: 'hello' },[h('span','world')]
 : h('p', 'other branch')
 ) 
 }
}

如果 ok 的值为 true,它将呈现 div,反之,它将呈现 p。同样,如果你想做 v-else-if 你需要嵌套这个三元表达式:

import { h } from 'vue'
 
const App = {
 render () {
 return this.ok
 ? h('div',{ id: 'hello' },[h('span','world')]
 : this.otherCondition
 ? h('p', 'other branch')
 : h('span')
 ) 
 }
}

我想你可能会喜欢创建一个变量,将不同的节点添加到该变量。所以当你不得不将这整个东西嵌套在一个表达式调用中这会很有用,但你不必这么做。

import { h } from 'vue'
 
let nodeToReturn
if(this.ok) {
 nodeToReturn = ...
} else if () {
 
}
 
const App = {
 render () {
 return this.ok
 ? h('div',{ id: 'hello' },[h('span','world')]
 : this.otherCondition
 ? h('p', 'other branch')
 : h('span')
 ) 
 }
}

这就是 JavaScript 灵活的地方,这看起来更像普通的 JavaScript。当你的代码变得更加复杂时您可以使用普通的 JavaScript 重构技巧使它们更容易理解。

我们讨论了 v-if, 接下来看看 v-for。 类似的,你也可以给它们加上 key,这是渲染函数中的渲染列表。

import { h } from 'vue'
 
const App = {
 render () {
 return this.list.map(item => {
 return h('div', {key: item.id}, item.text)
 })) 
 }
}

在渲染函数中,您可能要处理插槽。当你写一个重标记组件(markup heavy component),或者我更喜欢称之为特性组件(feature component),它与你的应用程序的外观布局结构有关,将实际的 HTML 显示给用户。对于那些类型的组件,我更喜欢始终使用模板。只有在我必须使用渲染函数的时候,比如我在写一些功能型的组件,有时会期望获取一些插槽内容,将其打包或者以某种方式操纵他们。在 Vue 3 里默认插槽将暴露在这个 this.$slot.default。如果对于组件什么都没有提供,这将是 undefined,所以你得先检查一下它的存在。如果它存在,它将永远是一个数组。有了作用域槽,我们可以将 props 传递给作用域槽,所以把数据传递到作用域槽只是通过传递一个参数到这个函数调用中。因为这是一个数组你可以将它直接放在 children 位置。

import { h } from 'vue'
 
const App = {
 render () {
 const slot = this.$slot.default
 ? this.$slot.default()
 : []
 
 return h('div', slot)
 }
}

你可以在 render 函数中用插槽做一件很强大的事,比如以某种方式操纵插槽,因为它只是一个 JavaScript 对象数组,你可以用 map 遍历它。

import { h } from 'vue'
 
const App = {
 render () {
 const slot = this.$slot.default
 ? this.$slot.default()
 : []
 
 slot.map(vnode => {
 return h('div', [vnode])
 })
 }
}

这里有一个例子,截住并更改插槽数据。假设我们有一个堆栈组件(tack component),在一些用户界面库(UI libraries)中很常见。你可以传递很多属性给它,得到嵌套的堆栈渲染结果,有点像 HTML 中 ulol 的默认样式。

<Stack size="4">
 <div>hello</div>
 <Stack size="4">
 <div>hello</div>
 <div>hello</div>
 </Stack>
</Stack>

渲染成这样:

<div class="stack">
 <div class="mt-4">
 <div>hello</div>
 </div>
 <div class="mt-4">
 <div class="stack">
 <div class="mt-4">
 <div>hello</div>
 </div>
 </div>
 </div>
</div>

这里有一个普通的基于模板的语法,在同一个插槽内它们都是默认插槽,你能做的只有渲染这个部分,在模板很难实现。但是你可以用渲染函数来实现,程序化的遍历插槽内的每个项目然后把它们变成别的东西。

import { h } from 'vue'
 
const Stack = {
 render () {
 const slot = this.$slots.default
 ? this.$slots.default()
 : []
 
 return h(
 'div',
 {class: 'stack'},
 slot.map(child => {
 return h(
  'div', 
  {class: `mt-${this.$props.size}`},
  [child]
  )
 })
 )
 }
}

我们用 slot.map 生成新的 vnode 列表,原来的子插槽被包装在里面。有了这个,我们把它放到一个 stack.html 文件里。

stack.html

<script src="https://unpkg.com/vue@next"></script>
<style>
 .mt-4 {
 margin: 10px
 }
</style>
 
 
<div id="app"></div>
 
<script>
 const { h, createApp } = Vue
 
 const Stack = {
 render() {
 const slot = this.$slots.default
 ? this.$slots.default()
 : []
 
 return h(
 'div',
 { class: 'stack' },
 slot.map(child => {
  return h('div', { class: `mt-${this.$attrs.size}` }, [child])
  // this.$props.size ?
 })
 )
 },
 }
 
 const App = {
 components: {
 Stack
 },
 template: `
 <Stack size="4">
 <div>hello</div>
 <Stack size="4">
 <div>hello</div>
 <div>hello</div>
 </Stack>
 </Stack>
 `
 }
 
 createApp(App).mount('#app')
</script>

何时/使用 Vue3 render 函数的教程详解

当你创作这些底层的公用设施组件,有时真的会遇到麻烦,这时渲染函数更有效。但话说回来,也需要了解每种方法的利弊,这些是为了让你更好地理解在什么情况下应该使用模板或使用渲染函数。基本上是当你用一个模板时遇到限制时,比如你就像我们刚才看到的那样,可能改为使用渲染函数会更有效。当你意识到想表达的逻辑用 JavaScript 更容易而不是使用模板语法时就使用它。从我的经验来看,这种情况在您创作可重用的功能组件,要跨多个应用程序共享或者在组织内部共享时更常见。在日常开发中你主要是在编写特性组件,模板通常是有效的方式,模板的好处是更简单,当你有很多标记的时候会通过编译器优化,它的另一个好处是它更容易让设计师接管组件并用CSS设计样式。因此,Vue 提供了这两个选项,当情况出现的时候以便您可以选择合适的方式。

到此这篇关于何时/使用 Vue3 render 函数的教程详解的文章就介绍到这了,更多相关Vue3 render 函数内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Jquery图形报表插件 jqplot简介及参数详解
Oct 10 Javascript
开发 Internet Explorer 右键功能表(ContextMenu)
Jul 03 Javascript
浅谈javascript函数式编程
Sep 06 Javascript
JS实现带有3D立体感的银灰色竖排折叠菜单代码
Oct 20 Javascript
一道常被人轻视的web前端常见面试题(JS)
Feb 15 Javascript
JavaScript canvas实现围绕旋转动画
Nov 18 Javascript
浅谈Angular HttpClient简单入门
May 04 Javascript
浅谈Node.js 沙箱环境
May 15 Javascript
快速搭建Node.js(Express)用户注册、登录以及授权的方法
May 09 Javascript
Vue中的组件及路由使用实例代码详解
May 22 Javascript
Vue项目接入Paypal实现示例详解
Jun 04 Javascript
js实现点击上传图片并设为模糊背景
Aug 02 Javascript
全面解析JavaScript Module模式
Jul 24 #Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
Jul 24 #Javascript
在Vuex中Mutations修改状态操作
Jul 24 #Javascript
Vue自动构建发布脚本的方法示例
Jul 24 #Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
Jul 24 #Javascript
vue data对象重新赋值无效(未更改)的解决方式
Jul 24 #Javascript
VUE项目axios请求头更改Content-Type操作
Jul 24 #Javascript
You might like
PHP几个数学计算的内部函数学习整理
2011/08/06 PHP
探讨如何在php168_cms中提取验证码
2013/06/08 PHP
php调用C代码的实现方法
2014/03/11 PHP
php获取英文姓名首字母的方法
2015/07/13 PHP
ecshop添加菜单及权限分配问题
2017/11/21 PHP
PHP文件打开关闭及读写操作示例解析
2020/08/06 PHP
javascript 装载iframe子页面,自适应高度
2009/03/20 Javascript
js左侧多级菜单动态的解决方案
2010/02/01 Javascript
页面只能打开一次Cooike如何实现
2012/12/04 Javascript
微信小程序 animation API详解及实例代码
2016/10/08 Javascript
AJAX和jQuery动态加载数据的实现方法
2016/12/05 Javascript
javascript数组去重方法分析
2016/12/15 Javascript
如何实现星星评价(jquery.raty.js插件)
2016/12/21 Javascript
JS实现一个简单的日历
2017/02/22 Javascript
Node.js常用工具之util模块
2017/03/09 Javascript
详解vue静态资源打包中的坑与解决方案
2018/02/05 Javascript
nodejs中express入门和基础知识点学习
2018/09/13 NodeJs
jquery的$().each和$.each的区别
2019/01/18 jQuery
vue移动端写的拖拽功能示例代码
2020/09/09 Javascript
Python 遍历子文件和所有子文件夹的代码实例
2016/12/21 Python
使用Python设计一个代码统计工具
2018/04/04 Python
Python查找第n个子串的技巧分享
2018/06/27 Python
python实现换位加密算法的示例
2018/10/14 Python
在Qt中正确的设置窗体的背景图片的几种方法总结
2019/06/19 Python
python实现超市管理系统(后台管理)
2019/10/25 Python
Python使用requests xpath 并开启多线程爬取西刺代理ip实例
2020/03/06 Python
pyspark给dataframe增加新的一列的实现示例
2020/04/24 Python
浅析Python打包时包含静态文件处理方法
2021/01/15 Python
python Protobuf定义消息类型知识点讲解
2021/03/02 Python
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
CSS3模拟IOS滑动开关效果
2016/09/28 HTML / CSS
小学生安全演讲稿
2014/04/25 职场文书
诚信的演讲稿范文
2014/05/12 职场文书
护士医德考评自我评价
2015/03/03 职场文书
团干部培训班心得体会
2016/01/06 职场文书
中国现代文学之经典散文三篇
2019/09/18 职场文书