最近在项目中,遇到一个问题,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,类似<my-tree :model="item">{{item}}</my-tree>
或者用<div v-if = 'false'>{{item}}<div>
这是我误打误撞发现的,当然,这很蠢。
所以你知道用vue.set的方法修改数组可以让vue检测到就行,不要使用第二种方法。

Logo

前往低代码交流专区

更多推荐