首页 > 其他分享 >Vue3 模板引用 ref 的实现原理

Vue3 模板引用 ref 的实现原理

时间:2023-11-17 16:27:11浏览次数:30  
标签:const 渲染 DOM 实例 Vue3 组件 ref 模板

什么是模板引用 ref

有时候可以使用  ref attribute 为子组件或 HTML 元素指定引用 ID。

<template>
  <input ref="input" />
</template>
<script>
  import { defineComponent, ref } from "vue";
  export default defineComponent({
    setup() {
      const input = ref(null);
      const focusInput = () => {
        input.value.focus();
      };
      return {
        input,
      };
    },
  });
</script>

这里在渲染上下文中暴露  input,并通过  ref="input",将其绑定到 input 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的  ref  键对应于渲染上下文中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。

设置当前渲染的实例对象

知道写的这个组件在运行的时候,先会创建一个组件实例对象instance,再通过运行这个组件实例对象的render方法获取这个组件的虚拟 DOM,然后再进行patch,渲染出真实 DOM。

在运行组件实例对象的 render 方法之前,会先设置保存正在渲染的组件实例对象currentRenderingInstance

renderComponentRoot 方法

import { setCurrentRenderingInstance } from "./componentRenderContext";

export function renderComponentRoot(instance) {
  const { proxy, render } = instance;
  let result;
  // 返回上一个实例对象
  const prev = setCurrentRenderingInstance(instance);
  result = render.call(proxy);
  // 再设置当前的渲染对象上一个,具体场景是嵌套循环渲染的时候,渲染完子组件,再去渲染父组件
  setCurrentRenderingInstance(prev);
  return result;
}

setCurrentRenderingInstance 方法

export let currentRenderingInstance = null;

export function setCurrentRenderingInstance(instance) {
  const prev = currentRenderingInstance;
  currentRenderingInstance = instance;
  return prev;
}

设置元素或者组件的 props 中的 ref

在获取组件的虚拟 DOM 的时候,其实是通过 createVNode 来创建的虚拟 DOM,在创建的虚拟 DOM 的时候会保存当前前渲染的实例对象到当前元素或者组件的 props 中的 ref 中。

export function createVNode(type, props?, children?) {
  const vnode = {
    type,
    props,
    ref: props && normalizeRef(props), // 创建虚拟DOM的时候设置ref
    children,
    component: null,
    key: props && props.key,
    shapeFlag: getShapeFlag(type),
    el: null,
  };
  return vnode;
}

来看看 normalizeRef 函数做了什么

import { currentRenderingInstance } from "./componentRenderContext"
const normalizeRef = ({
    ref
  }) => {
    return (
      ref != null
        ? isString(ref) || isRef(ref) || isFunction(ref)
          ? { i: currentRenderingInstance, r: ref}
          : ref
        : null
    ) as any
}

可以看到 normalizeRef 函数最主要是把当前的渲染实例对象currentRenderingInstance保存起来了。

模板引用的赋值

在上面开头的时候已经说了,模板引用 ref 只会在初始渲染之后获得。那么具体在源码中的位置是 patch 函数的底部,也就是把虚拟 DOM 进行 patch 渲染之后,再设置模版引用 ref。

function patch(n1, n2, container: any, parentComponent, anchor) {
  // 基于 n2 的类型来判断
  // 因为 n2 是新的 vnode
  const { type, shapeFlag, ref } = n2;

  // Fragment => 只渲染 children
  switch (type) {
    // 其中还有几个类型比如: static fragment comment
    case Fragment:
      processFragment(n1, n2, container, parentComponent, anchor);
      break;
    case Text:
      processText(n1, n2, container);
      break;
    default:
      // 这里就基于 shapeFlag 来处理
      if (shapeFlag & ShapeFlags.ELEMENT) {
        // 处理 element
        processElement(n1, n2, container, parentComponent, anchor);
      } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
        // 处理 component
        processComponent(n1, n2, container, parentComponent, anchor);
      }
      break;
  }

  // 模板引用ref只会在初始渲染之后获得
  if (ref != null && parentComponent) {
    setRef(ref, n2 || n1, !n2);
  }
}

再看看 setRef 函数中干了什么事情。

export function setRef(
    rawRef,
    vnode,
    isUnmount = false
  ) {
    // 判断如果是组件实例,则把改组件实例作为ref的值,否则就是把该元素作为ref值
    const refValue =
    vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
      ? vnode.component!.proxy
      : vnode.el
    // 如果n2不存在则是卸载
    const value = isUnmount ? null : refValue
    // 把在创建虚拟DOM的时候设置保存的组件渲染实例和ref键值解构出来
    const { i: owner, r: ref } = rawRef

    const setupState = owner.setupState
    // happy path中只考虑最简单的情况
    const _isString = isString(ref)

    if (_isString) {
        // 如果在对应于渲染上下文中存在ref键值,则 VNode 的相应元素或组件实例将被分配给该 ref 的值
        if (hasOwn(setupState, ref)) {
          setupState[ref] = value
        }
    }
  }

模版引用 ref 的赋值具体就是在 setRef 函数中实现的。判断如果是组件实例,则把改组件实例作为 ref 的值,否则就是把该元素作为 ref 值,再把在创建虚拟 DOM 的时候设置保存的组件渲染实例和 ref 键值解构出来,再判断如果在对应于渲染上下文中存在 ref 键值,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。

标签:const,渲染,DOM,实例,Vue3,组件,ref,模板
From: https://www.cnblogs.com/wp-leonard/p/17839012.html

相关文章

  • Vue3 的 effect、 watch、watchEffect 的实现原理
    所谓watch,就是观测一个响应式数据或者监测一个副作用函数里面的响应式数据,当数据发生变化的时候通知并执行相应的回调函数。Vue3最新的watch实现是通过最底层的响应式类ReactiveEffect的实例化一个reactiveeffect对象来实现的。它的创建过程跟effectAPI的实现类似,所......
  • sign_and_send_pubkey: signing failed: agent refused operation Permission denied
    在你的ternimal下执行该命令ssh-agent-s//或者ssh-agent/bin/bash然后再执行下面的命令ssh-add在重新repoinit......
  • vue3 使用 store
    在script中使用storehttps://blog.csdn.net/SubStar/article/details/116077737<script>import{getCurrentInstance}from"vue";import{useStore}from"vuex";exportdefault{setup(){//第一种方法:获取路由对象router的方法1constv......
  • ! (空引用忽略判断) 操作符 (C# reference)
    ref: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving主要是.Net6开始判断引用类型是否空,在项目文件中  PropertyGroup节点下  <Nullable>enable</Nullable,代表开启 ,在这个情况我想某个变量或者属性引用不要......
  • Makefile 模板(二)
    Makefile模板模板介绍支持存放中间文件的文件夹检查和创建支持源文件位于不同文件夹内模板OBJOUT:=./out/EXEOUT:=./out/INCLUDE_DIR:=./includeSRC_DIR_TEST=./src/test/SRC_DIR_THREADPOLL=./src/WorkThread/LIB:=-lpthreadSRC:=$(wildcard$(SRC_......
  • Vue3 Pinia对state的订阅监听($subscribe,$onAction)数据监听
    <template><divclass="main-container":class="{'show-scroll':targetIsVisible}"><div:style="{height:frameHeight+'px'}"class="main-content":class="{'show-......
  • mac 下使用 brew 安装包报错 error: Cannot install under Rosetta 2 in ARM default
    mac下使用brew安装包报错error:CannotinstallunderRosetta2inARMdefaultprefix(/opt/homebrew)!TorerununderARMuse:arch-arm64brewinstall...Toinstallunderx86_64,installHomebrewinto/usr/local.解决办法:arch-arm64brewinstallxxx......
  • vue2和vue3ref的区别(详解)
    Vue2和Vue3中ref的区别如下:在Vue2中,ref主要用于在模板中获取DOM元素或组件实例。而在Vue3中,虽然ref也可以获取DOM元素或组件实例,但更重要的是,它还可以将一个基本类型的变量转换成响应式的数据,无需再通过复杂的步骤来访问响应式数据。另外,Vue3的ref还支持对象属性和数组索引......
  • cbv源码,模板,请求响应,session
    1cbv源码......
  • Idea配置mybatis核心配置文件模板
    在我们日常开发中不可能将mybatis相关配置文件全部记住,我们这里通过在idea中配置模板快捷生成(本文演示idea版本为2022.02.01)。方法如下:1.进入idea设置File->settings2.选择Editor->FileandCodeTemplates->Files3.点击“+”创建模板,对模板命名,设置模板类型,设置模板默认名字(模板中......