Vuex State Management for Vue.js

repository·main·Indexed 12 days ago

https://github.com/vuejs/vuex

A centralized state management library for Vue.js applications (version 4.1.0) that ensures predictable state mutations and integrates with Vue Devtools. It provides a structured way to manage state through a store initialized with createStore, utilizing mutations for synchronous state changes and actions for asynchronous business logic. Includes support for the Composition API via the useStore composable, dynamic module registration, and component binding helpers like mapState and mapActions.

Tokens
35.9K
Snippets
134
Records
151
Agent score
98%

What's inside Vuex

  1. What is Vuex and when to use it

    main

    Vuex is a state management pattern and library for Vue.js applications. It provides a centralized store for all components in an application, enforcing rules that ensure state mutations are predictable.

    Core Concepts

    A Vue application typically consists of:

    • State: The source of truth that drives the app.
    • View: A declarative mapping of the state.
    • Actions: The possible ways the state changes in reaction to user inputs.

    Vuex implements a "one-way data flow" to solve the complexity of sharing state between multiple components (such as deeply nested components or siblings) without the brittleness of prop-drilling or manual event synchronization.

    When to use Vuex

    • Small Apps: You may not need Vuex. A simple store pattern or standard Vue component data might suffice. Using Vuex in a simple app adds unnecessary boilerplate.
    • Medium-to-Large SPAs: Vuex is recommended when you encounter complex shared state requirements that make managing state within individual components difficult or unmaintainable.

    Note on Pinia

    Pinia is now the official state management library for Vue. While Vuex 3 and 4 are maintained, Pinia is the recommended choice for new projects. Pinia offers an enhanced API and is essentially the successor to the concepts introduced in Vuex 5.

  2. What is a Vuex store?

    main

    A Vuex store is a reactive container that holds your application's state. Unlike a plain global object, Vuex stores provide two key features:

    1. Reactivity: When Vue components retrieve state from the store, they automatically and efficiently update when the state changes.
    2. Controlled Mutations: You cannot directly mutate the store's state. State changes must be performed by explicitly committing mutations. This creates a trackable record of every change, enabling advanced debugging tools like time-travel debugging and state snapshots.
  3. What is a Vuex plugin and how to use it

    main

    A Vuex plugin is a function that receives the store as its only argument. Plugins are used to expose hooks to every mutation via store.subscribe. They are initialized when the store is created.

    To use a plugin, pass it in the plugins array within the createStore options object.

    const myPlugin = (store) => {
      // called when the store is initialized
      store.subscribe((mutation, state) => {
        // called after each mutation.
        // mutation is in the format `{ type, payload }`
      })
    }
    
    const store = createStore({
      // ...
      plugins: [myPlugin]
    })
  4. Understand the Single State Tree concept

    main
    Vuex uses a single state tree, meaning a single object contains all application-level state and serves as the "single source of truth." This architecture makes it easy to locate specific state and take snapshots of the entire app state for debugging. While the state is centralized, it can be organized into sub-modules to maintain modularity. The state object must be a plain object, following the same rules as the data option in a Vue instance.
  5. Access local and root state in modules

    main

    Inside a module's logic, Vuex provides access to both the local module state and the global root state:

    • Mutations & Getters: The first argument is the local module state. In getters, the root state is provided as the 3rd argument.
    • Actions: The context object provides context.state (local) and context.rootState (global).
    const moduleA = {
      state: () => ({
        count: 0
      }),
      mutations: {
        increment (state) {
          // `state` is the local module state
          state.count++
        }
      },
      getters: {
        doubleCount (state) {
          return state.count * 2
        },
        sumWithRootCount (state, getters, rootState) {
          // rootState is the 3rd argument
          return state.count + rootState.count
        }
      },
      actions: {
        incrementIfOddOnRootSum ({ state, commit, rootState }) {
          // context.state is local, context.rootState is global
          if ((state.count + rootState.count) % 2 === 1) {
            commit('increment')
          }
        }
      }
    }
  6. Understanding the State Management Pattern

    main

    The state management pattern is based on a one-way data flow consisting of three main parts:

    1. State: The single source of truth that drives the application.
    2. View: A declarative mapping of the state.
    3. Actions: The possible ways the state can change in response to user interactions in the view.

    Why use a centralized store?

    In complex applications, multiple components often need to share the same state. Relying on props for deeply nested components or using events to sync state between siblings is fragile and difficult to maintain.

    By extracting shared state into a global singleton (the Vuex Store), any component in the tree can access the state or trigger actions, regardless of its position. This provides structure and improves maintainability by separating state logic from the view logic.

    const Counter = {
      // state
      data () {
        return {
          count: 0
        }
      },
      // view
      template: `
        <div>{{ count }}</div>
      `,
      // actions
      methods: {
        increment () {
          this.count++
        }
      }
    }
  7. Take state snapshots in a plugin

    main

    To compare the state before and after a mutation, a plugin must perform a deep copy of the state object.

    Warning: Plugins that take state snapshots should only be used during development due to the performance overhead of deep-copying the state.

    const myPluginWithSnapshot = (store) => {
      let prevState = _.cloneDeep(store.state)
      store.subscribe((mutation, state) => {
        let nextState = _.cloneDeep(state)
    
        // compare `prevState` and `nextState`...
    
        // save state for next mutation
        prevState = nextState
      })
    }
    
    // Use environment checks to ensure this only runs in development
    const store = createStore({
      // ...
      plugins: process.env.NODE_ENV !== 'production'
        ? [myPluginWithSnapshot]
        : []
    })
  8. Capturing state snapshots in plugins

    main

    To compare the state before and after a mutation, a plugin must perform a deep copy of the state object (e.g., using _.cloneDeep).

    Warning: Plugins that capture state snapshots should only be used during development. In production, you should disable them to avoid performance overhead. You can use environment variables to conditionally include them:

    const store = createStore({
      // ...
      plugins: process.env.NODE_ENV !== 'production'
        ? [myPluginWithSnapshot]
        : []
    })
    const myPluginWithSnapshot = (store) => {
      let prevState = _.cloneDeep(store.state)
      store.subscribe((mutation, state) => {
        let nextState = _.cloneDeep(state)
    
        // compares `prevState` and `nextState`...
    
        // saves state for the next mutation
        prevState = nextState
      })
    }
  9. What are Vuex getters and how to define them

    main

    Vuex getters are functions that allow you to compute derived state based on the store state. They act like computed properties for your store, providing a centralized way to share logic that filters or transforms state across multiple components.

    Getters receive the state as their first argument. They can also receive the getters object as their second argument, allowing you to compose getters by using other existing getters.

    ::: warning WARNING As of Vue 3.0, the getter's result is not cached as the computed property does. This is a known issue that requires Vue 3.1 to be released. :::

    const store = createStore({
      state: {
        todos: [
          { id: 1, text: '...', done: true },
          { id: 2, text: '...', done: false }
        ]
      },
      getters: {
        doneTodos (state) {
          return state.todos.filter(todo => todo.done)
        },
        doneTodosCount (state, getters) {
          return getters.doneTodos.length
        }
      }
    })
  10. Compose actions using Promises and async/await

    main

    Since actions are often asynchronous, store.dispatch returns a Promise if the action handler returns a Promise. This allows you to chain actions or wait for them to complete.

    • Chaining with .then(): You can call .then() on the result of store.dispatch to execute code after the action finishes.
    • Action Composition: An action can dispatch another action and wait for it to resolve before proceeding.
    • Async/Await: You can use async/await syntax within action handlers to write cleaner asynchronous logic.

    Note: If store.dispatch triggers multiple action handlers across different modules, the returned Promise resolves only when all dispatched handlers have resolved.

    // Example of using async/await in actions
    actions: {
      async actionA ({ commit }) {
        commit('gotData', await getData())
      },
      async actionB ({ dispatch, commit }) {
        await dispatch('actionA') // waits for actionA to finish
        commit('gotOtherData', await getOtherData())
      }
    }
  11. Use namespacing for self-contained modules

    main

    By default, actions, mutations, and getters are registered in the global namespace. To make a module self-contained and avoid name collisions, set namespaced: true.

    When namespaced, all assets are automatically prefixed with the module's registration path (e.g., account/login). Nested modules inherit the parent's namespace unless they also set namespaced: true.

    const store = createStore({
      modules: {
        account: {
          namespaced: true,
          state: () => ({ ... }),
          getters: {
            isAdmin () { ... } // -> getters['account/isAdmin']
          },
          actions: {
            login () { ... } // -> dispatch('account/login')
          },
          mutations: {
            login () { ... } // -> commit('account/login')
          },
    
          modules: {
            // inherits 'account' namespace
            myPage: {
              state: () => ({ ... }),
              getters: {
                profile () { ... } // -> getters['account/profile']
              }
            },
            // further nests the namespace
            posts: {
              namespaced: true,
              state: () => ({ ... }),
              getters: {
                popular () { ... } // -> getters['account/posts/popular']
              }
            }
          }
        }
      }
    })
  12. Reuse modules safely with state functions

    main

    If you need to register the same module multiple times or create multiple stores using the same module, do not use a plain object for the state. Using a plain object causes the state to be shared by reference, leading to cross-module state pollution.

    Instead, define the module's state as a function that returns the state object. This ensures each instance of the module gets its own unique state.

    const MyReusableModule = {
      state: () => ({
        foo: 'bar'
      }),
      // mutations, actions, getters...
    }