Vuex is a centralized state management architecture designed specifically for vue.js applications.
Background: Each component in a small application maintains its own state, a portion of the current application’s state, so the state of the entire application is scattered around, but it is common to share a portion of the state with multiple components.
States can be thought of figuratively as properties in our data.
State
Vuex uses a single state tree, where a single store object stores the state of the entire application layer. It makes it easier to locate a specific state and take a snapshot of the entire current application when debugging.
- Let’s set the stage. The single State tree used by Vuex does not conflict with modularity. The question is, how do you divide state and mutation into submodules?
- To use store, you must first
Vue.user(Vuex)
And then storeconst store = new Vuex.store()
Inject is defined in Vue instance APPnew Vue({store})
, the implementation is injected from the root component into all child components and can then be used in child componentsthis.$store
The call. - When a component needs to use multiple store state properties or getters, it can use a shared helper
mapState
, it will return an object.
it('helper: mapState (object)', () => { const store = new Vuex.Store({ state: { a: 1 }, getters: { b: () => 2 } }) const vm = new Vue({ store, computed: MapState ({// in mapState we can call store state and store getters a: (state, getters) => { return state.a + getters.b } }) }) expect(vm.a).toBe(3) store.state.a++ expect(vm.a).toBe(4) })Copy the code
So how do you use it with local computed properties? Typically, we use a tool that combines multiple objects into one and passes the resulting object to computed. But here we can do this super succinctly using the Object spread operator, the object extension operator, directly from ES6’s Stage 3.
Computed: {localComputed () {} // merge its attributes with localComputed attributes... mapState({ message: state => state.obj.message }) }Copy the code
Getters
Sometimes we need to derive other states from the store state and then use that state in multiple components. The usual approach is to copy this method or encapsulate it as a common method and import it when needed, but neither is really ideal. Vuex provides the getters attribute, which is used similarly to the computed attribute in stores. The method in getters takes two arguments, state and getters (other getters), as follows.
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}Copy the code
Then it becomes easy to use getters inside other components
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}Copy the code
- MapGetters maps store getters to local computed properties. In addition to using arrays, you can also use object aliases.
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])Copy the code
Mutations
The only way to change the state in the Vuex Store is to commit a mutation change. Mutation is similar to events: both have string types and handler handles. We actually modify state in handler, where state is the first parameter of each mutation.
const store = new Vuex.Store({ state: { count: 1 }, mutations: Increment (state) {// mutate state state.count++}}}) Handler store.com MIT ('increment') can only be called when using type increment to call mutationCopy the code
The second optional parameter of commit is payload, which can be a normal type, object type, and so on. The commit method can also be called as an object, in which case the object is treated as payload.
store.commit({
type: 'increment',
amount: 10
})Copy the code
- little tips
- It is recommended that you use uppercase Mutation to store all uppercase variables in a file and introduce them as needed. Use constants as method names using es6’s new computed attribute names feature.
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'Copy the code
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})Copy the code
Es6 evaluates the attribute name
/ / um participant: variables that contain Spaces as the attribute name complains, at this point it can be deposited as a string or the brackets package exists in var lastName = "last name"; Var person = {"first name": "Nicholas", // "lastName ": "Zakas"}; console.log(person["last name"]); // ZakasCopy the code
- Mutations must all be synchronous, and its changes must be implemented immediately after it is called because it is the only one that can modify state. If it uses an asynchronous method, it will make our state untraceable, making it difficult to locate the problem
- This. codestore.mit () can be used for commit mutation in a component, or the mapMutations method can be used, which maps methods in the component to a store.mit call (requiring store injection in the root component).
import { mapMutations } from 'vuex' export default { // ... Methods: {// Pass in an array... MapMutations ([' increments' // map this.increment() to this.code.store.mit (' increments ')]), mapMutations({ add: 'increment' // map this.add() to this.$store.commit('increment') }) } }Copy the code
Actions
Actions is submitted mutations, and it can have arbitrary asynchronous operations. The first argument in actions is context, which exposes the same set of methods/properties as the store instance, so you can call context.com MIT directly or access context.state or context.getters. We usually use es6 parameter deconstruction to simplify our code, writing {commit} directly.
actions: {
increment ({ commit }) {
commit('increment')
}
}Copy the code
- How do I trigger Actions?
The actions bystore.dispatch('actionName')
Mutation is triggered in the method body, but mutations can be triggered directly through store.mit, so why not directly use store.mit (‘ mutationName ‘)? Because actions can be performed asynchronously, mutations can only be performed synchronously. So this dispatch call can perform asynchronous operations within the action, which means asynchronous mutation can be performed.
- It can be triggered in the payload format or object format. The equivalent
// dispatch with a payload
store.dispatch('incrementAsync', {
amount: 10
})
// dispatch with an object
store.dispatch({
type: 'incrementAsync',
amount: 10
})Copy the code
- The actual application in the shopping Cart both invokes the asynchronous API and commits multiple mutations.
actions: { checkout ({ commit, state }, payload) { // save the items currently in the cart const savedCartItems = [...state.cart.added] // send out checkout request, And optimistically // clear the cart commit(types.checkout_request) // The asynchronous shop API accepts a success callback and a failure callback shop.buyProducts( products, // handle success () => commit(types.CHECKOUT_SUCCESS), // handle failure () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) } }Copy the code
- Distribute Actions in the component
You can usethis.$store.dispatch()
ormapActions
Map component methods tostore.dispatch
(root needs to be injected). withmapMutations
- Actions combination, how do you control Actions execution?
Since actions are asynchronous, it is difficult to know when an action is completed and how to combine multiple actions to handle complex asynchronous workflows.
Fortunately,store.dispatch()
This method returns the return value of the action handler we defined, so we can just return a Promise
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}Copy the code
You can use it this way
store.dispatch('actionA').then(() => {
// ...
})Copy the code
And then in the other action
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}Copy the code
Modules
Because Vuex uses a single-state tree, the Store grew as our app grew. To combat this, Vuex allows us to split the store into modules. Each module has its own state, mutations, actions, getters, and even nested modules. Such as:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: ModuleB}}) // Note that multiple modules are in the state object when called, A // -> moduleA's state Store.state. b // -> moduleB's stateCopy the code
- Modules state, local, or root?
- Mutations and getters, the first argument accepted is the local state of modules
const moduleA = {
state: { count: 0 },
mutations: {
increment: (state) {
// state is the local module state
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}Copy the code
- Similarly, in actions,
context.state
Is the local state, andcontext.rootState
For the root state
const moduleA = {
// ...
actions: {
incrementIfOdd ({ state, commit }) {
if (state.count % 2 === 1) {
commit('increment')
}
}
}
}Copy the code
- The third argument to getters is the root state
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}Copy the code
Strict Mode & Form Handling
In strict mode, if the Vuex state is changed outside of the mutation handler, the application will throw errors. For example, when we bind some data in Vuex to the input using Vue’s V-Model, once the input changes are sensed, we will try to modify the data directly. In strict mode, an error will be reported. So the recommendation is to bind the value and then call the action on input.
Copy the code
/ /... computed: { ... mapState({ message: state => state.obj.message }) }, methods: { updateMessage (e) { this.$store.commit('updateMessage', e.target.value) } }Copy the code
Mutation can be handled this way
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}Copy the code
Admittedly, this is pretty straightforward, but we can’t do it as well as the V-Model. The alternative is to continue using the V-Model, with bidirectional computed properties and setters.
computed: { message: {get () {return this.$store.state.obj. Message}, set (value) { Use new code.store.mit ('updateMessage', value)}}}Copy the code
It is recommended that strict mode be turned off when deploying to a development environment.