ref 模版引入功能介绍

lxf2023-04-05 09:42:01

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 模版提及的完成步骤

ref 模版引入功能介绍