Skip to the solution
MediumFrontend9 min read

What is defineExpose?

675 views

The problem

Question presented to candidate: "I have a <script setup> SearchField component and the parent page needs to focus it and clear it from a keyboard shortcut. I put ref="field" on it, but field.value.focus is undefined. What is defineExpose, and how would you use it here?"

What a strong answer should cover:

  • defineExpose({ ... }) is a <script setup> compiler macro that lists what a parent can reach through a template ref on this component; nothing else is visible.
  • Typical exposed members are imperative methods: focus(), clear(), validate(), open() / close(), scrollToTop().
  • Exposed refs and computeds are unwrapped on the parent side (field.value.length is a number, not a ref).
  • Rules: call it once, at the top level of <script setup>; it compiles to the expose() function from the setup context, and defineOptions cannot declare expose.
  • The parent can only use it after mount (the ref is null before), and in TypeScript the exposed shape is inferred, so calling a non-exposed member is a type error.
  • Prefer props and events for data; keep the exposed API small and command-shaped.

Clarifying questions expected:

  • "Does the parent only need to trigger actions, or also read state from the child?"
  • "Is the shortcut handled before the field is mounted, for example while it is behind a v-if?"

Code / implementation expected: Yes β€” a child using defineExpose with one or two methods and a parent calling them through a template ref.

  • defineexpose

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 9 sections

Read the code

VueA search field that exposes focus(), clear() and a length computed to its parent
<script setup>
import { ref, computed, defineComponent, h } from "vue";

// The child keeps its state private and publishes a small imperative API.
// Inside a <script setup> file this would be: defineExpose({ focus, clear, length })
const SearchField = defineComponent({
  emits: ["search"],
  setup(_, { expose, emit }) {
    const query = ref("");
    const inputEl = ref(null);
    const length = computed(() => query.value.length);

    const focus = () => inputEl.value.focus();
    const clear = () => { query.value = ""; focus(); };
    expose({ focus, clear, length });

    return () =>
      h("input", {
        ref: inputEl,
        value: query.value,
        placeholder: "search",
        onInput: (e) => { query.value = e.target.value; emit("search", query.value); },
      });
  },
});

const field = ref(null);
const lastSearch = ref("");
const info = ref("");

function showInfo() {
  // Exposed refs and computeds are unwrapped: length is a plain number here.
  info.value = `length=${field.value.length} query=${field.value.query} focused=${document.activeElement?.tagName}`;
}
</script>

<template>
  <SearchField ref="field" @search="(q) => (lastSearch = q)" />
  <button class="focus" @click="field.focus()">focus</button>
  <button class="clear" @click="field.clear()">clear</button>
  <button class="info" @click="showInfo">inspect</button>
  <p>last search event: "{{ lastSearch }}" | {{ info }}</p>
</template>
VueCounter.vue with defineExpose and the Parent.vue that calls it
<!-- Counter.vue -->
<script setup>
import { ref } from "vue";
const count = ref(0);
const step = ref(5);
function reset() { count.value = 0; }
defineExpose({ reset });
</script>
<template><button @click="count += step">{{ count }}</button></template>

<!-- Parent.vue -->
<script setup>
import { ref, onMounted } from "vue";
import Counter from "./Counter.vue";
const counter = ref(null);
const seen = ref("");
onMounted(() => {
  seen.value = `reset:${typeof counter.value.reset} count:${counter.value.count} step:${counter.value.step}`;
});
</script>
<template><Counter ref="counter" /><p>{{ seen }}</p></template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 43 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

How do you set up Vue Router (routes, RouterLink, RouterView, history modes)?

Continue