何时使⽤Vue3render函数的教程详解
什么是 DOM?
如果我们把这个 HTML 加载到浏览器中,浏览器创建这些节点,⽤来显⽰⽹页。所以这个HTML映射到⼀系列DOM节点,然后我们可以使⽤JavaScript进⾏操作。例如:
let item = ElementByTagName('h1')[0]
VDOM
⽹页可以有很多DOM节点,这意味着DOM树可以有数千个节点。这就是为什么我们有像Vue这样的框架,帮我们⼲这些重活⼉,并进⾏⼤量的JavaScript调⽤。
然⽽,搜索和更新数千个DOM节点很明显会变慢。这就是Vue和其他类似框架有⼀种叫做虚拟DOM的东西。虚拟DOM是表⽰DOM的⼀种⽅式。例如,这个HTML也可以通过⼀个虚拟节点来表⽰,看起来像这样。如您所见,它只是⼀个JavaScript对象。
<div>Hello</div>
{
tag: 'div',
children: [
{
text: 'Hello'
}
]
}
Vue知道如何使⽤此虚拟节点并挂载到DOM上,它会更新我们在浏览器中看到的内容。实际上还有⼀个步骤其中,Vue基于我们的模板创建⼀个渲染函数,返回⼀个虚拟DOM节点。
渲染函数可以是这样的:
render(h) {
return h('div', 'hello')
}
当组件更改时,Render函数将重新运⾏,它将创建另⼀个虚拟节点。然后发送旧的 VNode 和新的 VNode 到Vue中进⾏⽐较并以最⾼效的⽅式在我们的⽹页上更新。
我们可以将虚拟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: Click
},
'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: Click
},
'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')]
: herCondition
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')]
: herCondition
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}, )
}))
}
}
在渲染函数中,您可能要处理插槽。当你写⼀个重标记组件(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 = {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论