Can mix Composition API and Options API in same component
You can use Composition API via the setup() option in an Options API component, allowing both APIs in the same component. However, this is only recommended if you have an existing Options API codebase that needs to integrate with new features or external libraries written with Composition API.
Options API will not be deprecated
Options API is an integral part of Vue and is not planned to be deprecated. Many developers love it, and the benefits of Composition API primarily manifest in larger-scale projects. Options API remains a solid choice for low-to-medium-complexity scenarios.
Composition API definition and scope
Composition API is a set of APIs that allows authoring Vue components using imported functions instead of declaring options. It covers three main areas: Reactivity API (ref(), reactive() for reactive state), Lifecycle Hooks (onMounted(), onUnmounted() for component lifecycle), and Dependency Injection (provide(), inject() for Vue's DI system). It is a built-in feature of Vue 3 and Vue 2.7, and for older Vue 2 versions, the @vue/composition-api plugin is available.
Composition API comparison with React Hooks - setup only called once
Composition API setup() or <script setup> code is invoked only once, unlike React Hooks which are invoked repeatedly on every component update. This makes Composition API code align better with idiomatic JavaScript and eliminates stale closure problems.
Composition API comparison with React Hooks - no call order sensitivity
Composition API calls are not sensitive to call order and can be conditional, unlike React Hooks which are call-order sensitive and cannot be conditional.
Composition API automatic dependency collection in reactivity system
Vue's runtime reactivity system automatically collects reactive dependencies used in computed properties and watchers, so there is no need to manually declare dependencies like in React's useEffect or useMemo which require a dependencies array.
Composition API provides more flexible code organization
Composition API allows organizing code by logical concerns rather than by option type. Code dealing with the same logical concern can be grouped together in one place, reducing the need to scroll through the file and making refactoring into reusable utilities easier. This is particularly valuable for complex components with multiple logical concerns.
Composition API has better TypeScript support than Options API
Composition API uses mostly plain variables and functions which are naturally type-friendly, enabling full type inference with minimal manual type hints. Code written in Composition API looks largely identical in TypeScript and JavaScript. Options API required complex type gymnastics and still breaks down with mixins and dependency injection.
Composition API covers all stateful logic use cases
Composition API covers all stateful logic use cases. When using Composition API, only a few options may still be needed: props, emits, name, and inheritAttrs. Since Vue 3.3, you can directly use defineOptions in <script setup> to set the component name or inheritAttrs property.
Composition API is not functional programming
Despite being based on function composition, Composition API is NOT functional programming. It is based on Vue's mutable, fine-grained reactivity paradigm, whereas functional programming emphasizes immutability.
Composition API basic example with script setup
Example showing Composition API usage: import ref and onMounted from 'vue', declare reactive state with ref(0), create functions to mutate state, use lifecycle hooks. Template accesses count directly via {{ count }} and calls increment() on click.
Primary advantage of Composition API is logic reuse
The primary advantage of Composition API is that it enables clean, efficient logic reuse through Composable functions, solving all drawbacks of mixins. This has led to community projects like VueUse and provides a clean mechanism for integrating stateful third-party services into Vue's reactivity system.
Reactivity Transform composition-API-specific
Reactivity Transform is a Composition-API-specific feature and requires a build step.
TypeScript support for Reactivity Transform macros
Vue provides typings for Reactivity Transform macros which are available globally. To use them, explicitly reference the types in a file like env.d.ts with: /// <reference types="vue/macros-global" />. When explicitly importing macros from vue/macros, types work without declaring globals. The syntax works with all existing TypeScript tooling.
Composition API overview
With Composition API, component logic is defined using imported API functions. In Single-File Components, Composition API is typically used with <script setup>. The setup attribute makes Vue perform compile-time transforms that allow using Composition API with less boilerplate. Imports and top-level variables/functions declared in <script setup> are directly usable in the template.
Composables for code organization
Composables can be extracted not only for reuse, but also for code organization. As component complexity grows, you can organize component code into smaller functions based on logical concerns.
Using composables with Options API
When using Options API, composables must be called inside setup(), and the returned bindings must be returned from setup() so they are exposed to this and the template:
```js
import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
setup() {
const { x, y } = useMouse()
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
console.log(this.x)
}
}
```
Composables vs Mixins drawbacks
Mixins have three primary drawbacks compared to composables: (1) Unclear source of properties—using many mixins makes it unclear which property is injected by which mixin; (2) Namespace collisions—multiple mixins can register the same property keys causing collisions, while composables allow renaming destructured variables; (3) Implicit cross-mixin communication—mixins that need to interact rely on shared property keys making them implicitly coupled, while composables can pass values as arguments like normal functions.
Composables vs Renderless Components
The main advantage of composables over renderless components is that composables do not incur the extra component instance overhead. When used across an entire application, the amount of extra component instances created by the renderless component pattern can become a noticeable performance overhead. Use composables when reusing pure logic, and use components when reusing both logic and visual layout.
Composables vs React Hooks
Vue composables are similar to React hooks in terms of logic composition capabilities, and Composition API was in part inspired by React hooks. However, Vue composables are based on Vue's fine-grained reactivity system, which is fundamentally different from React hooks' execution model.
Composable input argument handling best practice
If you are writing a composable that may be used by other developers, it's a good idea to handle the case of input arguments being refs or getters instead of raw values. The toValue() utility function is helpful for this purpose. If your composable creates reactive effects when the input is a ref or getter, make sure to either explicitly watch the ref/getter with watch(), or call toValue() inside a watchEffect() so that it is properly tracked.
Reactive state acceptance in composables
This example shows how to refactor a composable to accept refs, getters, or plain values as input using watchEffect() and toValue():
```js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const fetchData = () => {
data.value = null
error.value = null
fetch(toValue(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
watchEffect(() => {
fetchData()
})
return { data, error }
}
```
toValue(url) is called inside the watchEffect callback to ensure reactive dependencies are tracked by the watcher.
Composable definition
A composable is a function that leverages Vue's Composition API to encapsulate and reuse stateful logic.
Composable naming convention
Composable function names should use camelCase and start with 'use' by convention.
Mouse tracker composable example
This example shows how to extract mouse tracking logic into a reusable composable:
```js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
```
Used in a component with `const { x, y } = useMouse()`.
Composables can be nested
One composable function can call one or more other composable functions, enabling composition of complex logic using small, isolated units.
Event listener composable example
This example shows how to create a reusable composable for adding and removing DOM event listeners:
```js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
```
Composable instance isolation
Each component instance calling a composable will create its own copies of the composable's state, so they won't interfere with one another. To manage shared state between components, use the State Management chapter.
Async fetch composable example
This example shows a basic composable for handling async data fetching:
```js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
```
toValue() utility for normalizing refs and getters
The toValue() API (added in 3.3) normalizes refs or getters into values. If the argument is a ref, it returns the ref's value; if the argument is a function, it calls the function and returns its return value; otherwise, it returns the argument as-is. It works similarly to unref() but with special treatment for functions.
Composable return value recommendation
The recommended convention is for composables to always return a plain, non-reactive object containing multiple refs. This allows destructuring in components while retaining reactivity. Returning a reactive object causes destructures to lose the reactivity connection.
Wrapping composable return with reactive()
If you prefer to use returned state from composables as object properties instead of destructuring, you can wrap the returned object with reactive() so that the refs are unwrapped. For example: `const mouse = reactive(useMouse())` makes `mouse.x` linked to the original ref.
Side effects in composables with SSR
When working on an application that uses Server-Side Rendering (SSR), perform DOM-specific side effects in post-mount lifecycle hooks like onMounted(). These hooks are only called in the browser, ensuring code inside them has access to the DOM.
Cleanup side effects in composables
Always clean up side effects in onUnmounted(). For example, if a composable sets up a DOM event listener, it should remove that listener in onUnmounted(). It is a good idea to use a composable that automatically does this, like useEventListener().
Composable usage restrictions
Composables should only be called in <script setup> or the setup() hook, and they should be called synchronously in these contexts. In some cases, you can also call them in lifecycle hooks like onMounted().
Composables after await in script setup
<script setup> is the only place where you can call composables after using await. The compiler automatically restores the active instance context for you after the async operation.
Default values with type-based props declaration
Type-based props declaration loses the ability to declare default values directly. In Vue 3.5+, Reactive Props Destructure solves this by using destructuring syntax: `const { msg = 'hello' } = defineProps<Props>()`. In Vue 3.4 and below, use the withDefaults compiler macro instead.
Runtime declaration vs type-based declaration for props
Vue props with TypeScript can be declared in two ways. Runtime declaration passes a runtime props object to defineProps(), where the compiler infers types from it. Type-based declaration passes a generic type argument to defineProps(), where the compiler infers runtime options from the type. You cannot use both approaches at the same time on the same component.
Runtime props declaration example
```vue
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
</script>
```
This example shows runtime declaration where the argument to defineProps() contains the runtime props definition.
Type-based props declaration example
```vue
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
```
This example shows type-based declaration where types are passed as a generic argument to defineProps().
Props with separate interface
Props types can be moved to a separate interface for reusability. The interface can be defined locally or imported from another file, including relative imports, path aliases (e.g., `@/types`), or external dependencies. This feature requires TypeScript as a peer dependency of Vue.
Generic type parameter limitations for defineProps
In Vue 3.2 and below, the generic type parameter for defineProps() was limited to a type literal or a reference to a local interface. This limitation was resolved in Vue 3.3. The latest version supports referencing imported and a limited set of complex types in the type parameter position. However, conditional types that require actual type analysis are not supported, and you cannot use conditional types for the entire props object, only for individual prop types.
withDefaults compiler macro example
```ts
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
```
This example shows using withDefaults to provide default values for type-based props. Mutable reference types like arrays and objects should be wrapped in functions to avoid shared state between component instances.
Default values for mutable types must use functions
When using withDefaults, default values for mutable reference types like arrays or objects must be wrapped in functions to avoid accidental modification and external side effects. This ensures each component instance gets its own copy of the default value. This requirement does not apply when using default values with destructuring.
Props typing without script setup
When not using `<script setup>`, use defineComponent() to enable props type inference. The type of the props object passed to setup() is inferred from the props option.
PropType utility for complex runtime props
For runtime props declaration with complex types, use the PropType utility type imported from 'vue'. This allows specifying complex type information at runtime: `book: Object as PropType<Book>`.
InjectionKey for provide/inject typing
Vue provides an InjectionKey interface, a generic type extending Symbol, to sync types between provider and consumer in provide/inject patterns. Create a key with `const key = Symbol() as InjectionKey<string>`, then use it: `provide(key, 'foo')` and `const foo = inject(key)`. It's recommended to place the injection key in a separate file for reuse.
String injection keys require explicit type declaration
When using string injection keys instead of InjectionKey, the type of the injected value defaults to `unknown` and must be explicitly declared via a generic type argument: `const foo = inject<string>('foo')`.
Injected value default type includes undefined
By default, injected values have a type that includes `undefined` (e.g., `string | undefined`), because there is no guarantee that a provider will provide the value at runtime. The `undefined` type can be removed by providing a default value: `const foo = inject<string>('foo', 'bar')` results in type `string`.
Force cast injected value
If you are certain that an injected value is always provided, you can force cast it: `const foo = inject('foo') as string`.
Composition API recommended over Options API for TypeScript
Vue recommends using Composition API with TypeScript instead of Options API because it offers simpler, more efficient, and more robust type inference.
Use defineComponent for TypeScript type inference
To let TypeScript properly infer types inside component options, you need to define components with defineComponent(). This function enables type inference for components defined in plain JavaScript as well.
defineComponent with Composition API example
import { defineComponent } from 'vue'
export default defineComponent({
// type inference enabled
props: {
message: String
},
setup(props) {
props.message // type: string | undefined
}
})
Add lang="ts" attribute to use TypeScript in SFCs
To use TypeScript in SFCs, add the lang="ts" attribute to <script> tags. When lang="ts" is present, all template expressions also enjoy stricter type checking.
SFC with TypeScript example
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
count: 1
}
}
})
</script>
<template>
<!-- type checking and auto-completion enabled -->
{{ count.toFixed(2) }}
</template>
lang="ts" works with <script setup>
lang="ts" can also be used with <script setup>.
defineComponent with Composition API setup function
defineComponent() also supports inferring the props passed to setup() when using Composition API without <script setup>.
<script setup lang="ts"> example
<script setup lang="ts">
// TypeScript enabled
import { ref } from 'vue'
const count = ref(1)
</script>
<template>
<!-- type checking and auto-completion enabled -->
{{ count.toFixed(2) }}
</template>
Generic components in defineComponent
Generic components are supported in render function / JSX components using defineComponent()'s function signature.
Generic components in <script setup>
Generic components are supported in SFCs using <script setup> with the generic attribute.