记录–妙用computed拦截v-model,面试管都夸我细

  • 记录–妙用computed拦截v-model,面试管都夸我细已关闭评论
  • 93 次浏览
  • A+
所属分类:Web前端
摘要

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


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

记录--妙用computed拦截v-model,面试管都夸我细

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

保持单向数据流

大家都知道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,面试管都夸我细