toRef, toRefs and unref

Keep a live reference to one property of a reactive object, destructure without losing reactivity, and accept refs or getters as arguments.

SyntaxtoRef(object, key, defaultValue?) | toRef(getter) | toRefs(object) | unref(val) | toValue(valOrRefOrGetter)

Reading a property off a reactive object gives you its value, not a connection to it. These four utilities move between the two forms without breaking tracking.

Out of an object

toRef(object, key) returns a ref wired to that one property: writes to the ref reach the object, and writes to the object are visible through the ref. An optional third argument supplies a default when the property is undefined. toRefs(object) does the same for every enumerable property, so const { query, page } = toRefs(filters) gives refs that still track, where plain destructuring would have given dead copies.

Since Vue 3.3, toRef also accepts a getter: toRef(() => props.size) returns a readonly ref that follows the prop. This is the form to use for props, because the property version would let you write through to a prop and trigger the read-only warning.

Into a value

unref(x) is shorthand for the longhand isRef(x) ? x.value : x; the two are identical. toValue(x), added in 3.3, goes one step further and also calls x when it is a function, which is what lets a composable accept a plain value, a ref or a getter for the same argument.

Example

Example · vue
<script setup lang="ts">
import { reactive, toRefs, computed } from 'vue'

interface Filters {
  query: string
  page: number
}

const filters = reactive<Filters>({ query: '', page: 1 })

// One ref per property, each still wired to `filters`.
// Plain destructuring here would copy the values and stop tracking.
const { query, page } = toRefs(filters)

const label = computed<string>(
  () => `page ${page.value} of "${query.value || 'everything'}"`
)

function nextPage(): void {
  page.value += 1 // writes through to filters.page
}

function reset(): void {
  filters.page = 1 // visible through the `page` ref
}
</script>

<template>
  <input v-model="query" type="search" placeholder="Search" />
  <p>{{ label }}</p>
  <p>filters.page is {{ filters.page }}</p>
  <button @click="nextPage">Next page</button>
  <button @click="reset">Reset</button>
</template>

<style scoped>
button {
  margin-right: 0.5rem;
}
</style>

When to use it

  • Destructuring a reactive options object without the values freezing
  • Passing one property of a store or a prop into a composable that expects a ref
  • Writing a composable argument that takes a constant, a ref or a getter
  • Giving an optional property a default while keeping it writable

More examples

A composable that accepts a value, a ref or a getter

One signature covers three call styles, so callers never have to wrap a constant in a ref to satisfy your function. This is the standard argument shape for composables; every library composable you meet takes its inputs this way.

Example · ts
// src/composables/useGreeting.ts
import { computed, toValue, type ComputedRef, type Ref } from 'vue'

export function useGreeting(
  name: string | Ref<string> | (() => string)
): ComputedRef<string> {
  // toValue reads a ref's .value, calls a getter, or passes a plain value
  // through. Added in Vue 3.3.
  return computed(() => `Hello, ${toValue(name)}`)
}

// src/demo/greeting.ts
import { ref } from 'vue'
import { useGreeting } from '../composables/useGreeting'

const plain = useGreeting('Ada')
console.log(plain.value)
// Output: Hello, Ada

const who = ref('Ada')
const live = useGreeting(who)
who.value = 'Grace'
console.log(live.value)
// Output: Hello, Grace

A readonly ref for a prop, and the longhand it replaces

Use the getter form whenever you need to hand a prop to something that expects a ref, such as a composable argument. Avoid toRef(props, 'size') for props: the ref it returns is writable, and writing it mutates a prop, which the development build warns about.

Example · ts
// src/components/SizeLabel.vue (script block only)
import { toRef, computed, type ComputedRef, type Ref } from 'vue'

const props = defineProps<{ size: number; unit?: string }>()

// Getter form, Vue 3.3+: a readonly ref that follows the prop.
const size: Readonly<Ref<number>> = toRef(() => props.size)

// The same thing written longhand.
const sizeLonghand: ComputedRef<number> = computed(() => props.size)

// Property form with a default, for a prop that may be undefined.
const unit = toRef(props, 'unit', 'px')

console.log(size.value, sizeLonghand.value, unit.value)
// Output: 10 10 px   (when rendered as <size-label :size="10" />)

Returning refs from a composable so callers can destructure

Internally the composable keeps one reactive object, which is comfortable to mutate. On the way out, toRefs converts it so `const { page } = usePagination()` behaves. Spread the result of toRefs when you also need to return functions alongside the state.

Example · ts
// src/composables/usePagination.ts
import { reactive, toRefs, type Ref } from 'vue'

interface Pagination {
  page: number
  perPage: number
}

export function usePagination(perPage = 20) {
  const state = reactive<Pagination>({ page: 1, perPage })

  function next(): void {
    state.page += 1
  }

  // toRefs keeps the caller's destructuring reactive.
  return { ...toRefs(state), next }
}

// src/demo/pagination.ts
import { usePagination } from '../composables/usePagination'

const { page, perPage, next } = usePagination(10)
next()
console.log(page.value, perPage.value)
// Output: 2 10

Discussion

  • Be the first to comment on this lesson.