Vue-next and why Maps solve a big problem

Michael Gallagher
Vue.js Developers
Published in
4 min readOct 7, 2019

So we got the first look at the code for vue-next, still in pre-alpha so some waiting left. But finally we can start to put some of our theories to the test. While Vue 2 does not support ES2015 Collection types, Vue 3 supports Map, Set, WeakMap and WeakSet. For Vue 2, this means that any mapped reactive data must use an Object as a map.

If you use ID lookups for data in your Vuex store, or even if you don’t, you might be aware of a nuisance problem in Vue 2 reactivity.

Say we have the following reactive user data:

{
1: { id: 1, name: 'Alice', ... },
2: { id: 2, name: 'Ben', ... },
3: { id: 3, name: 'Chloe', ... },
}

We might have some computed values on this data like Object.keys(users).length or using any of the common search / aggregation functions like forEach, reduce, find.

And we might also have other entities with references to these entities, i.e. foreign keys. When we look up this hash/map/Object for a foreign key it might look like this: users[blog.authorId]

Problem

When we add a new entry to this ID map, this triggers reactivity for the entire map, it is a well known issue and despite the fact that the new record (ID 4) will make no difference to the foreign key access for blog.authorId (say ID 3), it will still react and depending on the complexity of your relational data, this can be a big issue.

Above is a JSFiddle rendering a list of 3 items (blogs), each one has a foreign key to an author, which also has an ID map in the store. The UI has a parent component and child components. A few seconds after mounting, the store is updated with a new user record, the UI can be seen to refresh all 3 blog UI components.

What do Maps do?

Well we haven’t got a look at the next version of Vuex yet, and even Vue-next isn’t probably that easy to plug into real code yet. But we do now have access to the unit tests in the Vue-next codebase and thankfully there are plenty and we have a test rig to start playing with this new functionality.

You can find the Map.spec.ts on GitHub, and you can run the entire Jest suite quite easily with Node 10+ and Yarn. I use VSCode and can debug the Jest process in the IDE easily and breakpoint the specs directly.

The exact test I was looking for, wasn’t actually there, no doubt for the authoring developers it was an obvious logical branch and didn’t need to be covered. But here is the closest test I found:

it('should not observe non value changing mutations', () => {
let dummy
const map = reactive(new Map())
const mapSpy = jest.fn(() => (dummy = map.get('key')))
effect(mapSpy)
expect(dummy).toBe(undefined)
expect(mapSpy).toHaveBeenCalledTimes(1)
map.set('key', 'value')
expect(dummy).toBe('value')
expect(mapSpy).toHaveBeenCalledTimes(2)
map.set('key', 'value')
expect(dummy).toBe('value')
expect(mapSpy).toHaveBeenCalledTimes(2)
map.delete('key')
expect(dummy).toBe(undefined)
expect(mapSpy).toHaveBeenCalledTimes(3)
map.delete('key')
expect(dummy).toBe(undefined)
expect(mapSpy).toHaveBeenCalledTimes(3)
map.clear()
expect(dummy).toBe(undefined)
expect(mapSpy).toHaveBeenCalledTimes(3)
})

This test intends to test scenarios where reactivity should not fire. Here the effect is a function with a Jest spy, to track how many times it runs.

If you haven’t had a chance to look over the new reactivity system, here is an RFC document, possibly dated, but certainly it will cover the introduction.

So an effect is essentially some functionality which “reacts” to reactive data. Back to the test, this Map starts empty, registers an effect which accesses an entry with the key ‘key’, and then proceeds to modify the map and assert the times the effect runs and the value it produces.

Reading this, we know that adding an entry after an effect has been registered on it will trigger reactivity. Sounds obvious, but maybe not, good to see it confirmed!

We know that setting the same value again will not trigger the effect, and deleting the entry will trigger it, but not another delete. Good so far.

But what about adding a different key? Again, this may seem obvious, but for you and me reading this for the first time, best update the test.

After the first key was added…

map.set('anotherKey', 'anotherValue') // new key
expect(mapSpy).toHaveBeenCalledTimes(2) // effect doesn't fire

I ran the tests with this, and was glad to see they passed! Nice one, this means that new keys in a Map do not trigger effects on other keys.

This means the Vue 2 problem is fixed. And if you never came across this issue with Vue 2, rest assured you certainly won’t come across it with Vue 3!

Congrats to the Vue team on a big milestone, really great to see these progressive changes, this hard work is very much appreciated.

--

--

Michael Gallagher
Vue.js Developers

Living in the vibrant city of Buenos Aires, developing eCommerce software with Hip. Modern JS is my passion, Vue my tool of choice