skip to content

Can a Vue 3 component use the Options API and the Composition API together, and what rules govern that mix?

level: middleimportance: should knowfreq 45%

answer

  1. the setup option is the bridge
  2. runs before beforeCreate
  3. visibility goes one way
  4. script setup bindings stay hidden
  5. recommended only for existing options code

basics

~10 s

Yes, through the setup() option: it runs before every options hook, and what it returns is available on this to options; setup() cannot read options, and <script setup> bindings never reach the Options API.

solid answer

~40 s

Yes. An Options API component can declare a `setup()` option, call composables in it, and return their bindings. Three rules follow. `setup()` runs **before any options hook**, `beforeCreate` included, and `this` is `undefined` inside it, so it cannot read `data`, `computed` or `methods`. Visibility goes **one way**: whatever `setup()` returns is available to the template and on `this` in `computed`, `methods`, `watch` and hooks, with refs unwrapped. And variables in `<script setup>` are **not** added to the instance, so options code in a neighbouring `<script>` block cannot see them; the docs strongly discourage that combination. The docs recommend mixing only when an existing Options API codebase needs Composition API features or libraries.

code

js · 18 lines
js
import { useOnline } from './useOnline'

export default {
  props: { userId: Number },
  setup() {
    const { isOnline } = useOnline() // a composable adopted by an options component
    return { isOnline }
  },
  computed: {
    status() {
      return this.isOnline ? 'online' : 'offline' // setup binding read through this, unwrapped
    }
  },
  mounted() {
    // runs after setup(); setup() could not have read this.status
    console.log(this.status)
  }
}

go deeper

for a junior

Recall that the setup() option lets an options component use the Composition API, and that its returned bindings appear on this.

for a middle

Explain the order (setup before beforeCreate), the one-way visibility, ref unwrapping on this, and why script setup bindings do not reach options.

for a senior

Use the mix as a migration stepping stone without leaving features split across both styles, and catch name shadowing in review.

for a principal

Decide how long a codebase may live in the mixed state and what the exit criteria are for converting components fully.

## The bridge: the setup() option A Vue 3 component written with options can also declare `setup()`. This is the supported way to use the Composition API inside an Options API component, and the reason it works is that both styles run on one system; the Options API is implemented on top of the Composition API. The typical use is adopting a composable, a shared one or one from a library, in a component that is otherwise written with options. ## The rules of the mix 1. **Order.** `setup()` runs first, before any Options API lifecycle hook, even `beforeCreate()`. Options such as `data`, `computed` and `methods` are applied after it. 2. **No `this` in setup.** `this` is `undefined` inside `setup()`. Options state does not exist yet, and there is no route from setup to it. 3. **One-way visibility.** Bindings that `setup()` returns are exposed to the template and to options code through `this`, with refs **shallow-unwrapped**, so a method writes `this.x`, not `this.x.value`. 4. **Name resolution.** When a name is returned from `setup()` and also defined elsewhere, such as in `data()`, the instance proxy resolves the `setup()` binding first. Avoid duplicate names; the result surprises readers. 5. **`<script setup>` does not bridge.** Variables declared in `<script setup>` are not added as properties of the component instance, so an options object in a separate `<script>` block cannot read them. The docs strongly discourage mixing APIs that way. ## A worked example ```vue <script> import { useMouse } from './useMouse' export default { data() { return { clicks: 0 } }, setup() { const { x, y } = useMouse() // composable called in setup() return { x, y } // now on `this` and in the template }, methods: { record() { this.clicks++ console.log(`click at ${this.x}, ${this.y}`) // refs unwrapped on this } } } </script> <template> <button @click="record">Clicked {{ clicks }} times at {{ x }}, {{ y }}</button> </template> ``` ## When the mix is appropriate | Situation | Recommendation | |---|---| | Existing options component needs a composable | `setup()` option returning its bindings | | New component | pick one style for the whole component | | Incremental migration of a large options codebase | `setup()` as a stepping stone, then convert the component | | `<script setup>` plus options in a second `<script>` block | avoid; switch to an explicit `setup()` if options are needed | The docs' guidance is narrow: use both APIs in one component when an existing Options API codebase needs to integrate new features or external libraries written with the Composition API. ## Pitfalls reviewers look for - **Reading options from setup.** Writing `this.items` inside `setup()` fails because `this` is `undefined`. Move the logic into setup or leave it in options. - **Forgetting to return.** A composable called in `setup()` but not returned is invisible to options and the template. - **Split ownership.** Half of a feature in `data`/`methods` and half in `setup()` is harder to read than either style alone. Keep each concern on one side. - **Name clashes.** A `setup()` binding silently shadows a `data()` property of the same name. ## Why the direction is one-way `setup()` exists to create state, not to consume the instance. Keeping it free of `this` is what lets the same code be moved into a composable unchanged. Options code, on the other hand, always runs against the instance proxy, which is exactly where `setup()` bindings are published, so options can see setup but not the reverse.

  • If setup() returns count and data() also returns count, what does this.count read in a Vue 3 component?
    The `setup()` binding. The instance proxy checks setup state before `data`, props and other context, so the `data()` property is shadowed. Nothing breaks loudly, which is why duplicate names across the two styles should be avoided.
  • Why can't an options component read variables from a <script setup> block in the same file?
    Variables in `<script setup>` are compiled into the setup function's scope and are not added as properties of the component instance, so options code reading `this` never sees them. If a component needs both, use an explicit `setup()` option and return the bindings.

saying these in an interview costs you the question

  • Mixing the two styles in one component is not allowed in Vue 3.
  • setup() runs after data() and can read it through this.
  • Options code must use this.x.value for refs returned from setup().
  • Variables in script setup are visible to options in a separate script block.
  • Mixing styles in every new component is the recommended default.