文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

vue3封装自定义v-model的hooks示例详解

2024-04-02 19:55

关注

?前言

?基础

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

基本的v-model

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

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

需要注意的是,当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>

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

<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中定义两个值

对于自定义v-model:xxx来说,props中:

由此,上代码:

<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);
  },
});

?终极:封装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>

继续抽离封装

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

我们可以通过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>

最后

封装computed这种方式本文暂不赘述,小伙伴们可进行自行封装抽离,以上就是vue3自定义封装v-model的hooks示例详解的详细内容,更多关于vue3封装v-model hooks的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯