Restrict vue/vuex reactivity
Asked Answered
R

4

7

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).

Removable answered 1/6, 2017 at 12:5 Comment(0)
M
15

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.

Morrie answered 1/6, 2017 at 12:27 Comment(7)
I have a doubt, if the searchResults[ ] are in the state of the store and we use computed property in the component to fetch the searchResults[ ] from store vue will just update if there is change in that array léngth or even any change in the item propertiesReveille
With both approaches, the computed property will update when the array content changes.Morrie
if internally every result does not change then the computed property will only update if any items in the array are added or removed. Then why go an extra step and freeze the object if in you know the items in the array are not alteredReveille
I adjusted my Answer with an alternative solution, namely freezing the array and replacing it with a freh one when adding stuff (becaue your can't push() to a frozen array).Morrie
yup that would be a better solution. Thank you for rectifying my doubtReveille
Do you think that one freeze is enough, or something like deepFreeze required?Removable
One is enough. Vue will skip child objects when it encounters a frozen object.Morrie
A
1

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

Advise answered 3/6, 2021 at 10:52 Comment(0)
R
0

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
}
Removable answered 1/6, 2017 at 15:11 Comment(0)
D
0

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.

Doviedow answered 4/6, 2020 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.