Computed

news/2025/1/12 23:14:10/

保持单向数据流

大家都知道 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>

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

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({// getterget() {return props.modelValue},// setterset(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({// getterget() {return props.modelValue.name;},// setterset(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>

http://www.ppmy.cn/news/1116662.html

相关文章

Vite + Vue3 实现前端项目工程化

通过官方脚手架初始化项目 第一种方式&#xff0c;这是使用vite命令创建&#xff0c;这种方式除了可以创建vue项目&#xff0c;还可以创建其他类型的项目&#xff0c;比如react项目 npm init vitelatest 第二种方式&#xff0c;这种方式是vite专门为vue做的配置&#xff0c;…

【SSM】登录和注册

框架 controller 控制层 dao 持久层 interceptor 拦截器 model 实体层 uils 工具类 service 业务层 resources 资源文件层 mapper 放编写sql语句的文件&#xff0c;与持久层的文件对应 具体代码 Controller //控制层 public class UserController { //用于调用Servic…

【音视频】H264视频压缩格式

H264简介 H.264从1999年开始&#xff0c;到2003年形成草案&#xff0c;最后在2007年定稿有待核实。在ITU的标准里称为H.264, 在MPEG的标准里是MPEG-4的一个组成部分-MPEG-4 Part 10&#xff0c;又叫Advanced Video Codec&#xff0c;因此常常称为MPEG-4AVC或直接叫AVC。 压缩算…

2000-2018年各省能源消费和碳排放数据

2000-2018年各省能源消费和碳排放数据 1、时间&#xff1a;2000-2018年 2、范围&#xff1a;30个省市 3、指标&#xff1a;id、year、ENERGY、COAL、碳排放倒数*100 4、来源&#xff1a;能源年鉴 5、指标解释&#xff1a; 2018年碳排放和能源数据为插值法推算得到 碳排放…

[考前速记] 最大公约数与最大公倍数

最大公约数 int gcd(int a,int b){if (b0)return a;else return gcd(b,a%b); } 熟练了可以写成&#xff1a; int gcd(int a,int b){return b ? gcd(b,a%b):a; } 值得注意的是&#xff1a;让a和b均为非负数&#xff0c;可以使用algorithm下的abs(int x)和cmath下的fabs(dou…

会话控制学习

文章目录 介绍cookieexpress中使用cookie获取cookie session配置区别 介绍 cookie express中使用cookie 退出登录就是删除cookie 获取cookie 添加中间键后&#xff0c;直接获取 session 配置 区别

异步FIFO设计的仿真与综合技术(6)

概述 本文主体翻译自C. E. Cummings and S. Design, “Simulation and Synthesis Techniques for Asynchronous FIFO Design 一文&#xff0c;添加了笔者的个人理解与注释&#xff0c;文中蓝色部分为笔者注或意译。前文链接&#xff1a; 异步FIFO设计的仿真与综合技术&#xf…

day3_QT

day3_QT 1、文件保存2、始终事件 -闹钟 1、文件保存 2、始终事件 -闹钟 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> #include <QTime> #include <QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { clas…