这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

如何避免写出屎山,优雅的封装组件,在面试官面前大大加分,从这篇文章开始!

保持单向数据流

大家都知道vue是单项数据流的,子组件不能直接修改父组件传过来的props,但是在我们封装组件使用v-model时,不小心就会打破单行数据流的规则,例如下面这样:

<!-- 父组件 -->
<my-component v-model="msg"></my-component> <!-- 子组件 -->
<template>
<div>
<el-input v-model="msg"></el-input>
</div>
</template> <script setup>
defineOptions({
name: "my-component",
}); const props = defineProps({
msg: {
type: String,
default: "",
},
}); </script>

v-model实现原理

直接在子组件上修改props的值,就打破了单向数据流,那我们该怎么做呢,先看下v-model的实现原理:

<!-- 父组件 -->
<template>
<my-component v-model="msg"></my-component>
<!-- 等同于 -->
<my-component :modelValue="msg" @update:modelValue="msg = $event"></my-component>
</template>

emit通知父组件修改prop值

所以,我们可以通过emit,子组件的值变化了,不是直接修改props,而是通知父组件去修改该值!

子组件值修改,触发父组件的update:modelValue事件,并将新的值传过去,父组件将msg更新为新的值,代码如下:

<!-- 父组件 -->
<template>
<my-component v-model="msg"></my-component>
<!-- 等同于 -->
<my-component :modelValue="msg" @update:modelValue="msg = $event"></my-component>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('hello')
</script> <!-- 子组件 -->
<template>
<el-input :modelValue="modelValue" @update:modelValue="handleValueChange"></el-input>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: String,
default: '',
}
}); const emit = defineEmits(['update:modelValue']); const handleValueChange = (value) => {
// 子组件值修改,触发父组件的update:modelValue事件,并将新的值传过去,父组件将msg更新为新的值
emit('update:modelValue', value)
}
</script>

这也是大多数开发者封装组件修改值的方法,其实还有另一种方案,就是利用计算数据的get、set

computed 拦截prop

大多数同学使用计算属性,都是用get,或许有部分同学甚至不知道计算属性还有set,下面我们看下实现方式吧:

<!-- 父组件 -->
<script setup>
import myComponent from "./components/MyComponent.vue";
import { ref } from "vue"; const msg = ref('hello')
</script> <template>
<div>
<my-component v-model="msg"></my-component>
</div>
</template> <!-- 子组件 -->
<template>
<el-input v-model="msg"></el-input>
</template>
<script setup>
import { computed } from "vue"; const props = defineProps({
modelValue: {
type: String,
default: "",
},
}); const emit = defineEmits(["update:modelValue"]); const msg = computed({
// getter
get() {
return props.modelValue
},
// setter
set(newValue) {
emit('update:modelValue',newValue)
},
});
</script>

v-model绑定对象

那么当v-model绑定的是对象呢?

可以像下面这样,computed拦截多个值

<!-- 父组件 -->
<script setup>
import myComponent from "./components/MyComponent.vue";
import { ref } from "vue"; const form = ref({
name:'张三',
age:18,
sex:'man'
})
</script> <template>
<div>
<my-component v-model="form"></my-component>
</div>
</template> <!-- 子组件 -->
<template>
<div>
<el-input v-model="name"></el-input>
<el-input v-model="age"></el-input>
<el-input v-model="sex"></el-input>
</div>
</template>
<script setup>
import { computed } from "vue"; const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
}); const emit = defineEmits(["update:modelValue"]); const name = computed({
// getter
get() {
return props.modelValue.name;
},
// setter
set(newValue) {
emit("update:modelValue", {
...props.modelValue,
name: newValue,
});
},
}); const age = computed({
get() {
return props.modelValue.age;
},
set(newValue) {
emit("update:modelValue", {
...props.modelValue,
age: newValue,
});
},
}); const sex = computed({
get() {
return props.modelValue.sex;
},
set(newValue) {
emit("update:modelValue", {
...props.modelValue,
sex: newValue,
});
},
});
</script>

这样是可以实现我们的需求,但是一个个手动拦截v-model对象的属性值,太过于麻烦,假如有10个输入,我们就需要拦截10次,所以我们需要将拦截整合起来!

监听整个对象

<!-- 父组件 -->
<script setup>
import myComponent from "./components/MyComponent.vue";
import { ref } from "vue"; const form = ref({
name:'张三',
age:18,
sex:'man'
})
</script> <template>
<div>
<my-component v-model="form"></my-component>
</div>
</template> <!-- 子组件 -->
<template>
<div>
<el-input v-model="form.name"></el-input>
<el-input v-model="form.age"></el-input>
<el-input v-model="form.sex"></el-input>
</div>
</template>
<script setup>
import { computed } from "vue"; const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
}); const emit = defineEmits(["update:modelValue"]); const form = computed({
get() {
return props.modelValue;
},
set(newValue) {
alert(123)
emit("update:modelValue", newValue);
},
});
</script>

这样看起来很完美,但是,我们在set中alert(123),它却并未执行!!

原因是:form.xxx = xxx时,并不会触发computed的set,只有form = xxx时,才会触发set

Proxy代理对象

那么,我们需要想一个办法,在form的属性修改时,也能emit("update:modelValue", newValue);,为了解决这个问题,我们可以通过Proxy代理

<!-- 父组件 -->
<script setup>
import myComponent from "./components/MyComponent.vue";
import { ref, watch } from "vue"; const form = ref({
name: "张三",
age: 18,
sex: "man",
}); watch(form, (newValue) => {
console.log(newValue);
});
</script> <template>
<div>
<my-component v-model="form"></my-component>
</div>
</template> <!-- 子组件 -->
<template>
<div>
<el-input v-model="form.name"></el-input>
<el-input v-model="form.age"></el-input>
<el-input v-model="form.sex"></el-input>
</div>
</template>
<script setup>
import { computed } from "vue"; const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
}); const emit = defineEmits(["update:modelValue"]); const form = computed({
get() {
return new Proxy(props.modelValue, {
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, value,receiver) {
emit("update:modelValue", {
...target,
[key]: value,
});
return true;
},
});
},
set(newValue) {
emit("update:modelValue", newValue);
},
});
</script>

这样,我们就通过了Proxy + computed完美拦截了v-model的对象!

然后,为了后面使用方便,我们直接将其封装成hook

// useVModel.js
import { computed } from "vue"; export default function useVModle(props, propName, emit) {
return computed({
get() {
return new Proxy(props[propName], {
get(target, key) {
return Reflect.get(target, key)
},
set(target, key, newValue) {
emit('update:' + propName, {
...target,
[key]: newValue
})
return true
}
})
},
set(value) {
emit('update:' + propName, value)
}
})
}
<!-- 子组件使用 -->
<template>
<div>
<el-input v-model="form.name"></el-input>
<el-input v-model="form.age"></el-input>
<el-input v-model="form.sex"></el-input>
</div>
</template>
<script setup>
import useVModel from "../hooks/useVModel"; const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
}); const emit = defineEmits(["update:modelValue"]); const form = useVModel(props, "modelValue", emit); </script>

本文转载于:

https://juejin.cn/post/7277089907974422588

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--妙用computed拦截v-model,面试管都夸我细的更多相关文章

  1. 查询的model里面 一般都要有一个要返回的model做属性 ;查询前要传入得参数,查询后返回的参数 都要集合在一个model中

    查询的model里面 一般都要有一个要返回的model做属性

  2. 实体类,bean文件,pojo文件夹,model文件夹都一样

    实体类,bean文件,pojo文件夹,model文件夹都一样,这些都是编写实体类,这是我暂时看到的项目文件

  3. computed 里面 不能写箭头函数 都要写 function () {} 否则页面会都不显示

    computed 里面 不能写箭头函数 都要写 function () {} 否则页面会都不显示

  4. 工作记录之 [ python请求url ] v s [ java请求url ]

    背景: 模拟浏览器访问web,发送https请求url,为了实验需求需要获取ipv4数据包 由于不做后续的内容整理(有内部平台分析),故只要写几行代码请求发送https请求url列表中的url即可 开 ...

  5. struts2拦截器拦截成功后每次请求都出现拦截时的错误信息

    action中验证方法 在执行execute之前执行 @Override    public void validate() {        // TODO Auto-generated metho ...

  6. mysql删除重复记录语句,删除除了 id 号不同,其他都相同的学生冗余信息

    /** 在Mysql下执行: delete from my.stu where id not in( select min(id) id from my.stu group by code) ; 用途 ...

  7. react 记录:运行npm run eject命令暴露配置文件都报这个错误

    问题: react 使用create-react-app命令创建一个项目,运行npm run eject命令暴露配置文件都报这个错误 原因:主要是脚手架添加 .gitgnore文件,但是却没有本地仓库 ...

  8. Struts2拦截器的使用 (详解)

    Struts2拦截器的使用 (详解) 如何使用struts2拦截器,或者自定义拦截器.特别注意,在使用拦截器的时候,在Action里面必须最后一定要引用struts2自带的拦截器缺省堆栈default ...

  9. struts拦截器的知识

    如何使用struts2拦截器,或者自定义拦截器.特别注意,在使用拦截器的时候,在Action里面必须最后一定要引用struts2自带的拦截器缺省堆栈defaultStack,如下(这里我是引用了str ...

  10. springboot aop 自定义注解方式实现完善日志记录(完整源码)

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 一:功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能. 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型 ...

随机推荐

  1. 【Unity3D】导航系统

    1 导航系统简介 ​ 导航系统用于智能避障并寻找目标物体,如:王者荣耀中,当玩家跑到敌方塔的攻击范围内,敌方塔就会发射火团攻击玩家,当玩家逃跑时,火团会智能跟随玩家,其中智能跟随就使用到了导航系统. ...

  2. Vue+SpringBoot+ElementUI实战学生管理系统-4.后端API编写

    1.章节介绍 前一篇介绍了项目的表结构设计,这一篇编写后端API,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.项目截图 登录页 列表操作 动态图 ...

  3. maven打包时打包指定的lib文件夹

    今天在打包自己的spring boot项目时遇到了问题, 报找不到类和符号. 因为我有些依赖是放在项目lib文件夹中,那么打包的时候要连把它一起打包. 修改pom.xml, 添加一下内容: <b ...

  4. 马上就要元宵节了,这里给大家用css端上一碗汤圆

    「更多福利资讯查看:2024 首次大厂挑战」. ` .... ` .bowl_wrap{ width: 200px; height: 220px; margin: 100px auto; positi ...

  5. New Questions

    1. C++/Qt 实现一个信号槽 #include <functional> #include <iostream> #include <vector> clas ...

  6. WriteFile 奇怪的现象

    项目中有个需求是要对文本内容检索并重写,我们使用的是 WriteFile 覆盖旧的文本内容 最小示例: #include <Windows.h> #include <iostream ...

  7. win32 - wsprintfW的使用

    文档:将格式化的数据写入指定的缓冲区.根据格式字符串中相应的格式说明,将转换任何参数并将其复制到输出缓冲区.该函数在其写入的字符后附加一个终止空字符,但返回值的字符计数中不包含终止空字符. 例子: # ...

  8. 统信UOS系统开发笔记(四):从Qt源码编译安装之编译安装QtCreator4.11.2,并配置编译测试Demo

    前言   上一篇已经从Qt源码编译了Qt,那么Qt开发的IDE为QtCreator,本篇从源码编译安装QtCreator,并配置好构建套件,运行Demo并测试.   统信UOS系统版本   系统版本: ...

  9. 像闪电般击碎天空吧——快速轻量化模型之 SDXL-Lightning

    SDXL-Lightning 是一个由 ByteDance(字节跳动) 开发的文本到图像的生成模型,其主要贡献在于其高速的生成能力和轻量化的设计. 模型的特点 快速生成:SDXL-Lightning ...

  10. Mac环境下, VMware Fusion Pro下的虚拟机( CentOS 7)的 NAT网络配置

    前提实现说明 1.vm版本VMware Fusion Pro 12.1.0 2.centos版本centos7.6 1.虚拟机能访问外网,虚拟机能访问mac本机: 2.mac本机可以连接虚拟机 操作步 ...