【Vue3】聊聊组件的 props 为啥不让改的核心原理?(其实可以直接改)
大家都知道 vue 的组件的 props 不能直接修改,如果非要改的话只能通过 emit 来实现。 那么到底为啥不让改?本质原因是什么? 另外,你标题里说的又可以改?是不是标题党?放心,肯定不是。。。
prosp 到底让不让改?
不说理论了,容易绕晕,我们直接看代码:
// 第一部分
const liLei = ref('我是李雷')
const role = reactive({
name: '我是一个角色'
})
const myEmit = (value: string) => {
liLei.value = value
}
// 第二部分
const person = reactive({
name: liLei.value, // 注意:取的是 ref.value , 不是 ref 本身
role: role
})
// 问题一
person.name = '改名'
// 问题二
person.role.name = '角色更名'
// 第三部分
const myProps = shallowReadonly(person)
const emit = (value: string) => {
myEmit(value)
}
第一部分,我们定义了一个 liLei (ref)和 role (reactive)。 第二部分,我们用 liLei.value 和 role 作为初始化的数据,又定义了一个 person(reactive)。
那么问题来了。
问题一
问:我们修改 person.name ,会不会影响到 liLei (ref)?
答:不会,我们只是在初始化的时候,使用了 liLei.value ,之后就没有任何关系了,怎么还会有影响?
分析 props 不让改的原因
第一部分相当于父组件里的代码,第二部分相当于子组件里的代码。
template 会自动获取 ref.value 的值(string 类型),然后传入给子组件,对于基础类型,子组件只能得到一个的副本,从而无法影响本尊。
所以,组件的 props 不让改,就是这个原理。就算让你改,你也影响不了父组件。
想要改的话,只能调用第一部分的 myEmit,相当于在子组件使用 emit。
问题二
问:我们修改 person.role.name 会不会影响 role (reactive)?
答:会的。因为初始化的时候,我们用的是 role (的地址),得到 role 的地址后,可以通过这个地址找到本尊。
分析一下又可以改的原因
第一部分相当于父组件里的代码,第二部分相当于子组件里的代码。
对于引用类型(对象、数组、Map、set等),不管是 ref.value 还是 reactive,传递的都是地址,子组件得到地址之后,可以通过地址访问父组件的 role,所以可以在子组件里修改父组件的 role(的属性值)。
这就是子组件可以修改引用类型(对象、数组等)的原因。
问题三
问:那么第三部分,使用 shallowReadonly 是什么意思呢?
答:因为 reactive 是可以修改其属性值的,但是改了(第一层属性)又影响不了父组件,这算不算bug?所以需要用 shallowReadonly 做限制,不允许修改第一层属性。
问:为什么不直接使用 readonly,干脆不管哪一层都不让改呢?
答:我们改 perosn.role.name 的时候,是可以影响父组件的 role,既然可以改,那么为啥要拦着不让改?
证明观点
我们创建一个 props 对比一下结构就知道了。
const props = withDefaults(defineProps<{
name: string,
role: {
name: string
}
}>(), {
name: '我是正宗 props',
role: () => {
return {
name: '楼上说的对'
}
}
})
然后在父组件里传入数据。
- 对比一下结构
这结构是一模一样呀。
问题四
问:官网为什么不说可以这么改?
大概是怕增加心智负担吧,说多了容易懵,用 ref 还是 reactive ,都能争论好久。
另外,官网一再强调,是单向数据流,现在又说可以在子组件里面直接改 props,那要如何解释?(上面的解释也不够完整,反正我自己懂了。。。)
最后一个原因就是,大家喜欢解构,觉得 xxx.xxxx 太麻烦,所以 props 默认都是单层的,一般不会是多层属性。 单层确实不能改,多层的才能改,既然大多数情况都是单层的 props,那么就不给自己找麻烦了。
所以,内部代码上做了彩蛋,但是不告诉你,让你自己去大胆的猜。
应用场景
说了半天,传入一个对象进来有啥好处?用起来方便吗?
这个嘛,好处确实有,只不过不一定方便。
比如我们要做一个表单,里面有二、三十,甚至上百个字段,我们会定义一个 model,然后我们把 model 直接传给子组件,那么是不是在子组件里面可以直接改 model 了?
避免用 emit 折腾,尤其是子组件又嵌套子组件的情况下。
另外一个子组件可能对应多个字段,比如省市区县级联的情况,我们可以用一个子组件(比如 el-cascader)表示,但是这个子组件要对应省份、城市、区县三个字段。 v-model 当然可以支持多个字段,但是用起来麻烦呀,尤其在 v-for 的时候,哪有直接传入 model,然后可以随意对应任何一个或者多个字段来的轻松。
问题五
上面你刚才说“随意”?这是不是意味着“容易混乱”?
当然,弄不好肯定会乱。比如改了不应该由这个子组件对应的字段。
所以需要我们在源头上做好限制。
好吧,我摊牌了,相信说到这里,大家都看出来了,我这么折腾,其实就是为了能够更好的使用 json 绑定表单控件。 因为,在 json 里面可以把字段和字段对应的组件的属性绑定在一起,这样就不会“错位”了。
转载自:https://juejin.cn/post/7240486780577808445