跳至主要內容
Skip to content

Vue 中 ref 的作用

Vue 裡的 ref 有兩個常見語境,很容易被混在一起:

  1. 響應式 ref:用來建立一個響應式資料容器,例如 const count = ref(0)
  2. 模板 ref:用來取得 DOM 元素或子元件實例,例如 <input ref="inputRef" />

一句話回答:ref 在 Composition API 中主要用來包裝一個可響應的值,透過 .value 讀寫;在模板中,ref 也可以作為特殊 attribute,用來取得 DOM 或子元件引用。


一、 必考觀念

1. 響應式 ref 是什麼?

ref 可以把一個值包成響應式容器:

typescript
import { ref } from 'vue'

const count = ref(0)

count.value++

它回傳的不是原始數字,而是一個 ref object:

typescript
{
  value: 0
}

Vue 會攔截 .value 的 get/set:

  • .value 時收集依賴。
  • .value 時觸發更新。

簡化心智模型:

typescript
function ref(rawValue) {
  return {
    get value() {
      track()
      return rawValue
    },
    set value(nextValue) {
      rawValue = nextValue
      trigger()
    },
  }
}

真實實作更複雜,還會處理物件轉 reactive、shallow、依賴集合等。

2. 為什麼需要 .value

JavaScript 的原始型別不是引用,無法直接攔截普通變數的讀寫。

typescript
let count = 0
count++

Vue 無法知道你什麼時候讀了 count、什麼時候改了 count

所以 Vue 用一個物件包住它:

typescript
const count = {
  value: 0,
}

物件屬性可以透過 getter/setter 攔截,這就是 .value 的必要性。

3. 模板中為什麼不用 .value

在 template 裡,Vue 會自動解包 ref。

vue
<template>
  <button @click="count++">
    {{ count }}
  </button>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
</script>

模板裡可以直接寫 count,不需要 count.value

但在 <script setup> 裡仍要:

typescript
count.value++

這是很多新手容易混淆的地方。


二、 ref 可以包什麼?

1. 包原始型別

typescript
const count = ref(0)
const name = ref('Ada')
const visible = ref(false)

這是 ref 最常見用途。

2. 包物件

typescript
const user = ref({
  name: 'Ada',
  age: 28,
})

Vue 會把物件值轉成 reactive,所以內部屬性也能被追蹤:

typescript
user.value.name = 'Grace'

但注意,外層仍然要 .value

3. 替換整個值

ref 很適合需要整體替換的資料:

typescript
const user = ref(null)

user.value = await fetchUser()

這也是 refreactive 的重要差異之一:ref 天然適合整體替換。


三、 模板 ref

1. 取得 DOM 元素

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

<script setup lang="ts">
import { onMounted, ref } from 'vue'

const inputRef = ref<HTMLInputElement | null>(null)

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

這裡的 ref="inputRef" 是模板 ref。Vue 會在 DOM 掛載後,把真實 input 元素放進 inputRef.value

2. 配合 nextTick

如果 DOM 是由 v-if 控制,狀態改變後要等 DOM 更新完成才能拿到 ref:

vue
<template>
  <button @click="open">Open</button>
  <input v-if="visible" ref="inputRef" />
</template>

<script setup lang="ts">
import { nextTick, ref } from 'vue'

const visible = ref(false)
const inputRef = ref<HTMLInputElement | null>(null)

async function open() {
  visible.value = true
  await nextTick()
  inputRef.value?.focus()
}
</script>

延伸讀:nextTick 的作用與實現原理

3. 取得子元件實例

模板 ref 也可以指向子元件:

vue
<template>
  <BaseInput ref="baseInputRef" />
</template>

<script setup> 子元件裡,預設不會暴露所有內部內容。需要用 defineExpose

vue
<!-- BaseInput.vue -->
<script setup lang="ts">
function focus() {
  // focus internal input
}

defineExpose({
  focus,
})
</script>

父元件:

typescript
const baseInputRef = ref<InstanceType<typeof BaseInput> | null>(null)

baseInputRef.value?.focus()

這屬於命令式通訊,適合 focus、reset、scroll 這類操作,不適合一般資料流。


四、 ref 與 reactive 的差異

比較refreactive
適合資料原始型別、可整體替換的值物件、表單狀態、結構化狀態
存取方式script 中要 .value直接讀寫屬性
替換整體很自然直接替換會失去原 reactive 引用
解構可用 toRefs 或保持 ref直接解構會丟失響應性

1. ref 適合原始值

typescript
const count = ref(0)
const keyword = ref('')
const loading = ref(false)

2. reactive 適合物件狀態

typescript
const form = reactive({
  email: '',
  password: '',
})

但如果你要整個替換:

typescript
// 不建議:外部仍指向舊 reactive object
form = newForm

這時 ref 包物件更自然:

typescript
const form = ref({
  email: '',
  password: '',
})

form.value = newForm

五、 ref 解包規則

1. 模板自動解包

vue
<template>
  {{ count }}
</template>

等價於讀 count.value

2. reactive 物件中的 ref 會解包

typescript
const count = ref(0)

const state = reactive({
  count,
})

console.log(state.count) // 0

state.count 時會自動解包。

但陣列和集合裡的 ref 不會像物件屬性那樣完全解包:

typescript
const list = reactive([ref(1)])

console.log(list[0].value)

這是面試常見細節,不必一開始背很深,但要知道 ref 解包不是所有場景都一樣。

3. 解構 reactive 會丟失響應性

typescript
const state = reactive({
  count: 0,
})

const { count } = state

這裡的 count 是普通值,不再響應。

可以用 toRefs

typescript
const { count } = toRefs(state)

count.value++

六、 shallowRef

shallowRef 只追蹤 .value 本身,不深層轉換物件。

typescript
const chart = shallowRef(null)

適合:

  • 第三方實例。
  • 大型不可變資料。
  • 不希望 Vue 深層代理的物件。
  • 手動控制更新的資料。

例如圖表實例:

typescript
const chartInstance = shallowRef(null)

你通常只關心整個 instance 是否替換,不需要 Vue 追蹤 instance 裡面每個屬性。


七、 追問題庫

Q1:ref 的作用是什麼?

有兩種常見答案:

  1. 在 Composition API 中建立響應式資料。
  2. 在模板中取得 DOM 或子元件引用。

面試時最好主動區分這兩種語境。

Q2:為什麼 script 裡要 .value

因為 ref 是一個物件容器,真正的值在 .value 上。Vue 透過 .value 的 getter/setter 做依賴收集與觸發更新。

Q3:ref 和 reactive 怎麼選?

簡單策略:

  • 原始型別用 ref
  • 需要整體替換用 ref
  • 結構化物件狀態可用 reactive
  • composable 對外返回狀態時,常用 ref 或 toRefs 保持解構友好。

Q4:template ref 什麼時候有值?

DOM 掛載後才有值。

onMounted 裡通常可以取得。如果元素受 v-if 控制,切換後要等 nextTick

Q5:可以用 ref 做元件通訊嗎?

可以,但它是命令式方式,耦合較高。

適合 focus、reset、scrollTo 這類命令,不適合一般資料同步。一般資料流應該優先使用 Props、Emit、v-model、Provide/Inject 或 Pinia。

延伸讀:Vue 元件通訊方式有哪些及原理


八、 實作題

題目:打開搜尋框後自動 focus

需求:

  • 點擊按鈕顯示搜尋框。
  • 搜尋框顯示後自動 focus。
vue
<template>
  <button @click="openSearch">Search</button>
  <input
    v-if="visible"
    ref="inputRef"
    v-model="keyword"
  />
</template>

<script setup lang="ts">
import { nextTick, ref } from 'vue'

const visible = ref(false)
const keyword = ref('')
const inputRef = ref<HTMLInputElement | null>(null)

async function openSearch() {
  visible.value = true
  await nextTick()
  inputRef.value?.focus()
}
</script>

這裡有三種 ref:

  • visible:響應式布林值。
  • keyword:響應式字串,配合 v-model
  • inputRef:模板 ref,指向 DOM input。

九、 資深視角

1. ref 是響應式邊界

ref 把普通值包成可追蹤的響應式邊界。這讓原始值也能參與 Vue 的依賴追蹤。

從系統設計角度看,ref 是一個明確的「可變狀態容器」。

2. template ref 是逃生口

大部分 Vue 邏輯應該資料驅動,但有些場景必須碰 DOM:

  • focus。
  • 選取文字。
  • 讀尺寸。
  • scroll。
  • 接第三方套件。

template ref 是 Vue 給你的 escape hatch。它很有用,但不應該取代正常資料流。

3. 大型資料要考慮 shallowRef

如果你把大型資料或第三方 class instance 放進普通 ref,Vue 可能會深層轉 reactive。

這不一定是你想要的。對不可變資料、第三方實例、大型表格資料,可以評估 shallowRefmarkRaw 或資料切片。


總結

問題面試回答重點
ref 的作用建立響應式值;也可作為模板引用
為什麼有 .value透過物件 getter/setter 追蹤與觸發更新
模板是否要 .value不需要,模板會自動解包
ref vs reactiveref 適合原始值與整體替換,reactive 適合物件狀態
template ref 何時有值DOM 掛載後;v-if 切換後通常要 nextTick
ref 通訊風險命令式耦合高,不應取代 Props/Emit

一句完整的面試回答可以是:

Vue 中的 ref 有兩種常見作用。第一,它可以在 Composition API 中建立響應式資料容器,真正的值放在 .value 上,Vue 透過 .value 的 getter/setter 做依賴收集和更新觸發;模板中會自動解包,所以不需要寫 .value。第二,ref 也可以作為模板引用,用來取得 DOM 元素或子元件實例,常用於 focus、scroll、reset 等命令式操作。實務上要區分響應式 ref 和 template ref,不要把 template ref 當成一般資料流工具。

延伸閱讀