跳转到内容

模板Refs

虽然Vue的声明式渲染模型为你抽象了大部分直接DOM操作,但在某些情况下,我们可能仍然需要直接访问底层DOM元素。为了实现这一点,我们可以使用特殊的ref属性

模板
<input ref="input">

ref是一个特殊属性,类似于在v-for章节中讨论的key属性。它允许我们在组件挂载后获得对特定DOM元素或子组件实例的引用。这可能在你想要,例如,在组件挂载时程序化地聚焦输入,或在一个元素上初始化第三方库时非常有用。

访问Refs

要使用组合式API获取引用,我们可以使用useTemplateRef() 辅助函数

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

// the first argument must match the ref value in the template
const input = useTemplateRef('my-input')

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>

当使用TypeScript时,Vue的IDE支持和vue-tsc将根据匹配的ref属性所使用的元素或组件自动推断input.value的类型。

3.5之前的用法

在3.5版本之前,由于还没有引入useTemplateRef(),我们需要声明一个与模板ref属性值匹配的ref

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

// declare a ref to hold the element reference
// the name must match template ref value
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

如果不使用<script setup>,请确保也从setup()返回ref

js
export default {
  setup() {
    const input = ref(null)
    // ...
    return {
      input
    }
  }
}

生成的ref在this.$refs上暴露

vue
<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input" />
</template>

请注意,您只能在组件挂载后访问ref 。如果在模板表达式中尝试访问$refs.inputinput,则在第一次渲染时它将是undefinednull。这是因为元素在第一次渲染后才会存在!

如果您正在尝试监视模板ref的变化,请确保考虑到ref值为null的情况

js
watchEffect(() => {
  if (input.value) {
    input.value.focus()
  } else {
    // not mounted yet, or the element was unmounted (e.g. by v-if)
  }
})

另请参阅:[模板ref类型](/guide/typescript/composition-api#typing-template-refs)

v-for中的ref

需要v3.5或更高版本

当在v-for中使用ref时,相应的ref应该包含一个数组值,该数组值将在挂载后被填充

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

const list = ref([
  /* ... */
])

const itemRefs = useTemplateRef('items')

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

在Playground中尝试

3.5之前的用法

在3.5版本之前,由于还没有引入useTemplateRef(),我们需要声明一个与模板ref属性值匹配的ref。该ref也应包含一个数组值

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

const list = ref([
  /* ... */
])

const itemRefs = ref([])

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

当在v-for中使用ref时,生成的ref值将是一个包含对应元素的数组

vue
<script>
export default {
  data() {
    return {
      list: [
        /* ... */
      ]
    }
  },
  mounted() {
    console.log(this.$refs.items)
  }
}
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

在Playground中尝试

需要注意的是,ref数组不保证与源数组相同的顺序。

函数ref

除了字符串键之外,ref属性还可以绑定到一个函数,该函数将在每个组件更新时被调用,并为您提供完全的灵活性来存储元素引用。该函数接收元素引用作为第一个参数

模板
<input :ref="(el) => { /* assign el to a property or ref */ }">

请注意,我们正在使用动态的:ref绑定,因此我们可以传递一个函数而不是ref名称字符串。当元素被卸载时,参数将是null。当然,您也可以使用方法而不是内联函数。

组件上的ref

本节假设您了解组件。您可以随时跳过它,稍后再回来。

ref也可以用于子组件。在这种情况下,引用将是组件实例的引用

vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'

const childRef = useTemplateRef('child')

onMounted(() => {
  // childRef.value will hold an instance of <Child />
})
</script>

<template>
  <Child ref="child" />
</template>
3.5之前的用法
vue
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
  // child.value will hold an instance of <Child />
})
</script>

<template>
  <Child ref="child" />
</template>
vue
<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  mounted() {
    // this.$refs.child will hold an instance of <Child />
  }
}
</script>

<template>
  <Child ref="child" />
</template>

如果子组件使用Options API或没有使用<script setup>,则引用的实例将与子组件的this相同,这意味着父组件将完全访问子组件的每个属性和方法。这使得在父组件和子组件之间创建紧密耦合的实现细节变得容易,因此组件ref仅在绝对需要时使用 - 在大多数情况下,您应首先尝试使用标准的props和emit接口来实现父/子交互。

这里有一个例外,使用 <script setup> 的组件默认是 私有的:如果一个父组件通过 <script setup> 引用子组件,除非子组件选择使用 defineExpose 宏公开一个接口,否则父组件无法访问任何内容。

vue
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// Compiler macros, such as defineExpose, don't need to be imported
defineExpose({
  a,
  b
})
</script>

当父组件通过模板引用获取此组件的实例时,获取到的实例将是 { a: number, b: number } 的形状(引用自动解包,就像在正常实例上一样)。

另请参阅:[组件模板引用的类型](/guide/typescript/composition-api#typing-component-template-refs)

可以使用 expose 选项来限制对子实例的访问。

js
export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

在上面的例子中,通过模板引用引用此组件的父组件只能访问 publicDatapublicMethod

模板引用已加载