Vue 3中的ref和template refs详解(含Vue2迁移到Vue3方法)

Vue 3中的ref和template refs详解

在Vue 3中,ref和模板引用(template refs)是两个相关但不同的概念,它们在组合式API(Composition API)中扮演着重要角色。

ref - 响应式引用

ref是Vue 3中创建响应式数据的主要方式之一。

基本用法

typescript 复制代码
import { ref } from 'vue'

// 创建一个响应式引用
const count = ref(0)

// 访问或修改值需要使用.value
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

特点

  1. 包装原始值ref可以将基本类型(如数字、字符串、布尔值)转换为响应式对象
  2. 需要使用.value :在JavaScript中访问或修改ref值时,必须使用.value属性
  3. 在模板中自动解包 :在模板中使用时,Vue会自动解包ref,不需要写.value
vue 复制代码
<template>
  <div>{{ count }}</div> <!-- 不需要.value -->
</template>

复杂数据结构

对于对象和数组,ref内部使用reactive来实现响应式:

typescript 复制代码
const user = ref({
  name: '张三',
  age: 30
})

// 修改属性
user.value.age = 31

template refs - 模板引用

模板引用允许你直接访问DOM元素或组件实例。

Vue 2中的使用方式

在Vue 2中,我们通过this.$refs访问模板引用:

vue 复制代码
<template>
  <div>
    <input ref="inputElement">
    <ChildComponent ref="childComponent"/>
  </div>
</template>

<script>
export default {
  mounted() {
    // 访问DOM元素
    this.$refs.inputElement.focus()
    
    // 访问子组件实例
    this.$refs.childComponent.someMethod()
  }
}
</script>

Vue 3中的使用方式

在Vue 3的组合式API中,模板引用的使用方式发生了变化:

  1. 创建ref变量:首先创建一个ref变量
  2. 在模板中绑定:将这个ref变量绑定到元素或组件上
  3. 在组件挂载后访问 :组件挂载后,ref变量的.value将包含对应的DOM元素或组件实例
vue 复制代码
<template>
  <div>
    <input ref="inputRef">
    <ChildComponent ref="childComponentRef"/>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 创建模板引用
const inputRef = ref(null)
const childComponentRef = ref(null)

onMounted(() => {
  // 访问DOM元素
  inputRef.value.focus()
  
  // 访问子组件实例
  childComponentRef.value.someMethod()
})
</script>

动态模板引用(v-for中使用)

在循环中使用模板引用时,需要使用函数形式的ref:

vue 复制代码
<template>
  <div>
    <ul>
      <li v-for="(item, i) in list" :key="i" :ref="el => { if (el) itemRefs[i] = el }">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUpdate } from 'vue'

const list = ref(['苹果', '香蕉', '橙子'])
const itemRefs = ref([])

// 在更新前清空引用数组
onBeforeUpdate(() => {
  itemRefs.value = []
})

onMounted(() => {
  console.log(itemRefs.value) // DOM元素数组
})
</script>

示例:关于v-for生成的ref的复杂例子,Vue2+js移植到Vue3+TS

旧的Vue2+js程序:

vue 复制代码
<template>
    <MixerCtrlItem v-if="konvaInitlizedFlag" v-for="(item, index) in inputBottomGroups" :key="index"
        :ref="`col-${index}`" :x="0" :y="0" :parent="item" :deviceIp="deviceIp" :modelName="modelName"
        :paramName="paramName" :index="index" :channel="this.colPageIndex * this.pageColCount + index"
        :ctrlItemType="'mixer-input'" />
    <MixerCtrlItem v-if="konvaInitlizedFlag" v-for="(item, index) in outputRightGroups" :key="index" :x="0" :y="0"
        :ref="`row-${index}`" :parent="item" :deviceIp="deviceIp" :modelName="modelName" :paramName="paramName"
        :index="index" :channel="this.rowPageIndex * this.pageRowCount + index" :ctrlItemType="'mixer-output'" />
    <template v-if="konvaInitlizedFlag" v-for="(row, rowIndex) in centerContentGroupsMap" :key="rowIndex">
        <MixerCtrlItem v-for="(item, colIndex) in row" :key="`${rowIndex}-${colIndex}`"
            :ref="`row-${rowIndex}-col-${colIndex}`" :x="0" :y="0" :parent="item" :deviceIp="deviceIp"
            :modelName="modelName" :paramName="paramName" :rowIndex="rowIndex" :colIndex="colIndex"
            :channel="this.rowPageIndex * this.pageRowCount + rowIndex"
            :extraData2="this.colPageIndex * this.pageColCount + colIndex" :ctrlItemType="'mixer-node'" />
    </template>
</template>
javascript 复制代码
// 使用到ref的位置示例,type获取细节不表
const refMap = {
    'in-gain': `col-${channel}`,
    'out-gain': `row-${channel}`,
    'node-gain': `row-${channel}-col-${nodeInputChannel}`,
    'in-btn': `col-${channel}`,
    'out-btn': `row-${channel}`,
    'node-btn': `row-${channel}-col-${nodeInputChannel}`,
};

const refName = refMap[type];
if (!refName) {
    console.error('Unknown type:', type);
    return null;
}

// 处理数组型 ref(v-for 产生的)
const refs = this.$refs[refName];

在示例中,需要从Vue 2风格的$refs迁移到Vue 3的模板引用方式。具体步骤:

  1. 定义模板引用数组:
typescript 复制代码
const inputGainRefs = ref([])
const outputGainRefs = ref([])
const nodeGainRefsMap = ref([])
  1. 在模板中使用这些引用:
vue 复制代码
<MixerCtrlItem 
  v-for="(item, index) in inputBottomGroups" 
  :key="index"
  :ref="el => { if (el) inputGainRefs.value[index] = el }"
  ...其他属性
/>
  1. 在需要访问子组件时:
typescript 复制代码
// 访问输入增益组件
inputGainRefs.value[0].someMethod()

// 访问节点增益组件
nodeGainRefsMap.value[rowIndex][colIndex].someMethod()

总结

  1. ref :用于创建响应式数据,在JavaScript中需要使用.value访问
  2. template refs :用于访问DOM元素或组件实例
    • 在Vue 2中通过this.$refs访问
    • 在Vue 3中通过创建ref变量并在模板中绑定来实现
  3. 迁移策略
    • 创建对应的ref变量
    • 在模板中使用:ref绑定
    • 在组件挂载后通过.value访问实际元素或组件

这种方式不仅符合Vue 3的组合式API设计理念,还提供了更好的类型推断支持,特别是在使用TypeScript的项目中。