Can a Vue 3 component use the Options API and the Composition API together, and what rules govern that mix?
answer
- the setup option is the bridge
- runs before beforeCreate
- visibility goes one way
- script setup bindings stay hidden
- recommended only for existing options code
basics
~10 sYes, 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 sYes. 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 linesimport { 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
Recall that the setup() option lets an options component use the Composition API, and that its returned bindings appear on this.
Explain the order (setup before beforeCreate), the one-way visibility, ref unwrapping on this, and why script setup bindings do not reach options.
Use the mix as a migration stepping stone without leaving features split across both styles, and catch name shadowing in review.
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.