Vuex Action vs Mutations

前端 未结 13 1082
囚心锁ツ
囚心锁ツ 2020-12-02 04:37

In Vuex, what is the logic of having both \"actions\" and \"mutations?\"

I understand the logic of components not being able to modify state (which seems smart), but

13条回答
  •  渐次进展
    2020-12-02 05:32

    I have been using Vuex professionally for about 3 years, and here is what I think I have figured out about the essential differences between actions and mutations, how you can benefit from using them well together, and how you can make your life harder if you don't use it well.

    The main goal of Vuex is to offer a new pattern to control the behaviour of your application: Reactivity. The idea is to offload the orchestration of the state of your application to a specialized object: a store. It conveniently supplies methods to connect your components directly to your store data to be used at their own convenience. This allows your components to focus on their job: defining a template, style, and basic component behaviour to present to your user. Meanwhile, the store handles the heavy data load.

    That is not just the only advantage of this pattern though. The fact that stores are a single source of data for the entirety of your application offers a great potential of re-usability of this data across many components. This isn't the first pattern that attempts to address this issue of cross-component communication, but where it shines is that it forces you to implement a very safe behaviour to your application by basically forbidding your components to modify the state of this shared data, and force it instead to use "public endpoints" to ask for change.

    The basic idea is this:

    • The store has an internal state, which should never be directly accessed by components (mapState is effectively banned)
    • The store has mutations, which are synchronous modification to the internal state. A mutation's only job is to modify the state. They should only be called from an action. They should be named to describe things that happened to the state (ORDER_CANCELED, ORDER_CREATED). Keep them short and sweet. You can step through them by using the Vue Devtools browser extension (it's great for debugging too!)
    • The store also has actions, which should be async or return a promise. They are the actions that your components will call when they will want to modify the state of the application. They should be named with business oriented actions (verbs, ie cancelOrder, createOrder). This is where you validate and send your requests. Each action may call different commits at different step if it is required to change the state.
    • Finally, the store has getters, which are what you use to expose your state to your components. Expect them to be heavily used across many components as your application expands. Vuex caches getters heavily to avoid useless computation cycles (as long as you don't add parameters to your getter - try not to use parameters) so don't hesitate to use them extensively. Just make sure you give names that describe as close as possible what state the application currently is in.

    That being said, the magic begins when we start designing our application in this manner. For example:

    • We have a component that offers a list of orders to the user with the possibility to delete those orders
    • The components has mapped a store getter (deletableOrders), which is an array of objects with ids
    • The component has a button on each row of orders, and its click is mapped to a store action (deleteOrder) which passes the order object to it (which, we will remember, comes from the store's list itself)
    • The store deleteOrder action does the following:
      • it validates the deletion
      • it stores the order to delete temporarily
      • it commits the ORDER_DELETED mutation with the order
      • it sends the API call to actually delete the order (yes, AFTER modifying the state!)
      • it waits for the call to end (the state is already updated) and on failure, we call the ORDER_DELETE_FAILED mutation with the order we kept earlier.
    • The ORDER_DELETED mutation will simply remove the given order from the list of deletable orders (which will update the getter)
    • The ORDER_DELETE_FAILED mutation simply puts it back, and modifies to state to notify of the error (another component, error-notification, would be tracking that state to know when to display itself)

    In the end, we have a user experience that is deemed as "reactive". From the perspective of our user, the item has been deleted immediately. Most of the time, we expect our endpoints to just work, so this is perfect. When it fails, we still have some control over how our application will react, because we have successfully separated the concern of the state of our front-end application, with the actual data.

    You don't always need a store, mind you. If you find that you are writing stores that look like this:

    export default {
      state: {
        orders: []
      },
      mutations: {
        ADD_ORDER (state, order) {
           state.orders.push(order)
        },
        DELETE_ORDER (state, orderToDelete) {
           state.orders = state.orders.filter(order => order.id !== orderToDelete.id)
        }
      },
      actions: {
        addOrder ({commit}, order) {
          commit('ADD_ORDER', order)
        },
        deleteOrder ({commit}, order) {
          commit('DELETE_ORDER', order)
        }
      },
      getters: {
        orders: state => state.orders
      }
    }
    

    To me it seems you are only using the store as a data store, and are perhaps missing out on the reactivity aspect of it, by not letting it also take control of variables that your application reacts to. Basically, you can and should probably offload some lines of code written in your components to your stores.

提交回复
热议问题