Hone logo
Hone
Problems

Implementing a Custom v-model Directive in Vue 3 (TypeScript)

Vue's v-model directive is a powerful feature for handling form input and component state synchronization. This challenge focuses on understanding and reimplementing this core Vue functionality yourself. You will create a custom directive that mimics the behavior of v-model for a simple text input component, demonstrating how data binding and event handling work together.

Problem Description

Your task is to create a custom Vue directive named v-model in TypeScript that enables two-way data binding between a parent component's data property and a child component's input element. This directive should listen for input changes on the element and update the parent's data, and also update the element's value when the parent's data changes.

Key Requirements:

  1. Directive Definition: Create a Vue directive (using app.directive) named v-model.
  2. Input Binding: The directive should bind the value property of the host element to the value passed to the directive.
  3. Event Listening: The directive should listen for the input event on the host element.
  4. Data Update: When the input event fires, the directive should emit a custom event (update:modelValue) with the new value. This event is how Vue's v-model typically works under the hood in Composition API components.
  5. TypeScript Support: The directive and any associated types should be written in TypeScript.
  6. Component Integration: Demonstrate the directive's usage within a simple Vue component that has a data property and an input element.

Expected Behavior:

When the user types into the input field, the data property in the parent component should update in real-time. Conversely, if the parent component's data property is programmatically changed, the input field's value should reflect that change.

Edge Cases:

  • Consider how the directive should handle initial value setting.
  • Ensure the directive correctly detaches event listeners when the element is unmounted.

Examples

Example 1:

Parent Component (Vue SFC):

<template>
  <div>
    <input v-model="message" />
    <p>Current message: {{ message }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const message = ref('Initial value');
    return {
      message,
    };
  },
});
</script>

Custom v-model directive implementation (conceptual, as it would be registered globally or locally):

When the user types "Hello" into the input:

Output:

The input field displays "Hello". The message ref in the parent component updates to "Hello". The displayed text "Current message: Hello" updates accordingly.

Explanation:

The v-model directive on the input element connects the message ref to the input. As the user types, the input event is triggered, and our custom directive captures this event. It then emits an update:modelValue event, which Vue's v-model system uses to update the message ref.

Example 2:

Parent Component (Vue SFC):

<template>
  <div>
    <input v-model="count" type="number" />
    <p>Current count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const count = ref(0);

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment,
    };
  },
});
</script>

Custom v-model directive implementation (conceptual)

Initial state: count is 0. Input displays "0". "Current count: 0".

User clicks "Increment" button: count.value becomes 1.

Output:

The input field automatically updates to "1". The displayed text "Current count: 1" updates accordingly.

Explanation:

When the count ref is updated programmatically (via the increment function), Vue's reactivity system detects the change. Our custom v-model directive, which is listening for changes to the bound value, receives the updated count and sets the value property of the input element to 1.

Constraints

  • The implementation should use Vue 3's Composition API for directive definition.
  • The directive should be written entirely in TypeScript.
  • Focus on basic text input (<input type="text">, <input type="number">, <textarea>). Do not worry about checkbox or radio button behavior for this challenge.
  • The directive should be capable of handling string and number types for the bound value.

Notes

  • You'll need to register your custom directive with your Vue application instance (e.g., using app.directive).
  • Consider the binding object provided to the directive's hooks (mounted, updated, beforeUnmount).
  • The binding.value will hold the current data property value.
  • The el argument in the directive hooks refers to the DOM element the directive is attached to.
  • For handling updates, you'll likely want to listen for the DOM event and then emit a custom event. Vue's v-model on components typically uses an update:modelValue event. You can simulate this behavior for a directive by directly accessing the parent component's context (though in a real-world scenario, you'd likely be building a component that uses v-model, not replacing the directive itself for standard HTML elements). However, for the purpose of this exercise, focus on updating the el.value and emitting an event that could be caught by a parent if it were structured differently or if you were extending Vue's internal v-model logic. A more practical interpretation for a custom directive would be to emit an event that a parent component could listen to and then update its own state. For simplicity in this challenge, let's aim to make the el.value update correctly and conceptually mimic the emission.
  • Think about how to remove the event listener when the element is unmounted to prevent memory leaks.
Loading editor...
typescript