vue中遇到的坑---变化检测问题(数组相关)

WayneZhu WayneZhu     2022-09-01     275

关键词:

  最近在项目中遇到了一个问题,不知道为什么,所以最后通过动手做demo实践、查文档的方式解决了,这里做一个总结。

  

例1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>vue</title>
  <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
  <style>
    li:hover {
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <ul>
      <li v-for="item,index in items" v-on:click="handle(index)">
        <span>{{item.name}}</span>
        <span>{{numbers[index]}}</span>
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: ".wrap",
      data: {
        numbers: [],
        items: [
          {name: 'jjj'},
          {name: 'kkk'},
          {name: 'lll'},
        ]
      },
      methods: {
        handle: function (index) {
          // WHY: 更新数据,view层未渲染,但通过console这个数组可以发现数据确实更新了
           if (typeof(this.numbers[index]) === "undefined" ) {
             this.numbers[index] = 1;
           } else {
             this.numbers[index]++;
           }
        }
      }
    });
  </script>
</body>
</html>

这里的实现目的很明确 --- 我希望在点击li时先检测是否存在,当然是不存在的,所以就将值设置为1, 如果再次点击,就让数字累加。

但是出现的问题是: 点击之后数字并没有在view层更新,而通过console打印发现数据确实更新了,只是view层没有及时的检测到, 而我一直以来的想法就是: 既然vue实现的时数据双向绑定,那么在model层发生了变化之后为什么就没有在view层更新呢?  

首先,我就考虑了这是不是数组的问题,于是,我测试了下面的例子:

 

 

例2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>vue</title>
  <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
  <style>
    li:hover {
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <ul>
      <li v-for="item,index in items" v-on:click="handle(index)">
        <span>{{item.name}}</span>
        <span>{{numbers[index]}}</span>
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: ".wrap",
      data: {
        numbers: [],
        items: [
          {name: 'jjj'},
          {name: 'kkk'},
          {name: 'lll'},
        ]
      },
      methods: {
        handle: function (index) {
          // 不是数组,这里更新数据就可以直接在view层渲染
          this.items[index].name += " success";
        }
      }
    });
  </script>
</body>
</html>

这时,我再测试时就发现,这里的model层发生了变化时,view层就能及时、有效的得到更新。

而数组为什么不可以呢?  

 

 

于是在文档上的一个不起眼的地方找到了下面的说明:

其中最重要的一句话就是 --- 如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新,这个方法主要用于避开Vue不能检测到属性被添加的限制。

 

那么什么情况下Vue是不能检测到属性被添加呢?  根据参考链接,我们在文档上看到了很好的说明 --- 深入响应式原理

 

首先,我们要了解Vue是如何实现数据的双向绑定的!  

  把一个普通 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是仅 ES5 支持,且无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

知识补充:

  访问器属性不包含数据值,他们包含一对getter函数和setter函数(这两个函数不是必须的)。在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性是,会调用setter函数并传入新值,这个函数负责决定如何处理数据。

  访问器属性不能直接定义,必须是用Object.defineProperty()来定义。

  下面是一个例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>vue</title>
</head>
<body>
  <script>
    var book={
        _year:2004,
        edition:1
    };
    Object.defineProperty(book,"year",{
        get:function(){
            return this._year;
        },
        set:function(newValue){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    });
    console.log(book.year); // 2004  在读取访问器属性时会调用get函数
    book.year=2005;  // 在给访问器属性赋值时会调用set函数
    console.log(book.edition); // 2
  </script>
</body>
</html>

  这个例子应该可以很好的理解访问器属性了。

 所以,当对象下的访问器属性值发生了改变之后(vue会将属性都转化为访问器属性,之前提到了), 那么就会调用set函数,这时vue就可以通过这个set函数来追踪变化,调用相关函数来实现view视图的更新。

 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新

  即在渲染的过程中就会调用对象属性的getter函数,然后getter函数通知wather对象将之声明为依赖,依赖之后,如果对象属性发生了变化,那么就会调用settter函数来通知watcher,watcher就会在重新渲染组件,以此来完成更新。

 

OK!既然知道了原理,我们就可以进一步了解为什么出现了之前数组的问题了!

 

变化检测问题

  收到现代JavaScript浏览器的限制,其实主要是 Object.observe() 方法支持的不好,Vue不能检测到对象的添加或者删除。然而Vue在初始化实例时就对属性执行了setter/getter转化过程,所以属性必须开始就在对象上,这样才能让Vue转化它。 

  所以对于前面的例子就不能理解了 --- 数组中index都可以看做是属性,当我们添加属性并赋值时,Vue并不能检测到对象中属性的添加或者删除,但是其的确是添加或删除了,故我们可以通过console看到变化,所以就没有办法做到响应式; 而在第二个例子中,我们是在已有的属性的基础上进行修改的,这些属性是在最开始就被Vue初始化实例时执行了setter/getter的转化过程,所以说他们的修改是有效的,model的数据可以实时的在view层中得到相应。

补充知识: 什么是 Object.observe() ?

  在介绍之前,不得不残忍的说,尽管这个方法可以在某些浏览器上运行,但事实是这个方法已经废弃

  概述此方法用于异步地监视一个对象的修改。当对象的属性被修改时,方法的回调函数会提供一个有序的修改流,然而这个接口已经从各大浏览器移除,可以使用通用的 proxy 对象      

    方法: 

Object.observe(obj, callback[, acceptList])

  其中obj就是被监控的对象, callback是一个回调函数,其中的参数包括changes和acceptList,

  changes一个数组,其中包含的每一个对象代表一个修改行为。每个修改行为的对象包含: 

  • name: 被修改的属性名称。
  • object: 修改后该对象的值。
  • type: 表示对该对象做了何种类型的修改,可能的值为"add""update", or "delete"
  • oldValue: 对象修改前的值。该值只在"update""delete"有效。 

  acceptList在给定对象上给定回调中要监视的变化类型列表。如果省略, ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"] 将会被使用。

var obj = {
  foo: 0,
  bar: 1
};

Object.observe(obj, function(changes) {
  console.log(changes);
});

obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]

obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

delete obj.baz;
// [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]

 

  如上所示: 但是chrome也是不支持的,浏览器的兼容性如下:

 

参考文档: Object.ovserve()

推荐阅读文章: Object.observe() 引爆数据绑定革命

 

 

解决方法

  使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上。 还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名。

例3

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>vue</title>
  <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
  <style>
    li:hover {
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <ul>
      <li v-for="item,index in items" v-on:click="handle(index)">
        <span>{{item.name}}</span>
        <span>{{numbers[index]}}</span>
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: ".wrap",
      data: {
        numbers: [],
        items: [
          {name: 'jjj'},
          {name: 'kkk'},
          {name: 'lll'},
        ]
      },
      methods: {
        handle: function (index) {
          // WHY: 更新数据,view层未渲染,但通过console这个数组可以发现数据确实更新了
           if (typeof(this.numbers[index]) === "undefined" ) {
             this.$set(this.numbers, index, 1);
           } else {
             this.$set(this.numbers, index, ++this.numbers[index]);
           }
        }
      }
    });
  </script>
</body>
</html>

 

这样,我们就可以实现最终的目的了!

 

 

 

第二部分

  上面一部分是指在data下的数组,而如果是在store中的数组,一般可以这样:

[ADD_ONE] (state, index) {
      if ( typeof state.numbers[index] == "undefined") {
        Vue.set(state.numbers, index, 1)
      } else {
        Vue.set(state.numbers, index, ++state.numbers[index])
      }
    }

 即使用 Vue.set() 的方式来改变、增加。

   注意:这里是确定index的增加和减少,所以用 Vue.set() 的方式

  减的方式如下:

  [REMOVE_ONE] (state, index) {
      Vue.set(state.numbers, index, --state.numbers[index]);
    }

 

 

 

第四部分

 

   如果是在store的actions中我们需要对stroe中的数组进行填充,方法如下:

state内容:

    kindnames: []

 

 

Mutations内容:

    [ADD_KIND_NAME] (state, name) {
      state.kindnames.push(name);
    } 

 

注意: 这里直接使用push的方式

当然,除了push,我们还可以shift等各种方式。

 

actions的内容:

commit(ADD_KIND_NAME, state.items[index++].name);

 

 

 这里,state.items[index++].name获取到的是一个一个的字符串。

 

最后,看到数据如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注:同样可以参考文档 --- 细节与最佳实践

 

不骄不躁,努力前行。

 

vue数组中对象属性变化页面不渲染问题

 做checkbox多选功能的时候遇到了一个坑,逻辑怎么看都对,但是就是有bug,最后发现数组那里值变了页面勾选没有重新渲染。换了关键词搜索找到了相关方法。其实之前读文档教程的时候看到过这里,但是只有真的使用之后... 查看详情

在vue中使用async/await遇到的坑(代码片段)

最近无聊在搞一些新的东西,今天就遇到一个async/await的坑;因为我用的不是vue官方的脚手架,所以遇到这样的问题:awaitisareservedword这样的警告,我猜应该是缺乏相关的解析器。然后取掉await之后,又出现async出现问题:好吧,... 查看详情

vue数组中对象属性变化页面不渲染问题(代码片段)

...logs.com/thinkingthigh/p/7789108.html 做checkbox多选功能的时候遇到了一个坑,逻辑怎么看都对,但是就是有bug,最后发现数组那里值变了页面勾选没有重新渲染。换了关键词搜索找到了相关方法。其实之前读文档教程的时候看到过这... 查看详情

vue不能检测的数组变化对象变化(代码片段)

数组:由于JavaScript的限制,Vue不能检测以下变动的数组:当你利用索引直接设置一个项时,例如:vm.items[indexOfItem]=newValue当你修改数组的长度时,例如:vm.items.length=newLength 为了解决第一类问题,以下两种方式都可以实现和&nb... 查看详情

vue中为什么不能检测数组的变化-02-proxy(代码片段)

...defineProperty 。Object.defineProperty 真的无法监测数组下标的变化吗?分析vue2.x中对数组 Observe 部分源码对比 Object.def 查看详情

vue为什么不能检测数组的变化(代码片段)

...那这个限制是指Object.defineProperty吗?无法检测数组的索引变化?我们来测试一下看看。以下例子,对遍历数组中的每一项,用Object.defineProperty对其进行监测functiondefineReactive(data,key,value)Object.defineProperty(data,key,enumerable:true,configurabl... 查看详情

vue中使用keepalive组件缓存遇到的坑

...keep-alive组件。上一篇讲了keep-alive的基本用法,现在说说遇到的坑。先说项目中的配置 在App.vue中的设置 在router中增加配置meta   上面这个设置后发现问题了,从category进入detail页后 查看详情

vue如何检测数组变化

参考技术A原理分析:在数据初始化时调用initData方法,然后通过newObserver对数据进行监测,然后对数据进行判断,如果是数组并且支持原型链,就会执行protoAugment让目标原型链指向arrayMethods,arrayMethods用来改写数组的原型方法。... 查看详情

vue环境搭建过程中,遇到的坑爹的问题

1,在配置package.json下载node依赖包时,执行$cnpminstall过程中,这个过程是比较漫长的,尤其的这种corei5配置的电脑,简直有点卡的人怀疑人生,后来动了下有消息输出,我以为下载安装完毕了呢,然实际并木有,真正的安装结束... 查看详情

rxjava使用过程中遇到线程相关的坑(代码片段)

RxJava线程暴增的坑1、问题在使用RxJava的时候,对于开发者频繁使用subscribeOn(Schedulers.computation())或者.subscribeOn(Schedulers.io()),导致App线程暴涨,在业务繁多的App中,容易导致超过句柄数限制,导致App崩溃2、原因... 查看详情

rxjava使用过程中遇到线程相关的坑(代码片段)

RxJava线程暴增的坑1、问题在使用RxJava的时候,对于开发者频繁使用subscribeOn(Schedulers.computation())或者.subscribeOn(Schedulers.io()),导致App线程暴涨,在业务繁多的App中,容易导致超过句柄数限制,导致App崩溃2、原因... 查看详情

vue数组和对象渲染问题(代码片段)

vue数组和对象渲染问题最近项目有点忙碌,遇到好多问题都没有总结(╥﹏╥),在开发过程中,取vuex中的数组渲染完成之后,再次修改数组的值,数据更新了,但是视图并没有更新。以为是数组更新的问题,后来又以为是因为vue... 查看详情

vue对象或者数组中数据变化但是视图没有更新

参考技术A由于JavaScript的限制,Vue不能检测数组和对象的变化。但是我们还是有一些办法来回避这些限制并保证它们的响应性。Vue无法检测property的添加或移除。由于Vue会在初始化实例时对property执行getter/setter转化,所以property必... 查看详情

关于vue生命周期遇到的坑???

问题简述:页面第一次跳转之后,数据可以正常渲染,但第二次进入之后,页面无法正常渲染解决问题:之前开发项目时最常用的是created,其他的基本不用,但是这次组件做了缓存之后,发现页面渲染一次之后,再次打开时不... 查看详情

关于vue生命周期遇到的坑???

问题简述:页面第一次跳转之后,数据可以正常渲染,但第二次进入之后,页面无法正常渲染解决问题:之前开发项目时最常用的是created,其他的基本不用,但是这次组件做了缓存之后,发现页面渲染一次之后,再次打开时不... 查看详情

vue2.0深层数组嵌套的坑(代码片段)

...技术博客的包子!今天给大家讲个故事是我在项目中遇到的深层数组嵌套的时候绑定的数组进行赋值的时候竟然不同的数据绑定了同一个实例!这个数组是这样的 [ "categoryAttrId":123, "attName":"尺码", "... 查看详情

vue-pc端项目遇到的坑总结

参考技术A其中说到跨域的话首要的就是axios的配置主要原因是后台使用的是Multipart数组接收,vue这边使用的是formdata,红框一定要这样写,我被坑了好久,并且传输文件类的时候,不需要设置contentType,由于我用axios共同全局设置... 查看详情

记录appwebview内嵌vue单页应用所遇到的坑

...axios还有一套ui框架mint-ui,大致也就这些。下面来扒扒都遇到哪些坑。1.vue-cli脚手架打包的项目部署到服务器上打开空白首先可以确保路径是没问题的,vue-cli打包生产环境直接修改config目录下的index文件即可。将assetsPublicPath指向... 查看详情