最近在项目中,遇到一个问题,VueJS在递归组件时候,更改数组数据中的值,列表不进行重新渲染,查文档只解决了一部分,网上也没有相关的解决方法,在此做一个总结。
首先贴两段demo代码
第一段app.vue
<template>
<div class="tree-menu">
<ul v-for="item in theModel">
<my-tree :model="item">my-tree>
ul>
<div @click="change">
change the model
div>
div>
template>
<script>
import myTree from './components/treeMenu.vue'
import axios from 'axios'
export default {
name: 'app',
components: {
myTree
},
data() {
return {
theModel:[]
}
},
methods:{
change(){
this.theModel[0].menuName = '12312312'
console.log(this.theModel)
},
init() {
var Json = []
for(var i = 0; i<10 ;i++){
var children1 = []
for(var j = 0;j<10;j++){
var row1 = []
row1.menuName = '哈哈'
children1.push(row1)
}
var row2 = [];
row2.menuName = '呵呵'
row2.children = children1
Json.push(row2)
}
this.theModel = Json
console.log(this.theModel)
}
},
mounted() {
this.init();
}
}
script>
第二段 treeMenu.vue
<template>
<li>
<span @click="toggle">
<i v-if="isFolder" class="icon" :class="[open ? 'folder-open': 'folder']">i>
<i v-if="!isFolder" class="icon file-text">i>
{{ model.menuName }}
span>
<ul v-show="open" v-if="isFolder">
<tree-menu v-for="item in model.children" :key="item.id" :model="item">tree-menu>
ul>
li>
template>
<script>
export default {
name: 'treeMenu',
props: ['model'],
data() {
return {
open: false
}
},
computed: {
isFolder() {
return this.model.children && this.model.children.length
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.open = !this.open
}
}
}
}
代码省略掉了css部分,初始化数据也是随便写的。代码很简单,app.vue调用treeMenu组件,然后treeMenu组件进行递归,从而形成一个动态树级菜单,效果如下。
我的项目里需要我触发一个事件的时候对theModel里的某个属性值做修改,这里我写了一个change方法,用@click绑定在了change the model上,希望点击的时候把第一行的‘呵呵’给改掉。可是在我点击后,console.log中,theModel打印出来的值中第一行的‘呵呵’已经改变,可在列表中并没有重新渲染,既然vue实现的时数据双向绑定,那么在model层发生了变化之后为什么就没有在view层更新呢?
经过几天的探索,终于找到了原因所在:
项目里的数据是从API拿的,但是格式和我需要的不一样,于是我用以下方法重新拼接除了我需要的格式
var json = [];
var row1 = {};
row1.id= "1";
row1.name = "jyy";
或者var row2 = {id:'2',name:'abc'}
json.push(row1);
json.push(row2);
结果好死不死,我粗心的把row1定义为数组了,即row1 = [];
于是我的Json对象被定义为了‘关联数组’
这有什么区别?
数组和对象的一个区别是,数组中的数据没有“名称”(name),对象中的数据有“名称”(name)。但很多编程语言中,都有一种叫做“关联数组”(associativearray)的东西。这种数组中的数据是有名称的。比如在javascript中,可以这样定义一个对象:
var a={“城市”:”北京”,”面积”:16800,有趣,”人口”:1600};
但是,也可以定义成一个关联数组:
var a = new Array();
a[“城市”]=”北京”;
a[“面积”]=16800;
a[“人口”]=1600;
这样一来好像数组和集合就没有区别了,在Javascript语言中,关联数组就是对象,对象就是关联数组,唯一的区别在于,第二种方式创建的关联数组是有length属性的。
但是在Vue中,这种关联数组与对象集合的区别就有些明显了。
Vue在我们的data对象上都会定义一个ob属性指向新创建的Observer对象,以此对数据设置的监控器,一般都是不可枚举的。但由于JavaScript限制(底层原理不明),Vue无法对关联数组进行Observer对象创建,因此不能检测到数组对象的变化。
观察控制台打印的数据,也可发现关联数组和对象在Vue中的区别:
上半部分为对象,下半部分为关联数组
那么回到正题,我在项目中遇到的问题该怎么解决?
第一种方案:将自己拼接的Json改为对象集合的格式。
但是,当你命不好不小心碰到了这种关联数组结构的数据,又不好拼接的时候怎么办,这里提供第二种方案:
使用 Vue.set(array, indexOfArray,value)
三个值分别是,要修改的数组,数组的下表,和新的值
当然也可以用 `this.$set(array, indexOfArray,value)
你以为这就完了?由于我的代码是递归组件,当我把Data绑定到theModel后,用 v-for=‘item in theModel’遍历,再把item通过v-bind绑定到子组件,但是由于item我没有在组件中使用过,即使Vue检测到theModel数组的变化,也不能检测到item中数组的变化,所以这里强制你需要在代码中加入使用item,类似
,
或者用
这是我误打误撞发现的,当然,这很蠢。
所以你知道用vue.set的方法修改数组可以让vue检测到就行,不要使用第二种方法。