如何将Vue3的setup语法中自定义v-model改写为通用hooks实现?

2026-03-31 17:411阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2390个文字,预计阅读时间需要10分钟。

如何将Vue3的setup语法中自定义v-model改写为通用hooks实现?

目录+前言+基础+v-model的基本使用+多个v-model绑定+v-model修饰符+进阶+问题背景+方式一:通过watch中转换+方式二:computed的get和set+终极:封装v-model的hooks+提取公用逻辑+继承抽象封装+前言+基础篇

目录
  • 前言
  • 基础
    • 基本的v-model
    • 多个v-model绑定
    • v-model修饰符
  • 进阶
    • 问题背景
    • 方式一:通过watch中转
    • 方式二:computed的get和set
  • 终极:封装v-model的hooks
    • 提取公用逻辑
    • 继续抽离封装

前言

  • 基础篇:简单介绍vue3setup语法如何自定义v-model
  • 进阶篇:如何提取v-model语法作为一个公用hooks

基础

基础篇可绕过,只是对于官网给出的教程,进行了总结概括并给出demo

基本的v-model

子组件中满足两个点,即可完成自定义双向绑定:

  • props中定义一个值xxx
  • emit中定义一个update:xxx事件

下面我们来写一个最基本的v-model组件:

  • props中定义一个modelValue值,并绑定到inputvalue属性上;
  • emit中定义一个update:modelValue事件

需要注意的是,当modelValue作为props传入,update:modelValue事件将被自动注册到emit事件中

<template>   <input     type="text"     @input="emit('update:modelValue', $event.target.value)"     :value="props.modelValue"   /> </template> <script setup> const emit = defineEmits(); const props = defineProps({   modelValue: String, }); </script>

父组件中,引入modelComp子组件,并绑定test值到v-model上,test便完成了一次双向绑定。

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

这便是一个最基本的自定义v-model组件;

多个v-model绑定

当我们需要多个双向绑定时,如下:

<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/modelComp.vue"; const test = ref(""); const test1 = ref(""); const test2 = ref(""); </script>

子组件中,同样按着两个点来定义:

  • props中定义两个值,test1test2
  • emits中定义两个事件,update:test1update:test2

<template>   <input     type="text"     @input="emit('update:modelValue', $event.target.value)"     :value="props.modelValue"   />   <input     type="text"     @input="emit('update:test1', $event.target.value)"     :value="props.test1"   />   <input     type="text"     @input="emit('update:test2', $event.target.value)"     :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修饰符

vue提供了一些v-model修饰符,我们可以在v-model中使用他们:

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

在一些场景下,我们需要自己定义修饰符,来满足我们的需求,举个栗子:

<modelComp   v-model.a="test"   v-model:test1.b.c="test1" ></modelComp>

默认v-model中我们绑定了a修饰符,v-model:test1中则绑定bc两个修饰符;

对于修饰符,我们需要满足以下条件:

  • 对于默认v-model来说,需要props中定义两个值
    • modelValue
    • modelModifiers,接受修饰符key
  • 对于自定义v-model:xxx来说,props中:
    • xxx
    • xxxModeifiers,接受修饰符key

由此,上代码:

<template>   <input type="text" @input="vModelInput" :value="props.modelValue" />   <input type="text" @input="vModelTest1" :value="props.test1" /> </template> <script setup> const emit = defineEmits(["update:modelValue", "update:test1"]); const props = defineProps({   modelValue: String,   //接受v-model的修饰符   modelModifiers: {     default: () => ({}),   },   test1: String,   //接受v-model:test1的修饰符   test1Modifiers: {     default: () => ({}),   } }); const vModelInput = (e) => {   let value = e.target.value   console.log(props.modelModifiers);   //{a:true}   if(props.modelModifiers.a){       //处理value值   }   emit("update:modelValue", value); }; const vModelTest1 = (e) => {   let value = e.target.value   console.log(props.test1Modifiers);   //{b:true,c:true}   if(props.modelModifiers.b){       //处理value值   }   if(props.modelModifiers.c){       //处理value值   }   emit("update:test1", value); }; </script>

进阶

问题背景

基础篇中已经讲解了如何封装一个自定义v-model的组件,可是在实际开发中,子组件中使用@input:value来绑定我们的值,会比较麻烦,有没有更简单的办法呢?

我们通常想要对需要双向绑定的子组件,直接进行v-model绑定:

<!-- 子组件 --> <input type="text" v-model="xxx" />

问题来了,在子组件中接受到父组件的传值时,xxx我们应该绑定谁?直接绑定props.modelValue么?

<!-- 子组件 --> <input type="text" v-model="props.modelValue"/>

我们会得到一个错误:

⚠️reactivity.esm-bundler.js:512 Set operation on key "modelValue" failed: target is readonly.

因为props是一个readonly的值(isReadonly(props) === true),所以我们不能直接这么使用

所以,我们是需要一个中间值来绑定v-model

方式一:通过watch中转

借助内部变量绑定v-model,使用watch监听它,并同步数据props.xxx

<!-- 子组件 --> <template>   <input type="text" v-model="proxy" /> </template> <script setup> import { ref, watch } from "vue"; const emit = defineEmits(); const props = defineProps({   modelValue: String, }); const proxy = ref(props.modelValue); watch(   () => proxy.value,   (v) => emit("update:modelValue",v) ); </script>

因为有时候我们双向绑定的可能是一个对象或者数组,因此我们可以使用watch里的deep选项来深度监听并同步proxy;

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

当然,props.modelValue可能存在默认值传入,所以我们也可以加上immediate选项,使得组件在创建时,就直接给proxy赋上默认值;

方式二:computed的get和set

我们也可以借助computed提供的getset来进行数据同步

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

如何将Vue3的setup语法中自定义v-model改写为通用hooks实现?

终极:封装v-model的hooks

我们先来提取watch这种方式,将其封装为一个hooks

<!-- 子组件 --> <template>   <input type="text" v-model="proxy" /> </template> <script setup> import { ref, watch, computed } from "vue"; const emit = defineEmits(); const props = defineProps({   modelValue: String, }); const proxy = ref(props.modelValue); watch(   () => proxy.value,   (v) => emit("update:modelValue", v) ); </script>

在子组件中,我们用v-modelinput上绑定了一个内部值proxy,并以props.modelValue的值初始化proxy变量(ref(props.modelValue));

watch中,我们监听input上的绑定值proxy,在input进行输入其值变化时,向外分发emit('update:modelValue',v)事件,将改变的值动态传到外部组件上

提取公用逻辑

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

一个最简单的hooks便被封装好了;

<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>

继续抽离封装

考虑到以下几个点,继续进行抽离封装:

  • emit可以不传,更简洁的调用方式
  • 多个v-model:test1这种情况的事件,emit("update:xxxx")中的xxxx事件名需要提取

我们可以通过vue3提供的getCurrentInstance方法,获取当前的组件实例,而modelValue可覆盖,则抽取成变量:

//useVmodel2.js 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(     () => proxy.value,     (v) => _emit(event, v)   );   return proxy; }

好了,现在我们可以更简单的调用我们的hooks了:

<!-- 子组件 childModel --> <template>   <input type="text" v-model="modelValue" />   <input type="text" v-model="test" /> </template> <script setup> import { useVmodel } from "./hooks/useVmodel2"; const emit = defineEmits(); const props = defineProps({   modelValue: String,   test: String, }); const modelValue = useVmodel(props); const test = useVmodel(props, "test"); </script> <!-- 父组件 --> <template>   <Model v-model="modelValue" v-model:test="test" /> </template>  <script setup> import { ref, watch } from "vue"; import Model from "./childModel.vue"; const modelValue = ref(""); const test = ref(""); </script>

到此这篇关于vue3的setup语法如何自定义v-model为公用hooks的文章就介绍到这了,更多相关vue自定义v-model内容请搜索易盾网络以前的文章或继续浏览下面的相关文章希望大家以后多多支持易盾网络!

本文共计2390个文字,预计阅读时间需要10分钟。

如何将Vue3的setup语法中自定义v-model改写为通用hooks实现?

目录+前言+基础+v-model的基本使用+多个v-model绑定+v-model修饰符+进阶+问题背景+方式一:通过watch中转换+方式二:computed的get和set+终极:封装v-model的hooks+提取公用逻辑+继承抽象封装+前言+基础篇

目录
  • 前言
  • 基础
    • 基本的v-model
    • 多个v-model绑定
    • v-model修饰符
  • 进阶
    • 问题背景
    • 方式一:通过watch中转
    • 方式二:computed的get和set
  • 终极:封装v-model的hooks
    • 提取公用逻辑
    • 继续抽离封装

前言

  • 基础篇:简单介绍vue3setup语法如何自定义v-model
  • 进阶篇:如何提取v-model语法作为一个公用hooks

基础

基础篇可绕过,只是对于官网给出的教程,进行了总结概括并给出demo

基本的v-model

子组件中满足两个点,即可完成自定义双向绑定:

  • props中定义一个值xxx
  • emit中定义一个update:xxx事件

下面我们来写一个最基本的v-model组件:

  • props中定义一个modelValue值,并绑定到inputvalue属性上;
  • emit中定义一个update:modelValue事件

需要注意的是,当modelValue作为props传入,update:modelValue事件将被自动注册到emit事件中

<template>   <input     type="text"     @input="emit('update:modelValue', $event.target.value)"     :value="props.modelValue"   /> </template> <script setup> const emit = defineEmits(); const props = defineProps({   modelValue: String, }); </script>

父组件中,引入modelComp子组件,并绑定test值到v-model上,test便完成了一次双向绑定。

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

这便是一个最基本的自定义v-model组件;

多个v-model绑定

当我们需要多个双向绑定时,如下:

<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/modelComp.vue"; const test = ref(""); const test1 = ref(""); const test2 = ref(""); </script>

子组件中,同样按着两个点来定义:

  • props中定义两个值,test1test2
  • emits中定义两个事件,update:test1update:test2

<template>   <input     type="text"     @input="emit('update:modelValue', $event.target.value)"     :value="props.modelValue"   />   <input     type="text"     @input="emit('update:test1', $event.target.value)"     :value="props.test1"   />   <input     type="text"     @input="emit('update:test2', $event.target.value)"     :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修饰符

vue提供了一些v-model修饰符,我们可以在v-model中使用他们:

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

在一些场景下,我们需要自己定义修饰符,来满足我们的需求,举个栗子:

<modelComp   v-model.a="test"   v-model:test1.b.c="test1" ></modelComp>

默认v-model中我们绑定了a修饰符,v-model:test1中则绑定bc两个修饰符;

对于修饰符,我们需要满足以下条件:

  • 对于默认v-model来说,需要props中定义两个值
    • modelValue
    • modelModifiers,接受修饰符key
  • 对于自定义v-model:xxx来说,props中:
    • xxx
    • xxxModeifiers,接受修饰符key

由此,上代码:

<template>   <input type="text" @input="vModelInput" :value="props.modelValue" />   <input type="text" @input="vModelTest1" :value="props.test1" /> </template> <script setup> const emit = defineEmits(["update:modelValue", "update:test1"]); const props = defineProps({   modelValue: String,   //接受v-model的修饰符   modelModifiers: {     default: () => ({}),   },   test1: String,   //接受v-model:test1的修饰符   test1Modifiers: {     default: () => ({}),   } }); const vModelInput = (e) => {   let value = e.target.value   console.log(props.modelModifiers);   //{a:true}   if(props.modelModifiers.a){       //处理value值   }   emit("update:modelValue", value); }; const vModelTest1 = (e) => {   let value = e.target.value   console.log(props.test1Modifiers);   //{b:true,c:true}   if(props.modelModifiers.b){       //处理value值   }   if(props.modelModifiers.c){       //处理value值   }   emit("update:test1", value); }; </script>

进阶

问题背景

基础篇中已经讲解了如何封装一个自定义v-model的组件,可是在实际开发中,子组件中使用@input:value来绑定我们的值,会比较麻烦,有没有更简单的办法呢?

我们通常想要对需要双向绑定的子组件,直接进行v-model绑定:

<!-- 子组件 --> <input type="text" v-model="xxx" />

问题来了,在子组件中接受到父组件的传值时,xxx我们应该绑定谁?直接绑定props.modelValue么?

<!-- 子组件 --> <input type="text" v-model="props.modelValue"/>

我们会得到一个错误:

⚠️reactivity.esm-bundler.js:512 Set operation on key "modelValue" failed: target is readonly.

因为props是一个readonly的值(isReadonly(props) === true),所以我们不能直接这么使用

所以,我们是需要一个中间值来绑定v-model

方式一:通过watch中转

借助内部变量绑定v-model,使用watch监听它,并同步数据props.xxx

<!-- 子组件 --> <template>   <input type="text" v-model="proxy" /> </template> <script setup> import { ref, watch } from "vue"; const emit = defineEmits(); const props = defineProps({   modelValue: String, }); const proxy = ref(props.modelValue); watch(   () => proxy.value,   (v) => emit("update:modelValue",v) ); </script>

因为有时候我们双向绑定的可能是一个对象或者数组,因此我们可以使用watch里的deep选项来深度监听并同步proxy;

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

当然,props.modelValue可能存在默认值传入,所以我们也可以加上immediate选项,使得组件在创建时,就直接给proxy赋上默认值;

方式二:computed的get和set

我们也可以借助computed提供的getset来进行数据同步

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

如何将Vue3的setup语法中自定义v-model改写为通用hooks实现?

终极:封装v-model的hooks

我们先来提取watch这种方式,将其封装为一个hooks

<!-- 子组件 --> <template>   <input type="text" v-model="proxy" /> </template> <script setup> import { ref, watch, computed } from "vue"; const emit = defineEmits(); const props = defineProps({   modelValue: String, }); const proxy = ref(props.modelValue); watch(   () => proxy.value,   (v) => emit("update:modelValue", v) ); </script>

在子组件中,我们用v-modelinput上绑定了一个内部值proxy,并以props.modelValue的值初始化proxy变量(ref(props.modelValue));

watch中,我们监听input上的绑定值proxy,在input进行输入其值变化时,向外分发emit('update:modelValue',v)事件,将改变的值动态传到外部组件上

提取公用逻辑

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

一个最简单的hooks便被封装好了;

<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>

继续抽离封装

考虑到以下几个点,继续进行抽离封装:

  • emit可以不传,更简洁的调用方式
  • 多个v-model:test1这种情况的事件,emit("update:xxxx")中的xxxx事件名需要提取

我们可以通过vue3提供的getCurrentInstance方法,获取当前的组件实例,而modelValue可覆盖,则抽取成变量:

//useVmodel2.js 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(     () => proxy.value,     (v) => _emit(event, v)   );   return proxy; }

好了,现在我们可以更简单的调用我们的hooks了:

<!-- 子组件 childModel --> <template>   <input type="text" v-model="modelValue" />   <input type="text" v-model="test" /> </template> <script setup> import { useVmodel } from "./hooks/useVmodel2"; const emit = defineEmits(); const props = defineProps({   modelValue: String,   test: String, }); const modelValue = useVmodel(props); const test = useVmodel(props, "test"); </script> <!-- 父组件 --> <template>   <Model v-model="modelValue" v-model:test="test" /> </template>  <script setup> import { ref, watch } from "vue"; import Model from "./childModel.vue"; const modelValue = ref(""); const test = ref(""); </script>

到此这篇关于vue3的setup语法如何自定义v-model为公用hooks的文章就介绍到这了,更多相关vue自定义v-model内容请搜索易盾网络以前的文章或继续浏览下面的相关文章希望大家以后多多支持易盾网络!