vue中的h渲染函数运用与分析
前言
在一般情况下,我们在
vue
中,推荐使用template
模版语法来创建组件,然后在某些场景下,我们需要使用js完全的coding能力(也就是以js函数的思维来编写组件),这个时候就可以使用渲染函数了
关于渲染函数,官方的描述已经讲解得比较详细了,具体见 渲染函数 的详细说明!
本文将从底层的角度来分析何为vnode
、h()
函数的使用方式、分析关于h
函数的执行过程,以及扩展一下思维,了解关于什么是h高阶组件
vnode虚拟节点
vue
提供了一个h()
函数,用于创建vnode
,也就是说
1 | const vnode = h('div', {}, []) |
🤔 那么,什么是
vnode
?为什么要生成vnode
?对于vnode
应该如何使用?
👉 关于vnode的相关知识点,可以查看之前的一篇文章:vm实例如何渲染
⭐ 简而言之,vnode
是一个用于描述用户界面结构的JavaScript对象
简而言之,如下图所示:
👉 将一个组件渲染成vnode
,然后通过对比vnode
的不同,形成最终待更新界面的vnode
,然后只更新需要更新的dom节点,其工作流程如下图所示:
h()函数的使用方式
用来创建
vnode
虚拟节点的统一方法APIh()
函数更准确地说,应该是createVnode()
,只不过经常使用,因此使用h()
来替代,关于这个h()
函数的签名如下:
1 | function h( |
⭐ 关于上述三个参数的意义代表如下:
- type: 可以是html标签、组件、函数;
- props: 一个包含属性的对象,可以是null;
- children: 可以是字符串、数组、对象,代表的子节点
关于
h()
函数的使用方式有很多,以下是对应的使用场景(这边结合源码的方式来阐述)
在开始罗列相关的使用方式之前,先整理一下基础的成员类型
1 | // *********** 基础定义 *********** |
👇 关于每个h函数都是通过重载的方式来实现不同的调用方式!
渲染普通的html标签
1 | // 源码定义 |
渲染自定义element标签
1 | // 源码定义 |
渲染文本或者注释
1 | export const Text = Symbol.for('v-txt') |
渲染fragment
🤔 这里引入
fragment
是因为在vue3
中可以在template
模版中无需再嵌套一个公共的父容器节点,可以直接在template下嵌套多个孩子节点的情况,这个情况,其实就是在template
模版中隐式的使用一个fragment
来包裹template
下的孩子节点,使用方式如下:
1 | <template> |
在vue-tool
开发工具中查看,结果如下:
🥸 在template
中包含了两个根级别的div元素,它们被渲染为同一级别的兄弟元素,而且没有任何额外的包裹元素,且在vue-tool
视图下可见被fragment
包裹其中
在
vue3
中,Fragment
是一个特殊的类型,用于在模版中返回多个元素而不需要将多个元素包裹在一个额外的DOM元素中
1 | // Fragment是一个可以被new构造调用的,带有__isFragment=true属性的,接收$props=VNodeProps属性的函数 |
这边再次根据上述的例子,进行h()
调用并输出对应的vnode
,结果如下:
🥸 我们的确可以看到在输出的vnode
中,拥有这个__isFragment
、Symbol(v-fgt)
属性
将这个fragment
延伸至JSX,完整的例子如下:
fragment在h()函数中的使用
1 | // 源码定义 |
渲染teleport
官方Teleport介绍👉 用于将一个组件内部的一部分模版丢到该组件的DOM结构外侧的位置上!
渲染suspense
Suspense
,从 官方Suspense文档 来看,就是一个用于控制异步加载的高阶组件,当 🈶 异步的setup()
时,将会触发对应的插槽钩子组件作为展示,然后再在加载完毕时,才展示真正的组件!
其代码定义如下:
1 | declare var __FEATURE_SUSPENSE__: boolean |
🌟 从上述的类型定义,我们可以看出这个Suspense
类型是一个可以被new constructor()
调用,并返回一个带有$props, $slots: {default, fallback}
属性的对象!
渲染函数式组件
函数式组件是一种定义自身没有任何状态的组件的方式,就像纯函数一样(只要接收参数,返回结果一般是一样的),接收
props
,返回vnodes
,在渲染过程中不会创建组件实例,也不会出发常规的组件生命周期钩子, 👉 可以认为就是一个用来创建静态html(vnode在底层转html)的方法,也就是渲染函数!
由于没有this
关键词的引用,因此Vue
会把props
当作第一个参数传入,其方法定义如下:
1 | function MyComponent(props, { attrs, emit, slots }){} |
⚠ 除了props
以及emitss
,普通常规组件所使用的配置选项在函数式组件中不可用,但是我们可以通过 👇 的方式来给一个函数式组件添加对应的属性来进行声明:
1 | MyComponent.props = ['value'] |
⭐ 而且,如果没有在上述将对应的props给定义出来的话,那么传递给到这个函数式组件的相关属性都被作为普通的attr属性来使用了!
h高阶组件函数的运用-defineComponent
defineComponent
方法是vue3
用于定义组件的函数,它通过一个对象(或者一个返回对象的函数)来描述组件的选项,返回一个组件选项对象,这个方法不仅增强了TypeScript
中的类型推断,还明确了组件的意图
关于这个方法的简单介绍,具体可以见我的另一篇文章以及官网的介绍
这里主要分析一下关于官方并没有提及到的:
defineComponent拥有两种调用方式:
1
2
3
4
5
6
7
8
9
10
11// 方式一:直接传递一个setup函数以及一个包含props、emits、slots属性的对象
defineComponent(
(props, ctx) => {}, // setup函数
options: {props, emits, slots}
)
// 方式二:直接传递一个options对象,该对象包含方式一中的所有参数
defindComponent(
{
// 可以是vue2+中的ComponentOptions以及setup函数
}
)👉 其实这里底层都是传递的当前对象,也就是当前对象options被直接返回!
如果在defineComponent中options中同时定义了
data
以及setup
函数,依然可以正常运行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { defineComponent, ref } from 'vue'
export default defineComponent({
data() {
return {
message: '你好'
}
},
setup() {
const count = ref(0)
function increment() {
count.value ++
}
return { count, increment }
}
})🌟 这个例子中
message
以及count
、increment
,Vue
会自动处理它们的合并以及响应式,但是 🈶 两个需要注意的地方:
+. 执行时机:setup
函数会在组件的beforeCreate
钩子之后、created
钩子之前执行,因此,在setup
中定义响应式数据会早于data
中的数据准备就绪;
+. 合并行为:Vue
会自动合并setup
与data
以及methods
等返回的响应式状态,如果出现重名,则setup
函数返回的属性将会覆盖到选项中的同名响应式状态。
💯 因此为避免出现覆盖的情况,建议在同个组件中尽量保持一致,避免混用两种风格!!
- setup除了可以返回一个响应式变量组成的对象,也可以返回一个渲染函数👉 返回一个渲染函数将会阻止返回其他,也就是说将会忽略
1
2
3
4
5
6
7import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}template
模版,以及render
渲染函数,这将允许我们使用Composition API
时从这个setup
方法中返回的渲染函数来直接使用,也就是高阶组件的由来🌟 这里我们通过一个1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// useXXX.ts
import {h} from 'vue'
export function useXXX(params){
return defineComponent({
setup(){
return h('span', params)
}
})
}
// demo.vue
const XXX = useXXX('我是组件内容')
<template>
<XXX></XXX>
</template>useXXX()
方法来创建一个XXX组件,并直接在界面上使用,如果span替换为其他自定义组件,然后接收其他的一些参数,那么就可以实现高阶组件的效果!
👉 这个setup
函数还可以返回一个渲染函数,也就是h
函数的引用,因为在vue
中将会使用这个h
函数来创建一个vnode
组件,这与直接通过template➕对象
来创建一个vnode
的方式,是一致的,在defineComponent
方法中,返回一个渲染函数,将会替代掉原本使用template与options
的其他属性,因为返回一个渲染函数就h
是当前组件的最终结果了,执行结果是一个vnode
对象,与使用template以及options对象的目标是一致的,而且效率可能还更高!!
因为这个defineComponent()
的实现如下:
1 | // src/.../apiDefineComponent.ts 第301行 |