目录
- 一、认识 reactive 和 ref
- 二、isRef 和 unref
- 三、toRef 和 toRefs
- 四、toRef、toRefs、toValue、toRaw 的用法
- 五、toRaw、markRaw
- 六、shallowRef、shallowReactive、shallowReadonly
- 七、triggerRef、customRef
- 八、effectScope、getCurrentScope、onScopeDispose
- 九、isProxy、isReactive 和 isReadonly
- 十、h() 函数
- 十一、isVNode()
- 十二、cloneVNode()
- 十三、mergeProps() 函数
- 十四、resolveComponent() 和 resolveDirective()
- 十五、withDirectives() 和 withModifiers()
一、认识 reactive 和 ref
使用 Vue 的响应式系统的 “最佳实践” 是——**仅使用你声明对象的代理版本(Proxy)。
在定义一个变量时,何时需要使用 ref 或 reactive 呢?这需要根据“该变量的值”的情况而定:
- 若该变量的值是“非响应式”的值,此时直接将其定义为普通变量即可,无需使用 reactive 或 ref。
- 若该变量的值是“响应式”的“基本类型”值,此时需要使用 ref 定义它。
- 若该变量的值是“响应式”的“引用类型”值,此时推荐使用 reactive 定义它,当然也可以使用 ref 定义它。
1、reactive
reactive 的特性:
- reactive 可以定义响应式 “对象”——指使用
new
关键字创建的 Object、Array、String、Number、 Map、Set 等引用类型的实例对象——因为reactive创建的响应式对象是通过 Proxy 来实现的,所以传入数据不能为基础类型。 - reactive 接收一个对象类型参数,返回一个具有响应式状态的副本。
- 使用 reactive 定义的属性可以直接使用,不需要加 .value。
- 从reactive()返回的代理对象与原始对象是不一样的。用
===
操作符进行比较会返回 false。reactive 会对传入的引用类型进行包裹,创建一个该对象的 Proxy 代理。它是源对象的响应式副本,不等于原始对象。它“深层”转换了源对象的所有嵌套 property,解包并维持其中的任何 ref 引用关系。 - reactive 的局限性:
- 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
- 不能替换整个对象:当重新赋值 reactive 的值时,新的对象会覆盖对原始对象的引用,并且响应式连接会丢失。
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接。不要直接解构使用 reactive 定义的响应式对象,否则会造成该对象脱离 ref 响应式。需要用 toRefs 将其转化为响应式数据对象,然后再解构返回。
- reactive 是基于 Proxy的具体使用方式,通过和Reflect 的配合, 就能实现对于对象的拦截。
// 从reactive()返回的代理对象与原始对象是不一样的。用===操作符进行比较会返回false。
const a = {}
const proxy = reactive(a)
console.log(proxy === a) // false
// 你可以首先使用toRefs将对象的所有属性转换为响应式的,然后你可以解构对象而不丢失响应
let state = reactive({
count: 0,
})
const { count } = toRefs(state)
2、ref
为了兼容实现非对象类型值的响应性,vue3 提供了 ref。
ref 其实是 reactive 的扩展语法糖。ref 的实现逻辑是:由于 reactive 只能使 “对象类型” 的值具有响应性,所以 vue 的团队通过将 “[基本类型值].value
” 的形式将 “基本类型” 人为的构造转换成了 “对象类型”。
ref 的特性:
- ref 可以定义任意类型的响应式数据,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
- ref 会使它的值具有深层响应性。
- ref 定义的变量,改变值要 .value,而在 template 中不用写 .value;当你在模板上调用ref时,Vue会自动使用unref()进行解包。这样,你永远不需要在模板中使用.value进行访问。
- 一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。
- 在 template 模板渲染中,只有顶级的 ref 属性(文本插值的最终计算值,即
{{ }}
标签)才会被解包。 - 若直接对 ref 进行解构,则此 ref 变量会被解包丢失响应性;若将 ref 作为普通的 JavaScript 对象的子元素的值被访问,则此 ref 变量不会被解包,它依然具有响应性。
- ref 可以被传递到函数中而不丢失响应式。
- 对于基本数据类型,ref 基于 Object.defineProperty() 的响应式,通过给 .value 属性设置 setter 和 getter 实现数据劫持,但它的原始数据不会发生更改,且性能优于 reactive;而对于引用类型,ref 仍然是通过 reactive 包装实现的。
// 也不能对用ref()创建的响应式对象进行解构。这也会导致响应式丢失。但是,如果将ref分组在一个普通的JavaScript对象中,就不会丢失响应式。
import { ref } from 'vue'
const count = ref(0)
const countValue = count.value // ⚠️ disconnects reactivity
const { value: countDestructured } = count
const state = {
count: ref(1),
name: ref('Michael'),
}
const { count, name } = state // still reactive
在使用ref时到处使用.value可能很麻烦,但我们可以使用 unref 函数。如果unref()的参数是一个ref,就会返回其内部值。否则就返回参数本身。这是的val = isRef(val) ? val.value : val语法糖。
import { ref, unref } from 'vue'
const count = ref(0)
const unwrappedCount = unref(count)
// same as isRef(count) ? count.value : count`
推荐的模式是在一个reactive对象中对ref分组:
const loading = ref(true)
const error = ref(null)
const state = reactive({
loading,
error,
})
// You can watch the reactive object...
watchEffect(() => console.log(state.loading))
// ...and the ref directly
watch(loading, () => console.log('loading has changed'))
setTimeout(() => {
loading.value = false
// Triggers both watchers
}, 500)
如果你不需要state对象本身的响应式,你可以在一个普通的JavaScript对象中进行分组。 对 refs 进行分组的结果是一个单一的对象,它更容易处理,并使你的代码保持有序。你可以看到分组后的 refs 属于一起,并且是相关的。
【拓展】
也可以通过 shallow ref 来放弃深层响应性。对于浅层 ref,只有 .value 的访问会被追踪。浅层 ref 可以用于避免对大型数据的响应性开销来优化性能、或者有外部库管理其内部状态的情况。具体请参阅:
3、使用 reactive 和 ref 定义变量
(1)、使用 ref
①、在 setup() 函数中使用 ref
对于在 setup 中手动返回的响应式数据,在 template 中使用时无需加 .value
例如:
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup(props) {
const count = ref(0)
// 暴露给 template 的属性,可以直接在 template 中使用
return {
count
}
}
}
</script>
②、在 <script setup> 标签里使用 ref
如果将 setup 写在 <script> 标签里,则该标签里的脚本都是执行在 setup 里的,并且里面声明数据均会默认地暴露给 template,在 template 中使用时无需加 .value
。
例如:
<template>
<div>{{ name }} : {{ state.age }}</div>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('Marry')
const list = ref([])
const state = ref({
age: 18
})
console.log(name.value)
console.log(list.value)
console.log(state.value.age)
</script>
(2)、使用 reactive
例如:
<template>
<input type="number" v-model="count" />
<div>{{ count }}</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const state = reactive({
count: 100
})
// 使用 reactive 定义的属性可以直接使用,不需要加 .value
console.log(state.count)
return {
...toRefs(state) // 需要先用 toRefs 将其转化为响应式数据对象,然后再解构返回
}
}
}
</script>
以下案例中 reactive 的响应式会丢失:
- 响应式丢失1:
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
- 响应式丢失2:
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
(3)、看下 reactive 和 ref 定义的变量在控制台输出的数据结构的对比
// 使用 reactive 定义的变量
const obj1 = reactive({ count1: 0 })
console.log('obj1: ', obj1);
// 使用 ref 定义的变量
const obj2 = ref({ count2: 9 })
console.log('obj2: ', obj2);
输出结果如下:
4、reactive 和 ref 的源码解读
(1)、reactive 源码
// 创建reactive对象
export function reactive(target: object) {
// 如果reactive进入的是readonly的话直接返回,保持只读
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 创建reactive对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
(2)、ref 源码
// 是否是ref根据属性的__v_isRef决定
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}
export function ref(value?: unknown) {
return createRef(value, false)
}
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
// 创建ref对象,传入raw和是否是shallow
function createRef(rawValue: unknown, shallow: boolean) {
// 如果之前时ref则直接返回
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// Ref对象类
class RefImpl<T> {
// 存放 reactive(raw) 后的proxy
private _value: T
// 存放 raw
private _rawValue: T
// 建立与effect的关系
public dep?: Dep = undefined
// 是否ref的标识
public readonly __v_isRef = true
// 构造,传入raw 和 shallow
constructor(value: T, public readonly _shallow: boolean) {
// 存储 raw
this._rawValue = _shallow ? value : toRaw(value)
// 如果是不是shallow则 存储 reactive proxy 否则存储传入参数
this._value = _shallow ? value : toReactive(value)
}
// getter value拦截器
get value() {
// track Ref 收集依赖
trackRefValue(this)
return this._value
}
// setter value拦截器
set value(newVal) {
// 如果是需要深度响应的则获取 入参的raw
newVal = this._shallow ? newVal : toRaw(newVal)
// 查看要设置值是否与当前值是否修改
if (hasChanged(newVal, this._rawValue)) {
// 存储新的 raw
this._rawValue = newVal
// 更新value 如果是深入创建的还需要转化为reactive代理
this._value = this._shallow ? newVal : toReactive(newVal)
// 触发value,更新关联的effect
triggerRefValue(this, newVal)
}
}
}
// 将对象转化为reactive
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
二、isRef 和 unref
- isRef:检查某个值是否为 ref。
- unref:如果参数是 ref,则返回内部值,否则返回参数本身。这是 isRef 的一个变相语法糖。
三、toRef 和 toRefs
1、toRef——将一个 reactive 转化为一个 ref
- toRef 能够将 reactive 定义的对象的某个属性,转为一个 ref,且保持响应式两者的引用关系。
- 接收两个参数:源响应式对象 和 属性名,返回一个 ref 数据。
- 获取数据值的时候需要加
.value
。 - 使用 toRef 转化后生成的 ref 数据如果是引用类型数据时,那么它不是原始数据的拷贝,而是原始数据的引用,改变它的数据也会同时改变原始数据。
例如:在 setup 中使用父组件传递的 props 数据时,要引用 props 的某个属性,且要保持响应式连接,就必须使用 toRef。
<template>
<div>{{ myTitle }}</div>
</template>
<script>
import { defineComponent, toRef } from 'vue'
export default defineComponent({
props: [title],
setup (props) {
// 创建变量myTitle
const myTitle = toRef(props, 'title')
console.log(myTitle.value)
return {
myTitle
}
}
})
</script>
2、toRefs——将多个 reactive 自动解构为多个 ref
- toRefs 用于将响应式对象转换为普通对象,其中普通对象的每个属性都是指向原始对象相应属性的 ref,两者保持引用关系。
- toRefs 常用于 ES6 的解构赋值操作。但是,对一个响应式对象直接解构时,解构后的数据将不再有响应式,而使用 toRefs 可以方便解决这个问题。
- 获取数据值的时候需要加
.value
。 - 使用 toRefs 转化后生成的 ref 数据如果是引用类型数据时,那么它不是原始数据的拷贝,而是原始数据的引用,改变它的数据也会同时改变原始数据。
- 其作用和 toRef 类似,只不过 toRef 是对一个个属性手动赋值,而 toRefs 是自动解构赋值。
<template>
<div>{{ myTitle }}</div>
</template>
<script>
import { defineComponent, toRefs } from 'vue'
export default defineComponent({
props: [title],
setup (props) {
// toRefs 默认使用了解构赋值,创建了变量 myTitle
const { myTitle } = toRefs(props)
console.log(myTitle.value)
return {
myTitle
}
}
})
</script>
你也可以这样使用—— reactive
+ toRefs
联用:
<template>
<div>{{ name }}</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const obj = reactive({ name: 'Marry' })
return {
...toRefs(obj)
}
}
}
</script>
四、toRef、toRefs、toValue、toRaw 的用法
- toRef:可以将值、refs 或 getters 规范化为 refs 。也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。(详见上文)
- toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。(详见上文)
- toValue:将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。这可以在组合式函数中使用,用来规范化一个可以是值、ref 或 getter 的参数。
- toRaw:根据一个 Vue 创建的代理返回其原始对象。(详见下文)
1、toValue
将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。
这可以在组合式函数中使用,用来规范化一个可以是值、ref 或 getter 的参数。
语法:
function toValue<T>(source: T | Ref<T> | (() => T)): T
使用案例:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
在组合式函数中规范化参数:
import type { MaybeRefOrGetter } from 'vue'
function useFeature(id: MaybeRefOrGetter<number>) {
watch(() => toValue(id), id => {
// 处理 id 变更
})
}
// 这个组合式函数支持以下的任意形式:
useFeature(1)
useFeature(ref(1))
useFeature(() => 1)
五、toRaw、markRaw
- toRaw:根据一个 Vue 创建的代理返回其原始对象。
- markRaw:将一个对象标记为不可被转为代理。返回该对象本身。
1、toRaw
根据一个 Vue 创建的代理返回其原始对象。
语法:
function toRaw<T>(proxy: T): T
toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
使用案例:
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
2、markRaw
将一个对象标记为不可被转为代理。返回该对象本身。
语法:
function markRaw<T extends object>(value: T): T
使用案例:
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
【注意】:谨慎使用 markRaw() 和类似 shallowReactive()
markRaw() 和类似 shallowReactive() 这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:
- 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。
这应该是一种进阶需求,因为只在根层访问能到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:
const foo = markRaw({
nested: {}
})
const bar = reactive({
// 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
nested: foo.nested
})
console.log(foo.nested === bar.nested) // false
六、shallowRef、shallowReactive、shallowReadonly
- shallowRef: ref() 的浅层作用形式。
- shallowReactive:reactive() 的浅层作用形式。
- shallowReadonly:readonly() 的浅层作用形式。
1、shallowRef
和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
语法:
function shallowRef<T>(value: T): ShallowRef<T>
interface ShallowRef<T> {
value: T
}
shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。
使用示例:
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
2、shallowReactive
和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
【注意】:
浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。故请谨慎使用!
语法:
function shallowReactive<T extends object>(target: T): T
使用示例:
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++
3、shallowReadonly
和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
【注意】:
浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。故请谨慎使用!
语法:
function shallowReadonly<T extends object>(target: T): Readonly<T>
使用示例:
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性会失败
state.foo++
// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false
// 这是可以通过的
state.nested.bar++
七、triggerRef、customRef
- triggerRef:强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
- customRef:创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
1、triggerRef
强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
语法:
function triggerRef(ref: ShallowRef): void
使用案例:
const shallow = shallowRef({
greet: 'Hello, world'
})
// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
console.log(shallow.value.greet)
})
// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'
// 打印 "Hello, universe"
triggerRef(shallow)
2、customRef
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
语法:
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}
customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。
使用案例:创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用。
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
在组件中使用:
<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>
<template>
<input v-model="text" />
</template>
八、effectScope、getCurrentScope、onScopeDispose
- effectScop:创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。对于该 API 的使用细节,请查阅对应的 RFC。
- getCurrentScop:如果有的话,返回当前活跃的 effect 作用域。
- onScopeDispose:在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。
九、isProxy、isReactive 和 isReadonly
- isProxy:检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
- isReactive:检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
- isReadonly:检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。通过 readonly() 和 shallowReadonly() 创建的代理都是只读的,因为他们是没有 set 函数的 computed() ref。
十、h() 函数
h() 函数用来创建虚拟 DOM 节点 (vnode)。
// 完整参数签名
function h(
type: string | Component,
props?: object | null,
children?: Children | Slot | Slots
): VNode
// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode
type Children = string | number | boolean | VNode | null | Children[]
type Slot = () => Children
type Slots = { [name: string]: Slot }
第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop,第三个参数是子节点。
当创建一个组件的 vnode 时,子节点必须以插槽函数进行传递。如果组件只有默认槽,可以使用单个插槽函数进行传递。否则,必须以插槽函数的对象形式来传递。
为了方便阅读,当子节点不是插槽对象时,可以省略 prop 参数。
1、创建原生元素
import { h } from 'vue'
// 除了 type 外,其他参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都可以用于 prop
// Vue 会自动选择正确的方式来分配它
h('div', { class: 'bar', innerHTML: 'hello' })
// class 与 style 可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 prop 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnode 和字符串
h('div', ['hello', h('span', 'hello')])
2、创建组件
import Foo from './Foo.vue'
// 传递 prop
h(Foo, {
// 等价于 some-prop="hello"
someProp: 'hello',
// 等价于 @update="() => {}"
onUpdate: () => {}
})
// 传递单个默认插槽
h(Foo, () => 'default slot')
// 传递具名插槽
// 注意,需要使用 `null` 来避免
// 插槽对象被当作是 prop
h(MyComponent, null, {
default: () => 'default slot',
foo: () => h('div', 'foo'),
bar: () => [h('span', 'one'), h('span', 'two')]
})
十一、isVNode()
判断一个值是否为 vnode 类型。
十二、cloneVNode()
克隆一个 vnode。
十三、mergeProps() 函数
合并多个 props 对象,用于处理含有特定的 props 参数的情况。
十四、resolveComponent() 和 resolveDirective()
- resolveComponent():按名称手动解析已注册的组件。
- resolveDirective():按名称手动解析已注册的指令。
十五、withDirectives() 和 withModifiers()
withDirectives():用于给 vnode 增加自定义指令。
withModifiers():用于向事件处理函数添加内置 v-on 修饰符。
【参考文章】
vue - API 参考