skip to content

In Vue 3's Options API, how does the extends option differ from mixins, and what does an extending component not inherit?

level: middleimportance: nice to knowfreq 26%

answer

  1. single base, not an array
  2. folded in as the first mixin
  3. setup is not merged
  4. render comes from the component itself

basics

~20 s

Vue 3's extends takes one base component and merges it exactly like a first mixin; the difference is intent, inheritance versus composing features. It does not merge setup(), ignores expose, and the component's render function is not inherited.

solid answer

~50 s

Mechanically `extends` is almost the same as `mixins`: it takes a single options object and Vue folds it in as though it were the first mixin, before the `mixins` array and before the component's own options, using the same per-option strategies. The difference is intent: `mixins` composes chunks of behaviour, `extends` models a base component. What is not inherited matters most. `setup()` is not merged, so only the component's own `setup()` runs — extending a `<script setup>` component inherits none of its logic. `expose` declared on a base is ignored with a dev warning. And the runtime takes `render` from the component's own definition, so an extending component without its own template renders nothing and warns that it is missing a template or render function, unless the base supplied a template string and the runtime compiler is present.

code

js · 19 lines
js
import BaseTable from './BaseTable.vue' // an SFC: its template is compiled to BaseTable.render

// Broken: inherits data, methods and hooks, but no render function
export const UsersTableBroken = {
  extends: BaseTable,
  methods: {
    sort() { /* override */ }
  }
}
// dev warning: Component is missing template or render function

// Works: the extending component supplies its own render function
export const UsersTable = {
  extends: BaseTable,
  methods: {
    sort() { /* override */ }
  },
  render: BaseTable.render
}

go deeper

for a junior

Recall that extends takes one base component while mixins takes a list, and that both merge options into the component.

for a middle

Explain that extends is folded in as the first mixin, and name what does not travel: setup(), expose and the render function.

for a senior

Show you can spot a broken extension in legacy code — a blank component with a missing-render warning, or lost script setup logic — and replace it with a composable plus a wrapper.

for a principal

Argue for composition over inheritance as a team rule: extension hierarchies hide overrides and hooks, and composables keep every dependency explicit.

## What extends is In Vue 3's Options API, `extends` names **one** component definition that the current component builds on: ```js const BaseTable = { data() { return { sortKey: 'name' } }, methods: { sort() { /* … */ } } } const UsersTable = { extends: BaseTable, methods: { sort() { /* override */ } } } ``` The Vue API reference describes it as almost identical to `mixins` from an implementation perspective: the extended component is treated as though it were the **first mixin**. All of the usual per-option merge strategies apply. ## Extends versus mixins | Aspect | `extends` | `mixins` | |---|---|---| | Value | One options object | An array of options objects | | Fold position | First, before any mixin | After `extends`, in array order | | Intent | Inheritance: "this component is a kind of that one" | Composition: "this component also has these features" | | Merge strategies | Same per-option rules | Same per-option rules | | `setup()` | Not merged | Not merged | Because `extends` is folded in first, a mixin can override a base method, and the component's own options override both. Lifecycle hooks accumulate in the same order: global mixins, then the base, then mixins, then the component. ## What is not inherited This is where interview answers go wrong. Several things do **not** travel through `extends`: - **`setup()`**. Vue calls only the `setup` declared on the component's own definition. A base component's `setup()` is never called, and the API reference warns that `extends` is designed for the Options API and does not handle merging `setup()`. Extending a component written with `<script setup>` therefore inherits none of its state or logic — that component's whole implementation lives in its setup. - **`expose`**. An `expose` list declared on a base or a mixin is ignored, with the dev warning `"expose" option is ignored when declared in mixins or extends. It should only be declared in the base component itself.` - **The render function.** The runtime reads `render` from the component's own definition. A single-file component's template is compiled into a `render` function attached to that component, so an extending component with no template of its own gets **no** render function and Vue warns `Component is missing template or render function`. Only a base declaring a runtime `template` **string**, with a build that includes the runtime compiler, passes its template through the merged options. ## What does travel Everything the options merge knows about comes through `extends`: `data` (merged one level deep), `methods` and `computed` (the child's same-named entries win), lifecycle hooks and `watch` handlers (the base's run first), `props` and `emits` (combined, the child's definition of a same-named prop winning), `provide` and `inject`, and locally registered `components` and `directives`. So the child can rely on the base's props and state being present on `this`, and can override any method. What it cannot do is opt out of a base hook: the base's `mounted` runs whether or not the child declares its own. ## Why it is discouraged The docs mark `extends` as not recommended for Composition API code and recommend extracting reusable logic into a **composable** instead: prefer composing over inheriting. Beyond the setup gap, inheritance through options has the usual mixin costs: 1. Overrides are silent — a method in the child replaces the base's with no marker. 2. The base's hooks always run, and the child cannot skip them. 3. Reading the child alone does not tell you what `this` contains. ## When you still meet it - Legacy Options API codebases that modelled a family of form fields or tables as a base component. - Libraries that ship a base component expecting consumers to extend it. - Wrapping a third-party options component to override one method. In each case the safe practice is to give the extending component its own template or render function, and never rely on `setup()` or `expose` travelling from the base. ## How to answer State that `extends` is merged as the first mixin, so the difference from `mixins` is intent and arity, not mechanics. Then list the three gaps — `setup()`, `expose`, and the render function — and explain that composables replace it for new code.

  • If a component uses both extends and mixins and all three define mounted, in what order do the hooks run?
    Global mixins registered with `app.mixin()` run first, then the extended base's `mounted`, then each mixin's in array order, then the component's own. Vue 3 folds `extends` in before the `mixins` array and concatenates same-named lifecycle hooks, so all of them run in that order.
  • What should replace extends when a new component needs a base component's logic?
    Extract the shared logic into a composable and call it from each component's `setup()` or `<script setup>`; share markup by wrapping the base component and passing props and slots. The Vue docs recommend composition over inheritance here, partly because `extends` never merges `setup()`.

saying these in an interview costs you the question

  • extends accepts an array of base components, like mixins.
  • extends inherits the base component's setup() along with its options.
  • An extending component always renders the base component's template.
  • extends is merged after mixins, so the base overrides mixin methods.
  • extends uses a different merge strategy from mixins for methods and data.