大家都知道 vue 是单项数据流的,子组件不能直接修改父组件传过来的 props,但是在我们封装组件使用 v-model 时,不小心就会打破单行数据流的规则,例如下面这样:
直接在子组件上修改 props 的值,就打破了单向数据流,那我们该怎么做呢,先看下 v-model 的实现原理:
所以,我们可以通过 emit,子组件的值变化了,不是直接修改 props,而是通知父组件去修改该值!
子组件值修改,触发父组件的 update:modelValue 事件,并将新的值传过去,父组件将 msg 更新为新的值,代码如下:
这也是大多数开发者封装组件修改值的方法,其实还有另一种方案,就是利用计算数据的 get
、set
大多数同学使用计算属性,都是用 get,或许有部分同学甚至不知道计算属性还有 set,下面我们看下实现方式吧:
那么当 v-model 绑定的是对象呢?可以像下面这样,computed 拦截多个值
这样是可以实现我们的需求,但是一个个手动拦截 v-model 对象的属性值,太过于麻烦,假如有 10 个输入,我们就需要拦截 10 次,所以我们需要将拦截整合起来!
这样看起来很完美,但是,我们在 set 中 alert(123),它却并未执行!!
原因是:form.xxx = xxx 时,并不会触发 computed 的 set,只有 form = xxx 时,才会触发 set
那么,我们需要想一个办法,在 form 的属性修改时,也能 emit("update:modelValue", newValue);,为了解决这个问题,我们可以通过 Proxy 代理
这样,我们就通过了 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)
}
})
}