reactive定义的对象中的数组push不触发响应式?

lamber0808
  • 1.1k
const {effect, reactive} = Vue;
    const state = reactive({ show: true, values : [1,2,3] })
    effect(() => {
        if (state.show) {
            console.log(state.values);
        }
    })
    // 不触发响应式
    state.values.push(4)
    // 触发响应式
    state.values = [1]

为什么使用push不会触发effect执行,而使用赋值方式就可以重新触发effect执行。

回复
阅读 1.1k
4 个回答
Runningfyy
  • 1.3k

为了回答这个题目我专门调试了一下源码 老哥,不给个赞说不过去啦。

首先你这个代码state.show以及相关逻辑是冗余的 可以删掉。只考虑这个

 const state = reactive({arr: [1,2,3]})
 effect(() => {
        console.log(state.arr);
 })

你其实是想问 为什么 state.arr.push(4)不触发响应式
我初看也觉得反直觉,因为平时写业务逻辑的时候,这么写会引起视图更新。
不信你可以试试下面代码👇🏻


    const App = {
    template:`
      <p v-for="i in state.arr">{{i}}</p>
      `,
    setup() {
        
      const state = reactive({arr: [1,2,3]})
        setTimeout(()=>{
            state.arr.push(4)  //都能触发更新
            //state.arr=[4,5,6]  //都能触发更新
            // state.arr[3] = 4     //都能触发更新
        },2000)
      return {
        state
      }
    }
  }
  let vm = Vue.createApp(App).mount("#app");

首先显示为什么写业务代码能触发更新。
debugger以后可以看到对于arr数组收集了两次属性,一个是'length',一个是'arr'
image.png
state.arr = xxx可以引起更新是trigger(arr,'arr')
state.push(4)可以引起更新是引起了trigger(arr,'length') (类似直接触发length,具体可以看下图,实际上虽然key不是length,但是是通过各种判断,还是调用的length对应的set中的方法)
image.png
之所以引入模板会额外收集length是因为模板编译的时候调了renderlist方法对arr的length进行了取值
image.png


ok 回到你的原始问题上来
为什么只用effect的时候 push不更新呢
因为weakmap: target(arr)-->map('arr'=>set(fn))中只收集了'arr' 没有收集'length'。
所以state.arr = xxx能引起更新 而push虽然改变了length但是不会引起更新

如果effect里面写成这样console.log(state.arr.length)让收集'length',push此时就能触发响应式了


再补充一句
不是吧,VUE已经重写了push方法了,可以监听数据的变动的,这句话说的不太准确。
据我的了解,push方法重写是为了解决length引起的bug,和监听数据的变动没有直接关系。
监听对象上全部数据的变动是proxy的特点,但这不一定是好处。所以effect做了额外的处理,比如以下两种情况都是没有响应式的。

const state = reactive({a:'a'})
effect(() => {
    console.log(state);
})
state.c= 'c'
//-------
const state = reactive({a:'a'})
effect(() => {
    console.log(state.a);
})
state.c= 'c'

还是没理解传址。
可以这样认为,let a = []的时候,a的值是一个内存地址编码。而reactive等类似的侦听是在被侦听的对象的值发生变化时生效。push,pop等操作并不会改变原变量的值(原变量指向的内存地址),所以值没有发生变化,自然就不触发了。而赋值的方式是给了变量一个新内存地址编码,值变了,所以触发。

使用...试试

const arr = reactive([])

arr.push(...[1, 2, 3])

watch 深度监听

watch(
  () => state.values,
  () => {
    console.log(state.values);
  }, {
  immediate: true,
  deep: true
})
宣传栏