7
votes

Let's assume we have some array of objects, and these objects never change. For example, that may be search results, received from google maps places api - every result is rather complex object with id, title, address, coordinates, photos and a bunch of other properties and methods.

We want to use vue/vuex to show search results on the map. If some new results are pushed to the store, we want to draw their markers on the map. If some result is deleted, we want to remove its marker. But internally every result never changes.

Is there any way to tell vue to track the array (push, splice, etc), but not to go deeper and do not track any of its element's properties?

For now I can imagine only some ugly data split - keep the array of ids in vue and have separate cache-by-id outside of the store. I'm looking for a more elegant solution (like knockout.js observableArray).

4

4 Answers

14
votes

You can use Object.freeze() on those objects. This comes with a (really tiny!) performance hit, but it should be negligible if you don't add hundreds or thousands of objects at once.

edit: Alternatively, you could freeze the array (much better performance) which will make Vue skip "reactifying" its contents.

And when you need to add objects to that array, build a new one to replace the old one with:

state.searchResults = Object.freeze(state.searchResults.concat([item]))

That would be quite cheap even for bigger arrays.

0
votes

At the second glance data split seems not so ugly solution for this task. All that we need is using getters instead of the raw vuex state. We suppose that incoming results is an array with any objects that have unique id field. Then the solution could look like:

const state = {
    ids: []
}

let resultsCache = {};

const getters = {
    results: function(state) {
        return _.map(state.ids,id => resultsCache[id]);
    }
}

const mutations = {
    replaceResults: function(state,results) {
        const ids = [];
        const cache = {};
        (results||[]).forEach((r) => {
            if (!cache[r.id]) {
                cache[r.id] = r;
                ids.push(r.id);
            }
        });
        state.ids = ids;
        resultsCache = cache;
    },
    appendResults: function(state,results) {
        (results||[]).forEach((r) => {
            if (!resultsCache[r.id]) {
                resultsCache[r.id] = r;
                state.results.push(r.id);
            }
        });
    }
}

export default {
    getters,
    mutations,
    namespaced: true
}
0
votes

I created a fork out of vue called vue-for-babylonians to restrict reactivity and even permit some object properties to be reactive. Check it out here.

With it, you can tell Vue to not make any objects which are stored in vue or vuex from being reactive. You can also tell Vue to make certain subset of object properties reactive. You’ll find performance improves substantially and you enjoy the convenience of storing and passing large objects as you would normally in vue/vuex.

0
votes

You can use shallowRef to achieve this.

First import it:

import {shallowRef} from 'vue';

In your mutations you can have a mutation like this:

mutations: {
    setMyObject(state, payload) {
      state.myObject = shallowRef(payload.value);
    },
}

This will track replacing the object, but not changes to the objects properties.

For completeness here is the documentation to shallowRef:

https://v3.vuejs.org/api/refs-api.html#shallowref