Vue 3中的ref和template refs详解(含Vue2迁移到Vue3方法)
<h1 id="vue-3中的ref和template-refs详解">Vue 3中的ref和template refs详解</h1><p>在Vue 3中,<code>ref</code>和模板引用(template refs)是两个相关但不同的概念,它们在组合式API(Composition API)中扮演着重要角色。</p>
<h2 id="ref---响应式引用">ref - 响应式引用</h2>
<p><code>ref</code>是Vue 3中创建响应式数据的主要方式之一。</p>
<h3 id="基本用法">基本用法</h3>
<pre><code class="language-typescript">import { ref } from 'vue'
// 创建一个响应式引用
const count = ref(0)
// 访问或修改值需要使用.value
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
</code></pre>
<h3 id="特点">特点</h3>
<ol>
<li><strong>包装原始值</strong>:<code>ref</code>可以将基本类型(如数字、字符串、布尔值)转换为响应式对象</li>
<li><strong>需要使用.value</strong>:在JavaScript中访问或修改ref值时,必须使用<code>.value</code>属性</li>
<li><strong>在模板中自动解包</strong>:在模板中使用时,Vue会自动解包ref,不需要写<code>.value</code></li>
</ol>
<pre><code class="language-vue"><template>
<div>{{ count }}</div> <!-- 不需要.value -->
</template>
</code></pre>
<h3 id="复杂数据结构">复杂数据结构</h3>
<p>对于对象和数组,<code>ref</code>内部使用<code>reactive</code>来实现响应式:</p>
<pre><code class="language-typescript">const user = ref({
name: '张三',
age: 30
})
// 修改属性
user.value.age = 31
</code></pre>
<h2 id="template-refs---模板引用">template refs - 模板引用</h2>
<p>模板引用允许你直接访问DOM元素或组件实例。</p>
<h3 id="vue-2中的使用方式">Vue 2中的使用方式</h3>
<p>在Vue 2中,我们通过<code>this.$refs</code>访问模板引用:</p>
<pre><code class="language-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>
</code></pre>
<h3 id="vue-3中的使用方式">Vue 3中的使用方式</h3>
<p>在Vue 3的组合式API中,模板引用的使用方式发生了变化:</p>
<ol>
<li><strong>创建ref变量</strong>:首先创建一个ref变量</li>
<li><strong>在模板中绑定</strong>:将这个ref变量绑定到元素或组件上</li>
<li><strong>在组件挂载后访问</strong>:组件挂载后,ref变量的<code>.value</code>将包含对应的DOM元素或组件实例</li>
</ol>
<pre><code class="language-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>
</code></pre>
<h3 id="动态模板引用v-for中使用">动态模板引用(v-for中使用)</h3>
<p>在循环中使用模板引用时,需要使用函数形式的ref:</p>
<pre><code class="language-vue"><template>
<div>
<ul>
<li v-for="(item, i) in list" :key="i" :ref="el => { if (el) itemRefs = 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>
</code></pre>
<h2 id="示例关于v-for生成的ref的复杂例子vue2js移植到vue3ts">示例:关于v-for生成的ref的复杂例子,Vue2+js移植到Vue3+TS</h2>
<blockquote>
<p>旧的Vue2+js程序:</p>
<pre><code class="language-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>
</code></pre>
<pre><code class="language-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;
if (!refName) {
console.error('Unknown type:', type);
return null;
}
// 处理数组型 ref(v-for 产生的)
const refs = this.$refs;
</code></pre>
</blockquote>
<p>在示例中,需要从Vue 2风格的<code>$refs</code>迁移到Vue 3的模板引用方式。具体步骤:</p>
<ol>
<li>定义模板引用数组:</li>
</ol>
<pre><code class="language-typescript">const inputGainRefs = ref([])
const outputGainRefs = ref([])
const nodeGainRefsMap = ref([])
</code></pre>
<ol start="2">
<li>在模板中使用这些引用:</li>
</ol>
<pre><code class="language-vue"><MixerCtrlItem
v-for="(item, index) in inputBottomGroups"
:key="index"
:ref="el => { if (el) inputGainRefs.value = el }"
...其他属性
/>
</code></pre>
<ol start="3">
<li>在需要访问子组件时:</li>
</ol>
<pre><code class="language-typescript">// 访问输入增益组件
inputGainRefs.value.someMethod()
// 访问节点增益组件
nodeGainRefsMap.value.someMethod()
</code></pre>
<h2 id="总结">总结</h2>
<ol>
<li><strong>ref</strong>:用于创建响应式数据,在JavaScript中需要使用<code>.value</code>访问</li>
<li><strong>template refs</strong>:用于访问DOM元素或组件实例
<ul>
<li>在Vue 2中通过<code>this.$refs</code>访问</li>
<li>在Vue 3中通过创建ref变量并在模板中绑定来实现</li>
</ul>
</li>
<li><strong>迁移策略</strong>:
<ul>
<li>创建对应的ref变量</li>
<li>在模板中使用<code>:ref</code>绑定</li>
<li>在组件挂载后通过<code>.value</code>访问实际元素或组件</li>
</ul>
</li>
</ol>
<p>这种方式不仅符合Vue 3的组合式API设计理念,还提供了更好的类型推断支持,特别是在使用TypeScript的项目中。</p>
<p></p><br><br>
来源:https://www.cnblogs.com/dandelion-000-blog/p/18867929
頁:
[1]