SoFunction
Updated on 2025-04-07

How to customize v-model to public hooks in vue3

Preface

  • Basics: A brief introductionvue3ofsetupHow to customize syntaxv-model
  • Advanced: How to extractv-modelSyntax as a publichooks

Base

The basics can be bypassed, but the tutorials given by the official website are summarized and givendemo

Basic v-model

Custom bidirectional binding can be completed if two points are satisfied in the child component:

  • propsDefine a value inxxx
  • emitDefine one inupdate:xxxevent

Let's write a basic onev-modelComponents:

  • propsDefine one inmodelValuevalue and bind toinputofvalueAttributes;
  • emitDefine one inupdate:modelValueevent

It should be noted that whenmodelValueAspropsIncoming,update:modelValueEvents will be automatically registered toemitIn the event

<template>
  <input
    type="text"
    @input="emit('update:modelValue', $)"
    :value=""
  />
</template>

<script setup>
const emit = defineEmits();
const props = defineProps({
  modelValue: String,
});
</script>

In parent component, importmodelCompSubcomponents and bindingtestValue tov-modelOn, test completes a two-way binding.

<template>
  <modelComp v-model="test"></modelComp>
</template>

<script setup>
import { ref, watch } from "vue";
import modelComp from "./components/model/";
const test = ref("");
</script>

This is the most basic customizationv-modelcomponents;

Multiple v-model bindings

When we need multiple two-way bindings, as follows:

<modelComp
  v-model="test"
  v-model:test1="test1"
  v-model:test2="test2"
></modelComp>

<script setup>
import { ref, watch } from "vue";
import modelComp from "./components/model/";
const test = ref("");
const test1 = ref("");
const test2 = ref("");
</script>

In the subcomponent, it is also defined by two points:

  • propsdefine two values,test1andtest2
  • emitsdefines two events,update:test1andupdate:test2
<template>
  <input
    type="text"
    @input="emit('update:modelValue', $)"
    :value=""
  />
  <input
    type="text"
    @input="emit('update:test1', $)"
    :value="props.test1"
  />
  <input
    type="text"
    @input="emit('update:test2', $)"
    :value="props.test2"
  />
</template>

<script setup>
const emit = defineEmits(["update:modelValue","update:test1", "update:test2"]);
const props = defineProps({
  modelValue: String,
  test1: String,
  test2: String,
});
</script>

v-model modifier

vueProvide somev-modelModifiers, we canv-modelUse them in:

<modelComp
  ="test"
  v-model:="test1"
  v-model:="test2"
></modelComp>

In some scenarios, we need to define modifiers ourselves to meet our needs.Take a chestnut:

<modelComp
  ="test"
  v-model:="test1"
></modelComp>

defaultv-modelWe're boundaModifier,v-model:test1In the middle, bindbandcTwo modifiers;

For modifiers, we need to meet the following conditions:

  • For the defaultv-modelIn other words, it is necessarypropsDefine two values ​​in
    • modelValue
    • modelModifiers, accept modifierskeyvalue
  • For customizationv-model:xxxTo be honest,propsmiddle:
    • xxx
    • xxxModeifiers, accept modifierskeyvalue

From this, the above code:

&lt;template&gt;
  &lt;input type="text" @input="vModelInput" :value="" /&gt;
  &lt;input type="text" @input="vModelTest1" :value="props.test1" /&gt;
&lt;/template&gt;

&lt;script setup&gt;
const emit = defineEmits(["update:modelValue", "update:test1"]);
const props = defineProps({
  modelValue: String,
  //Accept the modifier of v-model  modelModifiers: {
    default: () =&gt; ({}),
  },
  test1: String,
  //Accept the modifier of v-model:test1  test1Modifiers: {
    default: () =&gt; ({}),
  }
});

const vModelInput = (e) =&gt; {
  let value = 
  ();
  //{a:true}
  if(){
      //Processing value  }
  emit("update:modelValue", value);
};

const vModelTest1 = (e) =&gt; {
  let value = 
  (props.test1Modifiers);
  //{b:true,c:true}
  if(){
      //Processing value  }
  if(){
      //Processing value  }
  emit("update:test1", value);
};
&lt;/script&gt;

Advanced

Problem background

The basics have already explained how to encapsulate a customv-modelcomponents, but in actual development, are used in subcomponents@inputand:valueIt will be more troublesome to bind our values. Is there an easier way?

We usually want to directly perform child components that require two-way bindingv-modelBind:

&lt;!-- Subcomponents --&gt;
&lt;input type="text" v-model="xxx" /&gt;

The problem is that when the child component receives the value passed from the parent component,xxxWho should we bind? Direct bindingIs it?

&lt;!-- Subcomponents --&gt;
&lt;input type="text" v-model=""/&gt;

We will get an error:

⚠️:512 Set operation on key "modelValue" failed: target is readonly.

becausepropsIt's onereadonlyThe value ofisReadonly(props) === true), so we cannot use it directly

So, we need an intermediate value to bindv-model

Method 1: Transfer through watch

Binding with internal variablesv-model,usewatchListen to it and synchronize the data

&lt;!-- Subcomponents --&gt;
&lt;template&gt;
  &lt;input type="text" v-model="proxy" /&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, watch } from "vue";
const emit = defineEmits();
const props = defineProps({
  modelValue: String,
});

const proxy = ref();

watch(
  () =&gt; ,
  (v) =&gt; emit("update:modelValue",v)
);
&lt;/script&gt;

Because sometimes we can bind two-way to an object or an array, so we can usewatchInsidedeepOptions for deep monitoring and synchronizationproxy;

watch(
  () => ,
  (v) => emit("update:modelValue",v),
  {deep:true}
);

certainly,There may be default values ​​passed in, so we can also add themimmediateOptions so that the component is directly given toproxyAssign default values;

Method 2: computed get and set

We can also use thecomputedProvidedgetandsetTo synchronize data

const proxy = computed({
  get() {
    return ;
  },
  set(v) {
    emit("update:modelValue", v);
  },
});

Ultimate: hooks that encapsulate v-model

Let's extract it firstwatchThis way, encapsulate it into onehooks

&lt;!-- Subcomponents --&gt;
&lt;template&gt;
  &lt;input type="text" v-model="proxy" /&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, watch, computed } from "vue";
const emit = defineEmits();
const props = defineProps({
  modelValue: String,
});

const proxy = ref();

watch(
  () =&gt; ,
  (v) =&gt; emit("update:modelValue", v)
);
&lt;/script&gt;

In the subcomponent, we usev-modelexistinputAn internal value is bound toproxy, andInitialization of the value ofproxyvariable(ref());

existwatchIn, we monitorinputThe binding value onproxy,existinputWhen the input value changes, distribute it to the outsideemit('update:modelValue',v)Event, dynamically pass the changed value to external components

Extract public logic

// 
import { ref, watch } from "vue";
export function useVmodel(props, emit) {
  const proxy = ref();
  watch(
    () => ,
    (v) => emit("update:modelValue", v)
  );
  return proxy;
}

The easiesthooksThen it was encapsulated;

<template>
  <input type="text" v-model="proxy" />
</template>

<script setup>
import { ref, watch, computed } from "vue";
import { useVmodel } from "./hooks/useVmodel1";
const emit = defineEmits();
const props = defineProps({
  modelValue: String,
});
const proxy = useVmodel(props, emit);
</script>

Continue to remove the package

Taking into account the following points, continue to extract the package:

  • emitCan not pass, a more concise call method
  • Multiplev-model:test1Events of this situation,emit("update:xxxx")In-housexxxxEvent name needs to be extracted

We can passvue3ProvidedgetCurrentInstancemethod, get the current component instance, andmodelValueCan be overridden, then extracted into variables:

//
import { ref, watch, getCurrentInstance } from "vue";
export function useVmodel(props, key = "modelValue", emit) {
  const vm = getCurrentInstance();
  const _emit = emit || vm?.emit;
  const event = `update:${key}`;
  const proxy = ref(props[key]);
  watch(
    () => ,
    (v) => _emit(event, v)
  );
  return proxy;
}

OK, now we can call ours more easilyhooksCome on:

&lt;!-- Subcomponents childModel --&gt;
&lt;template&gt;
  &lt;input type="text" v-model="modelValue" /&gt;
  &lt;input type="text" v-model="test" /&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useVmodel } from "./hooks/useVmodel2";
const emit = defineEmits();
const props = defineProps({
  modelValue: String,
  test: String,
});
const modelValue = useVmodel(props);
const test = useVmodel(props, "test");
&lt;/script&gt;

&lt;!-- Parent component --&gt;
&lt;template&gt;
  &lt;Model v-model="modelValue" v-model:test="test" /&gt;
&lt;/template&gt; 

&lt;script setup&gt;
import { ref, watch } from "vue";
import Model from "./";

const modelValue = ref("");
const test = ref("");
&lt;/script&gt;

This is the article about how to customize v-model as public hooks in vue3. For more related vue custom v-model content, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!