跳轉到內容

模板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

模板引用已載入