vuejs / vuejs/docs

[Enhancement]: Add "Large Mutable Structures" section to /guide/best-practices/performance.html?

Open
#2,311 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Vue
Stars
3.2k
Forks
5k
Avg merge
14d 17h
Merged PRs (30d)
1

Description

On the page about performance best practices, there's a section named Reduce Reactivity Overhead for Large Immutable Structures, which I found very enlightening. For example, if a Vue app fetches a long list of immutable objects used to populate a read-only table, then it makes sense to wrap that list in a shallowRef().

However, after reading the entire guide, I'm not sure what's the best way to handle large mutable state, specifically in terms of performance. For example, if I want to use a store to manage a lot of shared mutable state, which of the following 3 options is better strictly in terms of performance? (i.e. not in terms of code organization/code quality). Note that for simplicity, the state is not that big in the examples below, but image it contained 1000+ properties instead.

Option 1: Use a large and deeply-nested state object wrapped in a single ref()/reactive() instance:
// store.js
import { reactive } from "vue";

export const state = reactive({
    user: {
        name: "Alice",
        email: "",
        age: 0,
        subscribed: false,
    },
    // many other properties, and some of them could be nested 10+ levels
});
// App.vue
<script setup>
import { computed } from "vue";
import { state } from "./store.js";
const text = computed(() => state.user.subscribed ? "Unsubscribe" : "Subscribe");
const toggle = () => state.user.subscribed = !state.user.subscribed;
</script>

<template>
    <h1>Hello, {{ state.user.name }}!</h1>
    <button @click="toggle()">{{ text }}</button>
</template>
Option 2: Split the large state object into many shallow ref()/reactive() instances:
// store.js
import { ref } from "vue";

export const userName = ref("Alice");
export const userEmail = ref("");
export const userAge = ref(0);
export const userSubscribed = ref(false);
// many other refs, but all of them contain primitive types
// App.vue
<script setup>
import { computed } from "vue";
import { userName, userSubscribed } from "./store.js";
const text = computed(() => userSubscribed.value ? "Unsubscribe" : "Subscribe");
const toggle = () => userSubscribed.value = !userSubscribed.value;
</script>

<template>
    <h1>Hello, {{ userName }}!</h1>
    <button @click="toggle()">{{ text }}</button>
</template>
Option 3: Use a large but flat state object wrapped in a single ref()/reactive() instance:
// store.js
import { reactive } from "vue";

export const state = reactive({
    userName: "Alice",
    userEmail: "",
    userAge: 0,
    userSubscribed: false,
    // many other properties, but all of them contain primitive types (i.e. not nested)
});
// App.vue
<script setup>
import { computed } from "vue";
import { state } from "./store.js";
const text = computed(() => state.userSubscribed ? "Unsubscribe" : "Subscribe");
const toggle = () => state.userSubscribed = !state.userSubscribed;
</script>

<template>
    <h1>Hello, {{ state.userName }}!</h1>
    <button @click="toggle()">{{ text }}</button>
</template>

For option 1, my concern is that accessing or mutating very nested properties might cause more "reactivity overhead". For example, does the access of state.user.name and the mutation of store.user.subscribed in option 1 cause more overhead than the access of state.userName and the mutation of state.userSubscribed in option 3. Of course, in this case, the user name in option 1 is not that much more nested, but what if it was state.a.b.c.d.e.user.name instead, and we had hundreds or thousands of similar accesses/mutations across all the components in our app? Perhaps, there is zero overhead but it seems unclear from the documentation.

For option 2, my concern is that using many instances of ref()/reactive() could consume more memory and therefore could slow down performance for a large enough app. That said, I have a feeling that even when using a single ref()/reactive() instance as in options 1 and 3, Vue will recursively create more instances for child properties behind-the-scenes in order to provide "deep reactivity". Unfortunately, the simple implementation of ref() and reactive() in Reactivity in Depth doesn't handle deep reactivity, so it's unclear to me how the real Vue implementation would do it.

In short, code organization aside and strictly based on performance, is there one of these options that is better? I'm not sure if it's something that many Vue developers are struggling to understand (or even curious to know), so it might not warrant an addition to the performance page. You would know best, and thanks for your time!

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the performance best-practices page at guide/best-practices/performance.html and review its existing “Reduce Reactivity Overhead for Large Immutable Structures” section. Compare the three state-management options described in the issue, using the linked Reactivity in Depth and state-management pages as context. Done means deciding whether actionable guidance for large mutable state belongs in the guide and documenting a supported conclusion if it does.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
documentation
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.