何时/使用 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 相关文章推荐
JavaScript 创建对象和构造类实现代码
Jul 30 Javascript
超级酷和最实用的jQuery实例收集(20个)
Apr 21 Javascript
仅IE9/10同时支持script元素的onload和onreadystatechange事件分析
Apr 27 Javascript
jquery改变disabled的boolean状态的三种方法
Dec 13 Javascript
jquery简单图片切换显示效果实现方法
Jan 14 Javascript
基于javascript实现动态显示当前系统时间
Jan 28 Javascript
详解JavaScript的另类写法
Apr 11 Javascript
实例解析jQuery中如何取消后续执行内容
Dec 01 Javascript
基于jQuery实现滚动切换效果
Dec 02 Javascript
JavaScript队列的应用实例详解【经典数据结构】
Apr 12 Javascript
React+react-dropzone+node.js实现图片上传的示例代码
Aug 23 Javascript
前端MVVM框架解析之双向绑定
Jan 24 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隐形一句话后门,和ThinkPHP框架加密码程序(base64_decode)
2011/11/02 PHP
ThinkPHP2.0读取MSSQL提示Incorrect syntax near the keyword 'AS'的解决方法
2014/06/25 PHP
解决PHP使用CURL发送GET请求时传递参数的问题
2019/10/11 PHP
PHP unset函数原理及使用方法解析
2020/08/14 PHP
php提高脚本性能的4个技巧
2020/08/18 PHP
学习JS面向对象成果 借国庆发布个最新作品与大家交流
2009/10/03 Javascript
xss文件页面内容读取(解决)
2010/11/28 Javascript
基于jQuery的图片左右无缝滚动插件
2012/05/23 Javascript
Javascript计算两个marker之间的距离(Google Map V3)
2013/04/26 Javascript
关于js遍历表格的实例
2013/07/10 Javascript
javascript:void(0)的问题使用探讨
2014/04/10 Javascript
JavaScript返回0-1之间随机数的方法
2015/04/06 Javascript
JS响应鼠标点击实现两个滑块区间拖动效果
2015/10/26 Javascript
js中json处理总结之JSON.parse
2016/10/14 Javascript
JS如何设置iOS中微信浏览器的title
2016/11/22 Javascript
Javascript中常用类型的格式化方法小结
2016/12/26 Javascript
利用Angular+Angular-Ui实现分页(代码加简单)
2017/03/10 Javascript
nodejs个人博客开发第二步 入口文件
2017/04/12 NodeJs
浅谈Node.js ORM框架Sequlize之表间关系
2017/07/24 Javascript
js 倒计时(高效率服务器时间同步)
2017/09/12 Javascript
vue项目中使用tinymce编辑器的步骤详解
2018/09/11 Javascript
[01:49]一目了然!DOTA2DotA快捷操作对比第二弹
2014/05/16 DOTA
[03:01]完美世界DOTA2联赛PWL S2 集锦第二期
2020/12/03 DOTA
python读写ini文件示例(python读写文件)
2014/03/25 Python
python写的ARP攻击代码实例
2014/06/04 Python
Python实现爬取逐浪小说的方法
2015/07/07 Python
Python数据结构与算法之列表(链表,linked list)简单实现
2017/10/30 Python
Python 绘制酷炫的三维图步骤详解
2019/07/12 Python
详解Python爬虫爬取博客园问题列表所有的问题
2021/01/18 Python
详解canvas绘制网络字体几种方法
2019/08/27 HTML / CSS
英国家庭家具、照明和花园家具购物网站:Furniture123
2018/12/31 全球购物
护士个人自我鉴定
2014/03/24 职场文书
乡镇务虚会发言材料
2014/10/20 职场文书
审查起诉阶段律师意见书
2015/05/19 职场文书
2016大学生求职自荐信范文
2016/01/28 职场文书
python垃圾回收机制原理分析
2022/04/13 Python