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

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

保持单向数据流

大家都知道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. docker离线安装及设置默认存储目录

    一.离线安装Docker 在内网环境下,一般不能联网在线部署,这时候就需要以离线的方式安装docker.本文介绍在CentOS 7.6环境中离线安装docker的步骤. 1. 下载docker安装包 ...

  2. CentOS 7.3 源码安装squid 4.12 及安装过程遇到的一些问题

    一.源码安装squid 4.12 1.下载squid-4.12源码包 wget http://www.squid-cache.org/Versions/v4/squid-4.12.tar.gz tar ...

  3. 【Unity3D】粒子系统ParticleSystem

    1 简介 ​ 拖尾(TrailRenderer).线段渲染器(LineRenderer).粒子系统(ParticleSystem)是 Unity3D 提供的三大特效,其中粒子系统的功能最为强大,特效也 ...

  4. Oracle高级队列介绍

    原始链接:http://www.oracle-developer.net/display.php?id=411 oracle高级队列介绍 高级队列Advanced Queuing(AQ)在oracle ...

  5. junit使用mock objects进行单元测试

    上一篇我介绍了使用stub进行单元测试.那么mock objects和stub有何区别?什么情况下使用mock objects呢? 下面摘自junit in action书中的解释: mock obj ...

  6. 本地启动RocketMQ未映射主机名产生的超时问题

    问题描述 参考RocketMQ官方文档在本地启动一个验证环境的时候遇到超时报错问题. 本地环境OS:CentOS Linux release 8.5.2111 首先,进入到RocketMQ安装目录,如 ...

  7. 硬件开发笔记(十二):RK3568底板电路电源模块和RTC模块原理图分析

    前言   做硬件做系统做驱动,很难从核心板做起,所以我们先依赖核心板,分析底板周围的电路,然后使用AD绘制原理图和设计PCB,打样我司测试底板,完成硬件测试,再继续系统适配,驱动移植,从而一步一步完善 ...

  8. RK3588开发笔记(二):基于方案商提供sdk搭建引入mpp和sdk的宿主机交叉编译Qt5.12.10环境

    前言   上一篇项目已经构建好了Qt,板子接入mipi屏幕也跑起来了,Qt也能正常运行了,现在需要接入定制开发的sdk,sdk中使用了硬解码等资源涉及到bsp的mpp,所以下一步就是引入mpp和sdk ...

  9. PicGo如何设置阿里云图床

    打开阿里云官网.注册并且登录.然后产品下拉列表里面通过搜索或者直接找到存储.对象存储OSS 默认你已经激活了,然后进入到控制台里面. 注意事项 Bucket名称需要全英文,不能有大写字母 服务器选国内 ...

  10. Simulink模型指标分析与模型重构的最佳实践 - 软件模型质量保证不可忽视的一环

    在基于模型的开发中,优质的模型架构是生成优质代码的必要前提.静态模型分析对于模型的质量保证有着至关重要的作用,同时建模规范已在业内有着广泛而成熟的应用.然而建模规范并非模型设计原则合规性的唯一考量标准 ...