likes
comments
collection
share

【Vue3】聊聊组件的 props 为啥不让改的核心原理?(其实可以直接改)

作者站长头像
站长
· 阅读数 27

大家都知道 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: '楼上说的对'
      }
    }
  })

然后在父组件里传入数据。

  • 对比一下结构

【Vue3】聊聊组件的 props 为啥不让改的核心原理?(其实可以直接改)

这结构是一模一样呀。

问题四

问:官网为什么不说可以这么改?

大概是怕增加心智负担吧,说多了容易懵,用 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
评论
请登录