vue笔记-vue项目中对象数组数据变化,但视图未更新的解决方案(待项目细节描述)?

栏目: JavaScript · 发布时间: 6年前

内容简介:在负责的后台管理系统中,我的添加人员与编辑人员两个功能共用了一个组件,但是遇见一个问题.同样是用v-for去渲染一些标签,在使用编辑人员功能时,删除对象数组元素,对应的标签在页面上也会消失.但是在使用添加人员功能时,删除对象数组元素,对应的标签却不会在页面上消失.功能描述可能不详细,周末再上图!这里的实现目的很明确 --- 我希望在点击li时先检测是否存在,当然是不存在的,所以就将值设置为1, 如果再次点击,就让数字累加。但是出现的问题是: 点击之后数字并没有在view层更新,而通过console打印发现

在负责的后台管理系统中,我的添加人员与编辑人员两个功能共用了一个组件,但是遇见一个问题.同样是用v-for去渲染一些标签,在使用编辑人员功能时,删除对象数组元素,对应的标签在页面上也会消失.但是在使用添加人员功能时,删除对象数组元素,对应的标签却不会在页面上消失.功能描述可能不详细,周末再上图!

demo1:对象数组

<!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]++;
           }
           console.log(this.numbers);
        }
      }
    });
  </script>
</body>
</html>
复制代码

这里的实现目的很明确 --- 我希望在点击li时先检测是否存在,当然是不存在的,所以就将值设置为1, 如果再次点击,就让数字累加。但是出现的问题是: 点击之后数字并没有在view层更新,而通过console打印发现数据确实更新了,只是view层没有及时的检测到, 而我一直以来的想法就是: 既然vue实现的时数据双向绑定,那么在model层发生了变化之后为什么就没有在view层更新呢?

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

demo2: 非对象数组

<!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不能检测到属性被添加的限制。

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

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

访问器属性不能直接定义,必须是用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 重新计算,从而致使它关联的组件得以更新

vue笔记-vue项目中对象数组数据变化,但视图未更新的解决方案(待项目细节描述)?

即在渲染的过程中就会调用对象属性的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}]
复制代码
vue笔记-vue项目中对象数组数据变化,但视图未更新的解决方案(待项目细节描述)?

参考文档:Object.ovserve()

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

解决方法

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

解决代码示例:

<!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() 的方式

如果是在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笔记-vue项目中对象数组数据变化,但视图未更新的解决方案(待项目细节描述)?

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

原作者链接: www.cnblogs.com/zhuzhenwei9…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First HTML5 Programming

Head First HTML5 Programming

Eric Freeman、Elisabeth Robson / O'Reilly Media / 2011-10-18 / USD 49.99

What can HTML5 do for you? If you're a web developer looking to use this new version of HTML, you might be wondering how much has really changed. Head First HTML5 Programming introduces the key featur......一起来看看 《Head First HTML5 Programming》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具