Vuex Action vs Mutations

前端 未结 13 1085
囚心锁ツ
囚心锁ツ 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:18

    Disclaimer - I've only just started using vuejs so this is just me extrapolating the design intent.

    Time machine debugging uses snapshots of the state, and shows a timeline of actions and mutations. In theory we could have had just actions alongside a recording of state setters and getters to synchronously describe mutation. But then:

    • We would have impure inputs (async results) which caused the setters and getters. This would be hard to follow logically and different async setters and getters may surprisingly interact. That can still happen with mutations transactions but then we can say the transaction needs to be improved as opposed to it being a race condition in the actions. Anonymous mutations inside an action could more easily resurface these kinds of bugs because async programming is fragile and difficult.
    • The transaction log would be hard to read because there would be no name for the state changes. It would be much more code-like and less English, missing the logical groupings of mutations.
    • It might be trickier and less performant to instrument recording any mutation on a data object, as opposed to now where there are synchronously defined diff points - before and after mutation function call. I'm not sure how big of a problem that is.

    Compare the following transaction log with named mutations.

    Action: FetchNewsStories
    Mutation: SetFetchingNewsStories
    Action: FetchNewsStories [continuation]
    Mutation: DoneFetchingNewsStories([...])
    

    With a transaction log that has no named mutations:

    Action: FetchNewsStories
    Mutation: state.isFetching = true;
    Action: FetchNewsStories [continuation]
    Mutation: state.isFetching = false;
    Mutation: state.listOfStories = [...]
    

    I hope you can extrapolate from that example the potential added complexity in async and anonymous mutation inside actions.

    https://vuex.vuejs.org/en/mutations.html

    Now imagine we are debugging the app and looking at the devtool's mutation logs. For every mutation logged, the devtool will need to capture a "before" and "after" snapshots of the state. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is committed, and there's no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable!

    0 讨论(0)
  • 2020-12-02 05:20

    I think the TLDR answer is that Mutations are meant to be synchronous/transactional. So if you need to run an Ajax call, or do any other asynchronous code, you need to do that in an Action, and then commit a mutation after, to set the new state.

    0 讨论(0)
  • 2020-12-02 05:20

    It might seem unnecessary to have an extra layer of actions just to call the mutations, for example:

    const actions = {
      logout: ({ commit }) => {
        commit("setToken", null);
      }
    };
    
    const mutations = {
      setToken: (state, token) => {
        state.token = token;
      }
    };
    

    So if calling actions calls logout, why not call the mutation itself?

    The entire idea of an action is to call multiple mutations from inside one action or make an Ajax request or any kind of asynchronous logic you can imagine.

    We might eventually have actions that make multiple network requests and eventually call many different mutations.

    So we try to stuff as much complexity from our Vuex.Store() as possible in our actions and this leaves our mutations, state and getters cleaner and straightforward and falls in line with the kind of modularity that makes libraries like Vue and React popular.

    0 讨论(0)
  • 2020-12-02 05:29

    1.From docs:

    Actions are similar to mutations, the differences being that:

    • Instead of mutating the state, actions commit mutations.
    • Actions can contain arbitrary asynchronous operations.

    The Actions can contain asynchronous operations, but the mutation can not.

    2.We invoke the mutation, we can change the state directly. and we also can in the action to change states by like this:

    actions: {
      increment (store) {
        // do whatever ... then change the state
        store.dispatch('MUTATION_NAME')
      }
    }
    

    the Actions is designed for handle more other things, we can do many things in there(we can use asynchronous operations) then change state by dispatch mutation there.

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-02 05:32

    Because there’s no state without mutations! When commited — a piece of logic, that changes the state in a foreseeable manner, is executed. Mutations are the only way to set or change the state (so there’s no direct changes!), and furthermore — they must be synchronous. This solution drives a very important functionality: mutations are logging into devtools. And that provides you with a great readability and predictability!

    One more thing — actions. As it’s been said — actions commit mutations. So they do not change the store, and there’s no need for these to be synchronous. But, they can manage an extra piece of asynchronous logic!

    0 讨论(0)
提交回复
热议问题