关于props响应式问题

来源:5-15 Props--v2.6.11(五)

慕粉4283821

2021-01-02

老师,先祝您元旦快乐,新年大吉啦~~
关于props 的响应式,比如 下面的代码

// child 子组件
<template>
	<div>{{ a }}</div>
	<div>{{ b.c }}</div>
</template>

<script>
props: {
  a: {
    type: Object
  },
  b: Object
}
</script>
// parent 父组件
<template>
	<child :a="a" :b="b" />
</template>

<script>
data: {
  a: { a: 2 },
  b: { c: 3 }
}
</script>

首先在父组件 init 时, 会将 a, b 变成响应式对象, 然后在渲染过程中,为什么说 父组件访问了 a 和 b呢? 是因为 给 child 组件传值的时候 访问 了 a b 触发了 getter 吗?

另外在子组件 initProps 时, 也会将 a 和 b 变成响应式对象。 对于a 属性,子组件直接引用了a, 所以 子组件渲染watcher 订阅了 a 这个依赖,而对于 b属性,子组件使用的是 b的c属性, 触发了c属性的getter, 子组件渲染watcher订阅了 c这个依赖。

所以在props 值变化时:
对于属性a :

  1. 执行 this.a = { a: 3 } 赋值操作,会触发 setter,通知 子组件渲染watcher 更新,所以子组件更新了。
  2. 执行 thia.a.a = 4 改变对象属性, 同样会触发setter,但此时 通知的是 父组件渲染wathcer, 因为 子组件只遍历了一层。父组件重新渲染了,在 patch 过程,对于组件vnode, 会进行prepatch操作,从而updateChildComponent 改变 对props 属性进行重新计算, 此时 会触发 属性a 的setter, 从而触发 子组件渲染watcher 重新渲染。

对于属性b:
子组件渲染时访问了 b中的c 属性,触发 c属性的getter,子组件渲染watcher 订阅了 依赖。
修改时:
3. 执行 this.b.c = 9 修改对象b中的属性值, 会触发c的setter, 子组件渲染watcher 重新渲染,子组件更新。
4. 执行 this.b = { c: 99 } 赋值操作,此时触发的是 b的setter, 父组件渲染watcher 进行更新,patch 过程 对 props 属性 赋值时, 触发了b的setter, 此时子组件 渲染 wacther 重新渲染,子组件更新。

总结: props的更新 如何影响子组件的重新渲染 和 子组件 视图中如何引用这个props 有关系,这影响到watcher 订阅的依赖 到底是 整个对象,还是某个对象属性。

老师,写的有点多,还请辛苦看下 有没有理解上的误区,感谢老师~

写回答

3回答

ustbhuangyi

2021-01-04

1. 父组件在模板中把 a,b 传递给子组件,就是访问了 a 和 b,触发了他们的 getter
2. 你的理解有点问题,先看下面截图

//img.mukewang.com/szimg/5ff28be409b42aab16540810.jpg
所以 执行 this.a = { a: 3 } 是 props 值被修改和你后面的 执行 this.b = { c: 99 }  逻辑是一样的。

thia.a.a = 4 和  this.b.c = 9 是一样的

1
1
慕粉4283821
我还是有点问题,回复里不能放代码,发在帖子里,麻烦老师看下
2021-01-05
共1条回复

ustbhuangyi

2021-01-05

注意你前一个例子,在父组件定义的数据

data() {
 return {
   a: {
     a: 2
   },
   b: {
     c: 3
   }
 }
}

当你在父组件修改 this.a.a  = 4 的时候,就是触发了 a 的 setter,

你在子组件的模板中引用了 prop a,这个 a 就是 { a: 2} 的对象,它就是指向父组件的 this.a,因为模板中访问了 a,那么自然子组件的 render watcher 也会收集这个 a 依赖

所以当你修改 this.a.a  = 4 的时候,就是触发了 a 的 setter,自然就会触发子组件 render watcher 的 update,重新渲染子组件。

我建议你写一个简单的 demo,打上 debugger 断点调试一下更清楚。

0
8
前端工程师666777888
回复
ustbhuangyi
this.a.a的改变,会导致this.a的改变吗? 我感觉this.a并没有改变。因为仅仅是this.a内部的属性变了,而this.a的引用没变,还是原来的引用。 我感觉this.a.a的改变,this.a是感知不到这个变化的,也就是说this.a并没有变化。thia.a.a的改变,不应该有任何子组件的重新渲染 ___________ this.$set(this.a,a,5)。这样写,是不是this.a就能感知到变化了
2021-04-10
共8条回复

慕粉4283821

提问者

2021-01-05

我有点不理解: 截图中说的 子组件渲染过程“访问过这个对象prop” 这里的访问是指怎么访问呢?

我个人理解:

// 子组件中的插值
{{ a }}
{{ b.c }}

上面的a, b 都是 prop, 我认为 只有 prop中的 a 被访问了,触发 a 的setter, 子组件render watcher会 订阅a的dep。 而 对于对象b,子组件只是访问了对象b的c属性,只会触发c属性的getter,子组件 render watcher 会订阅 c属性的dep。

基于上述理解, 如果直接 对 a 赋值修改,那么会直接触发 子组件重新渲染。

如果对 b 直接赋值进行修改,我认为会触发父组件重新渲染,在父组件重新渲染的patch 过程,对于组件vnode, 执行prepatch,这个过程会修改 子组件的prop属性的值,也就是修改 b的值,但是此时 修改b的值并不能引起子组件重新渲染,因为子组件只是订阅了 c属性的dep。 这里就说不通了。

所以是不是 在组件中写 b.c 也会触发 b的getter呢. 好像是这样的,写了如下例子来证明:

let prop = {}, value = {};
Object.defineProperty(prop, 'b', { 
    get() {
        console.log('get b');    
        return value;
    },
    set() {
        console.log('set prop.b');
        return 44;
    }
})

Object.defineProperty(prop.b, 'c', {
    get() {
        console.log('get c');    
        return 2;
    },
    
    set(newV) {
      console.log('set prop.b.c');
      return 33;
    }
});

prop.b  // get b

prop.b.c // get b get c  也会触发b的getter, 先取b,再取c

prop.b.c = 33; // get b set prop.b.c , 只会触发 c属性的setter

上面的prop 就好比 vue 中的 vm._prop 对象。 所以书写 b.c 的时候 其实是 触发了b的getter, 触发了 子组件render watcher 订阅 b 的dep。 在对 b赋值的时候 就会 触发子组件重新渲染。 而对于修改b的属性,只有修改 b.c 会引起子组件重新渲染。 因为b.c在render时使用了。 修改其他属性 比如 b.x 就不会。


对于a 属性来说,直接修改,引起 子组件render watcher 更新,重新渲染。 如果修改a 的属性值, 也不会触发 a 属性的setter。那视图是怎么更新的呢?  老师在截图里的描述是“对象类型的prop的内部属性变化时候,并没有触发子组件prop的更新,但是在组件渲染过程,访问过这个对象prop,prop触发getter,子组件render watcher 会订阅依赖。然后在父组件更新这个对象prop的某个属性时,会触发setter,从而通知子组件render watcher 重新渲染。”

 这里说 组件渲染访问了这个对象prop(也就是a属性),更新内部属性值时,并没有触发a的setter。

比如 a.x = 2 此时只是触发了 x 的setter, a的getter, 并没有触发 a 的setter, 那是怎么通知子组件的呢?



0
0

Vue.js 源码深入解析 深入理解Vue实现原理

全方位讲解 Vue.js 源码,进阶高级工程师

4920 学习 · 1022 问题

查看课程