ref 模版引入功能介绍
依据官网叙述,ref
是一个特殊 attribute,当一个 DOM 原素或是子组件案例挂后面,可以直接掌握到初始化原素的特性或是方式
<script setup>
import { ref, onMounted } from 'vue'
// 申明一个 ref 来储放该元素引入
// 务必和模版中的 ref 同名的
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
因此简单的说,ref 模版引入是直接开展 DOM 操控的一个方法,相近 getElementById()
,用于进行一些 vue 数字驱动实体模型没法覆盖情景。但是需要注意的是,对 DOM 实际操作一定是要在 DOM 3D渲染完毕之后,因此使用 ref 模版引入的过程当中需要考虑 DOM 不可能的状况
下面为大家介绍一下 vue3 中 ref 模版提及的完成基本原理
完成基本原理
vue3 根据 createApp
方式建立 vue 案例并通过 mount
方式初始化 DOM 连接点,在 mount
方式实施过程中,根据 createVNode
方式创建一个 vnode 连接点,从最后形成 vnode 节点 createBaseVNode
的办法中可以看出,ref 特性早已包含于建立的 vnode 目标中
// 函数调用已省去,实际函数公式文件目录在 packages/runtime-core/src/vnode.ts
function createBaseVNode() {
const vnode = {
type,
props,
key: props && normalizeKey(props),
// ref 模版引入特性
ref: props && normalizeRef(props),
children,
component: null,
ctx: currentRenderingInstance
// ... 省去一部分特性
} as VNode
// 回到一个 vnode 目标
return vnode
}
根据 normalizeRef
方式创立了 ref 特性,能够看见在符合条件时将 currentRenderingInstance 变量赋值给 ref 特性
const normalizeRef = ({
ref,
ref_key,
ref_for
}: VNodeProps): VNodeNormalizedRefAtom | null => {
return (
ref != null
? isString(ref) || isRef(ref) || isFunction(ref)
// 在符合条件时将 currentRenderingInstance 变量赋值给 ref 特性
? { i: currentRenderingInstance, r: ref, k: ref_key, f: !!ref_for }
: ref
: null
) as any
}
currentRenderingInstance 自变量用以纪录现阶段3D渲染的部件案例,根据 setCurrentRenderingInstance
方式来设定 currentRenderingInstance 自变量,与此同时回到父元件的案例
export function setCurrentRenderingInstance( instance: ComponentInternalInstance | null ): ComponentInternalInstance | null {
const prev = currentRenderingInstance
// 纪录现阶段部件案例
currentRenderingInstance = instance
// 回到父元件的部件案例
return prev
}
setCurrentRenderingInstance
方式为什么会回到父元件的案例,是由于 vue3 3D渲染过程中需要3D渲染父部件再3D渲染子组件,在3D渲染子组件环节中,当需要使用父部件案例时(比如用 inject 获得依赖注入数值),就可以用 setCurrentRenderingInstance
的传参立即获得
在子组件3D渲染结束后,setCurrentRenderingInstance
能被再度启用并把现阶段的部件案例设定为父部件3D渲染案例,那样保证子组件在3D渲染完毕之后,还可以恰当掌握到父元件的案例
renderComponentRoot
方式回到元件的 vnode,环节中增设了 currentRenderingInstanc 自变量
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
let result
// 设定现阶段已经3D渲染的部件案例
const prev = setCurrentRenderingInstance(instance)
// ... 实行元件的 setup 函数公式和 render 函数公式以形成元件的 VNode
// 将现阶段的部件案例设定为父部件3D渲染案例
setCurrentRenderingInstance(prev)
return result // 回到元件的 VNode
}
在 renderComponentRoot
函数公式实行结束后,进入 patch
方式将 vnode 转换成真实 DOM,在 patch 函数公式实施的结尾,根据 setRef
方式来设定 ref 模版引入
const patch: PatchFn = (
n1,
n2,
parentComponent = null,
parentSuspense = null,
// 省去一部分主要参数
) => {
// ... 不同种类的 vnode 处理方式
// 设定 ref 模版引入
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
}
}
在 setRef
方式中对 ref 种类有多种处理结果(源代码中还有一些界限状况,这儿仅仅最简化的情景)
- 数组类型的 ref,赋值启用
setRef
方式解决 - 函数类型的 ref,立即实行 ref 函数公式(vue3 中所有函数都能通过
callWithErrorHandling
方式实行) - 字符串类型的 ref,将 ref 值 value 取值给3D渲染前后文的 setupState 目标
export function setRef(
rawRef: VNodeNormalizedRef,
oldRawRef: VNodeNormalizedRef | null,
parentSuspense: SuspenseBoundary | null,
vnode: VNode,
isUnmount = false
) {
// 二维数组方式的 ref,赋值启用 setRef 方式
if (isArray(rawRef)) {
rawRef.forEach((r, i) =>
setRef(
r,
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
parentSuspense,
vnode,
isUnmount
)
)
return
}
// ! 假如是多线程部件而且都还没初始化,立即回到
if (isAsyncWrapper(vnode) && !isUnmount) {
return
}
// 解决 ref 数值 value
// ! 假如是部件,获得元件的案例,不然获得 vnode 的 原素
const refValue =
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
? getExposeProxy(vnode.component!) || vnode.component!.proxy
: vnode.el
const value = isUnmount ? null : refValue
const setupState = owner.setupState
// 函数类型的 ref,实行 ref 函数公式
if (isFunction(ref)) {
callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
} else if (_isString) {
// 字符串类型的 ref,若是在相匹配3D渲染前后文存有 ref 的 key
// 取值 ref 值给3D渲染下面 setupState
if (hasOwn(setupState, ref)) {
setupState[ref] = value
}
}
}
最终再简单梳理一下 ref 模版提及的完成步骤