Does Vue support reactivity on Map and Set data types?

vue.js

vue.js Problem Overview


The docs for Vue.js mention the smart auto-change-tracking for plain Javascript objects:

> When you pass a plain JavaScript object to a Vue instance as its data option, Vue.js will walk through all of its properties and convert them to getter/setters using Object.defineProperty.

Since Javascript's Map and Set datatypes are designed to be used with their in-built get/set methods, how can I get Vue to track calls (and therefore, changes) to the internal state of Maps and Sets?

vue.js Solutions


Solution 1 - vue.js

Vue.js does not support reactivity on Map and Set data types (yet?).

The feature ticket has some discussion and this work around (by user "inca"):

> Sets and Maps are not observable by Vue. In order to use those — > either in v-for, or in computed properties, methods, watchers, > template expressions, etc. — you need to create a serializable replica > of this structure and expose it to Vue. Here's a naive example which > uses a simple counter for providing Vue with information that Set is > updated: > > 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; > } > } > > This illustrates a kinda hacky but 100% working method for making > non-observable data reactive. Still, in real world cases I ended up > with serialized versions of Sets/Maps (e.g. you'd probably want to > store the modified versions of sets/maps in localstorage and thus > serialize them anyway), so no artificial counters/hacks were involved.

Solution 2 - vue.js

As far as I know, Vue's reactivity tracks assignments. If you perform an assignment on your set, it should track reactivity, for example:

methods: {
  add(item) {
    this.mySet = new Set(this.mySet.add(item));
  }
}

This gives you a cleaner code, but with an obvious problem: performance.

Just pick your solution according to your needs :)

Solution 3 - vue.js

Vue 3

Yes, it does.

Vue 3 docs cover these at Reactivity in depth and Basic Reactivity APIs.

There are four kinds of "reactive" and the "reference" in addition. I'm still trying to find the usage patterns that suit me, after 6+ months with Vue 3 and ES6 coding. The main question is, whether to use Reactive or Reference.

Reference

Reference is the easy way to go. This is what gets returned by computed, for example. It makes a reactive version of one's JavaScript value, e.g. a Map or a Set.

There's a gotcha. If one uses Reference in one's JavaScript, a .value needs to be added to dereference the value. Using it in template HTML does not need that addition. This makes Reference perfect for UI facing things, but less so for internal programming.

I currently add Ref postfix to the name of any value or function that provides a Reference. That's just me. It's easy to get confused if one uses both Reference and Reactive (TypeScript would help, here).

Reactive

Reactive is made for Map-like use. One can initialise it as:

const rve = reactive( new Map() )

Accessing such does not need the .value. However, it seems Reactive does not have the enumeration methods (e.g. .entries()) that would allow it to be used Map-like. Therefore, it seems aimed at a use case where you know the keys of an object. But this may change.

I wish Reactive was brought in the direction that it can be used as a 1:1 replacement for ES Map. This would make it easy for me: Reactive for Maps and Reference for the rest.

I would also wish the name would change, to bring them closer. RMap would be fine - maybe I'll make one (derive from Reactive and add the enumeration methods).

Summary

The strong answer, with Vue 3, is "YES".

However, the developer guidance can be made more straightforward, clearly stating which would be the logic for picking Reference or Reactive, and what eg. their runtime pros and cons are, without needing to read various blog posts.


Edit: My current leaning is towards Ref, but I try to unwrap the reactivity quite early within the code, leading to just one .value within a computed.

Solution 4 - vue.js

Using @vue/composition-api I have come up with the following solution for Vue 2.

// useReactiveSet.ts
import { ref, computed } from '@vue/composition-api'

export default function useReactiveSet<T>() {
  const version = ref(1)
  class ReactiveSet extends Set<T> {
    add(value: T) {
      super.add(value)
      version.value += 1
      return this
    }
  }
  const inner = ref(new ReactiveSet())

  const set = computed(() => {
    version.value
    return inner.value
  })

  return set
}

// Component.vue
{
  // ...
  setup() {
    const set = useReactiveSet<number>()

    setTimeout(() => {
      // this will trigger rerender
      set.value.add(2)
    }, 1000)

    return {
      set,
    }
  },
  // ...
}

You can use the same approach for Map.

There is package vue-reactive-collection which adds support for reactive Map and Set in Vue 2.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBryceView Question on Stackoverflow
Solution 1 - vue.jsHillView Answer on Stackoverflow
Solution 2 - vue.jsluixalView Answer on Stackoverflow
Solution 3 - vue.jsakauppiView Answer on Stackoverflow
Solution 4 - vue.jsAndrew VasilchukView Answer on Stackoverflow