尽管存在 prop 和事件,但有时你可能仍然需要直接访问 JavaScript 中的子组件。为此,可以使用 ref attribute
为子组件
或 HTML 元素
指定引用 ID
。例如:
<input ref="input" />
例如,你希望在组件挂载时,以编程的方式 focus 到这个 input 上,这可能有用
const app = Vue.createApp({})
app.component('base-input', {
template: `
<input ref="input" />
`,
methods: {
focusInput() {
this.$refs.input.focus()
}
},
mounted() {
this.focusInput()
}
})
此外,还可以向组件本身添加另一个 ref,并使用它从父组件触发 focusInput 事件:
<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput.focusInput()
$refs
只会在组件渲染完成之后生效。这仅作为一个用于直接操作子元素的“逃生舱”——你应该避免在模板或计算属性中访问$refs
。
Vue 3.x 中模板引用
在使用组合式 API 时,响应式引用
和模板引用
的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回:
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null) // 与模板<template></template>中的引用ref=“root”名称相同
onMounted(() => {
// DOM元素将在初始渲染后分配给ref
console.log(root.value) // <div>这是根元素</div>
})
return {
root
}
}
}
</script>
这里我们在渲染上下文中暴露 root,并通过 ref="root",将其绑定到 div 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的 ref 键对应于渲染上下文中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值
。
作为模板使用的 ref
的行为与任何其他 ref 一样:它们是响应式
的,可以传递到 (或从中返回) 复合函数中。
JSX 中的用法
export default {
setup() {
const root = ref(null)
return () =>
h('div', {
ref: root
})
// with JSX
return () => <div ref={root} />
}
}
v-for 中的用法
组合式 API 模板引用在 v-for 内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理:
<template>
<div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
{{ item }}
</div>
</template>
<script>
import { ref, reactive, onBeforeUpdate } from 'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs
}
}
}
</script>
侦听模板引用
侦听模板引用的变更可以替代前面例子中演示使用的生命周期钩子。
但与生命周期钩子的一个关键区别是,watch()
和 watchEffect()
副作用是在 DOM 被挂载或更新之前
运行的,所以当侦听器运行副作用时,模板引用还没有被更新。
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
const root = ref(null)
watchEffect(() => {
// 这个副作用在 DOM 更新之前运行,因此,模板引用还没有持有对元素的引用。
console.log(root.value) // => null
})
return {
root
}
}
}
</script>
因此,使用模板引用的侦听器应该用 flush: 'post'
选项来定义,这将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。这将在 DOM 被更新后运行副作用,并确保模板引用与 DOM 保持同步并引用正确的元素。
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
const root = ref(null)
watchEffect(() => {
console.log(root.value) // => <div></div>
},
{
flush: 'post'
})
return {
root
}
}
}
</script>