Set & Map 数据结构是无法被 Vue 监听的

这次在写业务的时候因为是无限层级的递归表单,用了 provide/inject 来暴露注册以便收集数据。所以就想要用 Map 数据结构来处理数据的收集等等功能。直接使用 记录ID 作为键名就可以了,这样也可以直接去重。但是写完了业务代码之后发现我把后代的表单注册进来并没有触发响应,导致数据回填失败。

所以想着是不是 Vue 2x 无法监听 Map 或者 Set 的数据变更的。就去检索了一下相关的信息,发现 Vue 2x 真的无法监听 Map 以及 Set 俩兄弟。只能够通过修改其他变量来曲线救国。

Vue 3x 通过 Proxy 来代理就没有了这个问题。


想要在 Vue2 中来实现我上面的需求的话:

#方法1. 改写成 数组 对象。每次去 find 一下是否已经存在上级元素,然后插入到 child 中,但是对于层级比较深的就不是特别好使了,需要递归才行。

所以我先 const list = [] 声明了一个数组对象,同时也 const tmpMapper = new Map() 声明一个 Map 数据对象把这些 一级祖先 .set 进去,因为是同一个引用所以 数组内的数据 和 Map 内的数是同一个,直接修改 Map 内的属性 数组内的元素也会变更。
然后开始遍历通过 parentId 关联的平级结构列表,因为是从前往后正序排列的,所以直接通过 tmpMapper.get(item.parentId) 就可以获取到父级了,然后 push 当前元素到父级的 child 当中,同时也 .settmpMapper 当中。如此往复,这样就可以把通过 parentId 关联的平级结构列表转成树结构了。最后再把 list 赋值给在 data 中提前声明好的数组变量就可以了。

只是后续操作修改的时候都会比较麻烦。不过好在我只有初始化的时候需要这样创建一次就行了。之后通过转换成树状的数组递归渲染即可。

#方法2. 添加一个标识符,在每次操作 Map 对象的时候去给这个标识符 +1,这样通过监听数字标识符的变更来触发更新。

一般来说这样都是通过 computed 来把 Map 对象转为数组。因为其实最终在 template 中循环的时候其实也是一个可递归的变量,所以使用 Array.form() 直接转换成数组使用就行了。

data() {
  mySetChangeTracker: 1,
  mySet: new Set(),
},
  
computed: {
  mySetAsList() { 
    // By using `mySetChangeTracker` we tell Vue that this property depends on it,
    // so it gets re-evaluated whenever `mySetChangeTracker` changes
    return this.mySetChangeTracker && Array.from(this.mySet);
  },
},

methods: {
  add(item) {
    this.mySet.add(item);
    // Trigger Vue updates
    this.mySetChangeTracker += 1;
  }
}

相关阅读

[Feature]: Add support of the iterator protocol (es6) for v-for directive (map, weakset, weakmap) · Issue #1319 · vuejs/vue
vuejs doesn’t support Sets as backing fields for binding (nor maps, weakmap, weakset reactivity) · Issue #5241 · vuejs/vue
Support more collection data types in v-for · Issue #2410 · vuejs/vue