# How Vue 3 Computed Properties Work with Ref Objects in `<script setup>`: Mechanisms and Use Cases

> Understand how Vue 3 computed properties work with ref objects in script setup. Explore the mechanisms and common use cases for efficient reactive state management.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: deep-dive
- Published: 2026-02-16

---

**In Vue 3's `<script setup>` syntax, the `computed` property creates a lazy, cached derived state that automatically tracks reactive dependencies from `ref` objects through a three-layer architecture involving compiler auto-imports, runtime SSR-aware wrappers, and core reactivity effects.**

In the `vuejs/core` repository, the `<script setup>` block serves as compile-time sugar that streamlines the Composition API by automatically exposing top-level variables to the template. When working with **Vue 3 computed properties alongside ref objects**, understanding the precise mechanism—from compiler injection to reactive dependency tracking—enables you to optimize performance and avoid common reactivity pitfalls.

## The Internal Mechanism of `computed` with `ref` Objects

### Compiler-Level Auto-Import and Registration

The Single-File Component (SFC) compiler in `packages/compiler-sfc` automatically detects usage of `computed` within `<script setup>` blocks. According to the source code, the compiler treats `computed` as a macro-type import, injecting the necessary dependency resolution without requiring explicit manual imports in many configurations. This registration ensures that any top-level `computed` declaration becomes part of the component's public instance and template context.

### Runtime Wrapper in [`apiComputed.ts`](https://github.com/vuejs/core/blob/main/apiComputed.ts)

The actual `computed` function exported for component usage resides in **[`packages/runtime-core/src/apiComputed.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiComputed.ts)**. This wrapper extends the base reactivity implementation with Vue-specific behaviors:

- **SSR Awareness**: The wrapper accepts `isInSSRComponentSetup` as a third argument, allowing the reactivity layer to suppress browser-only side effects during server-side rendering.
- **Recursive Dependency Warnings**: In development mode (`__DEV__`), if `warnRecursiveComputed` is enabled in the app configuration, the wrapper sets a `_warnRecursive` flag on the `ComputedRefImpl` instance to detect circular dependencies.

```typescript
// packages/runtime-core/src/apiComputed.ts
export const computed: typeof _computed = (getterOrOptions, debugOptions?) => {
  const c = _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
  if (__DEV__) {
    const i = getCurrentInstance()
    if (i && i.appContext.config.warnRecursiveComputed) {
      (c as unknown as ComputedRefImpl<any>)._warnRecursive = true
    }
  }
  return c as any
}

```

### Core Reactivity Implementation

The underlying logic executes in **[`packages/reactivity/src/computed.ts`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/computed.ts)**, where the `ComputedRefImpl` class manages lazy evaluation and caching. When you pass a getter function accessing `ref` values (e.g., `count.value`), the implementation:

1. Creates a `ReactiveEffect` that tracks all dependencies accessed during getter execution.
2. Maintains a `dirty` flag to cache results until dependencies mutate.
3. Automatically triggers updates through the `track` and `trigger` system when underlying refs change.

```typescript
// packages/reactivity/src/computed.ts (simplified)
export function computed(getter) {
  let dirty = true
  let value
  const effect = new ReactiveEffect(getter, () => {
    if (!dirty) {
      dirty = true
      trigger(computedRef, 'set')
    }
  })
  const computedRef = {
    get value() {
      if (dirty) {
        dirty = false
        value = effect.run()
      }
      track(computedRef, 'get')
      return value
    }
  } as ComputedRef
  return computedRef
}

```

### Template Exposure and Automatic Unwrapping

Because `<script setup>` exposes top-level variables automatically, the compiler adds `computed` declarations to the component's render context. When the template accesses `{{ double }}` (where `double` is a computed property based on a `ref`), Vue automatically unwraps the `ComputedRef` without requiring `.value` syntax in the template.

## Common Use Cases for Vue 3 Computed Properties with Ref Objects

**Derived Values from Multiple Refs**

Use `computed` to combine multiple `ref` objects into a single derived state. The calculation only re-executes when specific dependencies change, not on every render.

**Expensive Calculations**

Cache heavy computational logic—such as array reductions or object transformations—that depends on reactive state. The getter runs only when the underlying `ref` values mutate, preventing performance bottlenecks during component re-renders.

**Read-Only Derived State**

`computed` returns a read-only `ComputedRef`, enforcing immutability. This pattern protects derived state from accidental mutations while allowing the template and script to react to changes in source refs.

**SSR-Safe Computations**

The [`apiComputed.ts`](https://github.com/vuejs/core/blob/main/apiComputed.ts) wrapper automatically handles server-side rendering constraints, ensuring computed values hydrate consistently between server and client without executing browser-specific side effects during SSR.

## Practical Implementation Examples

Simple derived value combining refs:

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

const firstName = ref('Jane')
const lastName = ref('Doe')

// Updates only when firstName or lastName changes
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>

<template>
  <p>{{ fullName }}</p>
  <input v-model="firstName" placeholder="First name"/>
  <input v-model="lastName" placeholder="Last name"/>
</template>

```

Expensive calculation with caching:

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

const numbers = ref([1, 2, 3, 4, 5])

// Recomputes only when numbers array reference changes
const sum = computed(() => {
  console.log('recomputing sum')
  return numbers.value.reduce((a, b) => a + b, 0)
})
</script>

<template>
  <p>Sum: {{ sum }}</p>
  <button @click="numbers.push(numbers.length + 1)">Add number</button>
</template>

```

SSR-aware boolean computation:

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

const counter = ref(0)
const isEven = computed(() => counter.value % 2 === 0)
</script>

<template>
  <p>{{ isEven ? 'Even' : 'Odd' }}</p>
  <button @click="counter++">Increment</button>
</template>

```

## Key Source Files in vuejs/core

Understanding the `computed` implementation requires examining these specific files:

- **[`packages/runtime-core/src/apiComputed.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiComputed.ts)**: Vue-specific wrapper providing SSR context and development warnings for recursive dependencies.
- **[`packages/reactivity/src/computed.ts`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/computed.ts)**: Core implementation containing `ComputedRefImpl`, lazy effect scheduling, and dependency tracking.
- **[`packages/compiler-sfc/src/script/analyzeScriptBindings.ts`](https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/script/analyzeScriptBindings.ts)**: Detects `computed` declarations to expose them to the template scope during compilation.
- **[`packages/compiler-sfc/src/script/resolveType.ts`](https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/script/resolveType.ts)**: Handles TypeScript type resolution for `computed` return types in `<script setup>`.

## Summary

- **`computed`** in `<script setup>` creates a **lazy, cached derived state** that tracks `ref` dependencies automatically.
- The implementation flows through three layers: **compiler auto-import**, **runtime SSR wrapper** ([`apiComputed.ts`](https://github.com/vuejs/core/blob/main/apiComputed.ts)), and **core reactivity engine** ([`computed.ts`](https://github.com/vuejs/core/blob/main/computed.ts)).
- **Caching** occurs via a dirty-check mechanism in `ComputedRefImpl`, preventing unnecessary recalculation until dependencies mutate.
- **Common use cases** include deriving values from multiple refs, expensive calculations, enforcing read-only state, and maintaining SSR consistency.
- Top-level `computed` declarations in `<script setup>` are **automatically exposed** to templates without manual return statements.

## Frequently Asked Questions

### How does `computed` differ from a regular method in the template?

A regular method executes on every component render, regardless of whether dependencies changed. In contrast, `computed` caches the result and only re-evaluates when reactive dependencies tracked within its getter function mutate. According to the vuejs/core source, this optimization occurs because `ComputedRefImpl` maintains a `dirty` flag that gates execution until the effect scheduler detects a change.

### Can I directly assign a value to a computed property?

No. When created with a getter function, `computed` returns a read-only `ComputedRef`. Attempting to set `.value` on a read-only computed triggers a warning in development mode. If you need writable computed properties, you must provide both getter and setter functions to the `computed` call.

### Why isn't my computed property updating when the ref changes?

Ensure you are accessing the `.value` property of the `ref` inside the computed getter. The reactivity system only tracks dependencies that are explicitly accessed during the getter's execution. If you pass the ref object itself without unwrapping it, the dependency tracking fails because the object reference remains stable while its internal value changes.

### Is `computed` automatically SSR-safe in Vue 3?

Yes. The wrapper in [`packages/runtime-core/src/apiComputed.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiComputed.ts) automatically passes `isInSSRComponentSetup` to the reactivity core. This flag ensures that computed effects avoid browser-specific side effects during server-side rendering, maintaining consistent hydration behavior without requiring manual environment checks in your component code.