Ask yourself more why every day. You always have unimaginable gains. A rookie Xiaobai's growth path (copyer)
preface:
This blog mainly introduces the basic use of vuex4, from creating a store object, Then, the internal attributes of the store are explained one by one (that is, the five major and core of vuex: state, getters, variations, actions and modules). The explanation is mainly in terms of types, parameters and so on. The content is a little long and can be collected and read slowly (hee hee)
Introduction to vuex
Vuex is designed for Vue JS application development state management mode + library. It uses centralized storage to manage the state of all components of the application, and uses corresponding rules to ensure that the state changes in a predictable way.
I won't describe it specifically. I understand everything. I can't tell what I don't understand (hahaha)
vuex4 installation
npm install vuex@next //Now the default is vuex3, so add next to install the next version //package.json file, view the version "dependencies": { "vuex": "^4.0.2" },
vuex creates a store object
A method, createStore, is provided in vuex4 to create a store object
If you don't want to see the of generic analysis, you can skip the reference section directly
Analyze the type of createStore
// vuex/types/index.d.ts export function createStore<S>(options: StoreOptions<S>): Store<S>
analysis:
-
createStore receives a generic with the name S
-
Receive a parameter, the parameter is an object, and the type of the object is StoreOptions
-
Return a Store instance
Analysis parameters
export interface StoreOptions<S> { state?: S | (() => S); //Object or function to return the object getters?: GetterTree<S, S>; actions?: ActionTree<S, S>; mutations?: MutationTree<S>; modules?: ModuleTree<S>; plugins?: Plugin<S>[]; strict?: boolean; devtools?: boolean; }
According to the above interface, we can see that generic S is to limit the type of state
Analysis return value
Store is a generic class
// vuex/dist/vuex.global.js function createStore (options) { return new Store(options) }
What is returned is an instance object of the Store class
Create a store instance object, and its type is defined as IRootState
import { createStore } from 'vuex' interface IRootState { count: number } const store = createStore<IRootState>({ //Define the type qualification of state }) export default store
Register in the project's APP
import { createApp } from 'vue' import store from '@/store' const app = createApp(App) app.use(store) app.mount('#app')
In this way, the store can be used directly in each component.
state of vuex core module
Where data is stored (data warehouse), state can be a function or an object
const store = createStore<IRootState>({ //Define the type qualification of state state() { //When creating, the type of state has been limited return { count: 0 } } })
Above, we have defined a state data, so we can show it in the component. There are two types of classification:
The first form:
//Use $store directly in the component <h1>{{$store.state.count}}</h1>
Note: the above situation is a little special. If we manually install vuex, ts will give an alarm if it does not know $store, but it will not affect the operation process of the project. But for most people, they certainly can't accept the warning of the wave line. The scenario is as follows:
How to solve this problem and make ts know $store?
In the project created by vue3, there is a file, ships Vue d.ts
//use. d.ts to declare $store, you can solve it declare let $store: any
The second form:
Borrow the calculated API provided in vue3, and then show it (the above warning file will not exist here)
You also need to use a function useStore, which is provided internally by vuex. Because this cannot be used in setup, you can't use this in vue2$ Store form.
import { defineComponent, computed} from 'vue' import { useStore } from 'vuex' export default defineComponent({ name: 'App', setup() { const store = useStore() const count = computed(() => store.state.count) //Get the value of count return { count } } }) //Display in template <h1>{{count}}</h1>
The effect of the above two forms is the same. (whichever you choose is up to you)
- First, the field in the template is too long and the code is not elegant
- The second is to increase the amount of code with the help of two functions
getters of vuex core module
getters is used to deform the state and then display it back
The count example here is not easy to demonstrate, so I listed other data for demonstration (better reflected)
interface IRootState { coder: any[] } const store = createStore<IRootState>({ state() { return { coder: [ {name: 'Front end personnel', number: 5}, {name: 'Back end personnel', number: 15}, {name: 'Tester', number: 3}, {name: 'product', number: 2} ] } }, getters: { coderAllCount(state) { return state.coder.reduce((prev, item) => { return prev + item.number }, 0) } } }) export default store
Code analysis: the state data here is coder, which is a data. If the total number of people is displayed in the interface, Getters is a good choice (in fact, another case is that the component directly gets the value in state for calculation and display. Imagine that if the total number of people is required in many places, it needs to be written many times). Write it directly in getters, so that it can be used in many places without writing the same code repeatedly.
Displayed in the component, the two forms are the same as state
//Form I <h1>{{$store.getters.coderAllCount}}</h1> //Form II const coderAllCount = computed(() => store.getters.coderAllCount)
Parameter analysis of getters
In the above example, you can see that one of the parameters is state, which is the state in the store. analysis:
ts type analysis of getters. If you don't want to see it, you can skip it directly
It can be found from the above that createStore receives a parameter, which is an object. The type of the object is StoreOptions. One of the properties is getters, which is defined as follows
getters?: GetterTree<S, S>; //generic interface //GetterTree interface GetterTree<S, R> { [key: string]: Getter<S, R>; //Generic interface (know method name as string) } //Getter type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;
The existence of modules is considered here
During the above analysis, we know that the four receiving parameters are: state, getters, rootState and rootGetters,
Of course, the existence of modules is considered here, so this will be discussed when explaining the module below.
Here, we are the same as Store, so we also explain why gettertree < s, s > passes two same generics, and the state and rootState here are congruent, and getters and rootGetters are congruent
Personal opinion: Although the attribute in getters is a function, please regard it as an attribute. (because () is not required when using)
mutations of vuex core module
1. What do mutations do?
Changes is the only way to change the state of a store in vuex.
2. How to trigger the method in mutations?
The commit attribute in the instance object created by createStore is a method, and the received parameters can be in the form of string and object (payload: submit payload)
3. Can functions in mutations be asynchronous?
No, the synchronization code must be used in the mutation (an important principle). For example, during the log of the mutation, devtools needs to capture the snapshot of the previous state and the next state. However, during the asynchronous function, devtools does not know what to execute, so it can not capture, so the state is an untraceable state
Parameter analysis:
ts parameter type analysis of changes. If you don't want to see it, you can skip it directly
It can be found from the above that createStore receives a parameter, which is an object. The type of the object is StoreOptions. One of the attributes is mutations, which is defined as follows:
mutations?: MutationTree<S>; //generic interface export interface MutationTree<S> { [key: string]: Mutation<S>; //Generic interface (know method name as string) } //Mutation export type Mutation<S> = (state: S, payload?: any) => any;
From the above type analysis, we can see that the method in mutation receives two parameters: state and payload (optional)
mutation can only modify the state in the current module
Code example
// store/index.ts interface IRootState { count: number } const store = createStore<IRootState>({ state() { return { count: 0 } }, mutations: { increment(state) { //The type of state here can be automatically derived as IRootState state.count += 1 } } })
Trigger in component (e.g. click event)
//In component import { useStore } from 'vuex' export default defineComponent({ name: 'App', setup() { const store = useStore() const btn = () => { store.commit('increment') // increment corresponds to the method name in mutations } return { btn } } })
In the above, the words with the only error prone feeling are easy to write wrong, and then produce bug s. If you want to solve the above problem, use:
Constant instead of Mutation event
//mutation-types.js export const INCREMENT = 'INCREMENT' // plus
// store/index.ts import { INCREMENT } from './mutation-types' mutations: { [INCREMENT] (state) { state += 1 } }
//In component import { INCREMENT } from '@/store/mutation-types' store.commit(INCREMENT) //Use exported constants
In this way, errors can be reduced, even though the words are written incorrectly. Moreover, this has the advantage that you can analyze at a glance what changes have been executed without constantly folding and looking for code in the changes.
Submit load (payload)
There are mainly two forms: string and object
Recommended: object form
Code demonstration:
const store = createStore<IRootState>({ state() { return { count: 0 } }, mutations: { increment(state, payload) { //The type of state here can be automatically derived as IRootState state.count += payload.count } } })
//String form const btn = () => { store.commit('increment', {count: 10}) //Or store.commit(INCREMENT, {count: 10}) }
//Object form const btn = () => { store.commit({ type: 'increment', count: 10 }) //Or store.commit({ type: INCREMENT, count: 10 }) } //Here, the form of the object: payload refers to the entire object passed
actions of vuex core module
1. Can action directly modify the state?
No, you can only modify the value of state in the store, so the action submits the change, and let the change modify it
2. How to trigger the method in actions?
The dispatch attribute in the instance object created by createStore is a method, and the received parameters can be in the form of string and object (payload: submit payload)
3. Can asynchronous functions be written in action?
Yes, action is specifically used to handle asynchrony. After the asynchronous execution is completed, the mutation is triggered to modify the state and make the state traceable.
parameter analysis
ts analyzes the parameter types of action. If you don't want to see it, you can skip it directly (it's recommended to see it because it's complex)
It can be found from the above that createStore receives a parameter, which is an object. The type of the object is StoreOptions. One of the attributes is actions. The type definition is as follows:
actions?: ActionTree<S, S>; export interface ActionTree<S, R> { [key: string]: Action<S, R>; } export type Action<S, R> = ActionHandler<S, R> | ActionObject<S, R>; //Union type ActionHandler | ActionObject export type ActionHandler<S, R> = (this: Store<R>, injectee: ActionContext<S, R>, payload?: any) => any; //The first parameter is this (no need to pass) //The second parameter injects a context as an object //The third parameter is payload export interface ActionObject<S, R> { //For the action in the module, trigger the store in the root node root?: boolean; handler: ActionHandler<S, R>; } //Type of registered context export interface ActionContext<S, R> { dispatch: Dispatch; commit: Commit; state: S; getters: any; rootState: R; rootGetters: any; }
From the above types, we can roughly analyze:
The method in actions generally receives two parameters, and the second parameter is optional
Parameter 1: context (required parameter)
context is an object that contains the following attributes: commit, dispatch, state, rootstate, getters, and rootgetters
- commit: trigger the method in changes and modify the state
- dispatch: trigger method in actions
- State: get the state value in the current module
- Getters: get the values in getters in the current module
- rootState: get the state value in the root node
- rootGetters: get the value in getters in the root node
//Two forms of parameter 1 actions: { //Writing method 1: write the context object directly increment(context) {}, //Writing method 2: deconstruct the object and list only the attribute names we need decrement({commit, dispatch}) }
Parameter 2: payload (optional parameter)
The existence of payload lies in whether we distribute to action and whether we carry parameters.
// store/index.ts const store = createStore<IRootState>({ state() { return { count: 0 } }, mutations: { increment(state, payload) { //The type of state here can be automatically derived as IRootState state.count += payload.count } }, actions: { increment({ commit }, payload) { setTimeout(() => { commit({ type: 'increment', count: payload.count }) }, 1000) } } })
Case 1: no parameters are carried
const store = useStore() cosnt btn = () => { store.dispatch('increment') // No parameters are carried here, so payload does not exist }
Case 2: carry parameters
const store = useStore() //There are two forms: the form of directly passing parameters and the form of objects cosnt btn = () => { store.dispatch('increment', {count: 100}) // Here, parameters are carried, so payload is an object } //payload ==> {count: 100} cosnt btn = () => { store.dispatch({ type: 'increment', count: 100 }) } //Payload = = > {type: 'increment', count: 100} objects have different forms
Module of vuex core module
1. Why use module?
Due to the use of a single state tree, all the states of the application will be concentrated in a relatively large object. When the application becomes very complex, the store object may become quite bloated. Therefore, it is necessary to divide the modules, so the idea is relatively clear, which is also conducive to the division of labor and development of the team.
Type analysis:
ts analyzes module types. If you don't want to see them, you can skip them directly
Define a module:
const countModule = {}
So what is the type of countModule?
//This is vuex4 the type definition of the module export interface Module<S, R> { namespaced?: boolean; state?: S | (() => S); getters?: GetterTree<S, R>; actions?: ActionTree<S, R>; mutations?: MutationTree<S>; modules?: ModuleTree<R>; }
As can be seen from the above, it is the same as the basic instance created by createStore, except that there is an additional attribute named (which will be mentioned later)
Therefore, the type of defining a module is determined
import { Module } from 'vuex' const countModule: Module<S, R> = {}
S: Is the type qualification of state in the module
R: Is the type qualification of the state in the root store
Basic usage: (counting module as an example)
Preparation: interface definition
interface ICountState { //state object qualification in count module count: number } interface IRootState { //Type qualification of state in root store count: number }
Step 1: define a countModule module
import { Module } from 'vuex' //type: Module<S, R> //Receive two generic types. The first S is the state type of the current module, and the second R is the state type of the node const countModule: Module<ICountState, IRootState> = { state() { return { count: 0 } }, getters: {}, mutations: { increment(state) { console.log('modular store Medium increment method') state.count += 1 } }, actions: {} } export default countModule
Step 2: register the counting module in the root store
import { createStore } from 'vuex' import countModule from './count/count' const store = createStore<IRootState>({ state() { return { count: 0 } }, mutations: { increment(state) { console.log('root store Medium increment method') state.count += 1 } } modules: { countModule } })
Code analysis:
A counting module is defined, in which there is a count attribute in state and an increment method in mutations; The state in the root store also has a count attribute, and the changes has an increment method; (only changes are tested here, because getters and actions are the same as changes.)
Start with the topic:
1. Distinction between state in root store and state in module
Question: how to render the count in the root store and the count in the count module?
<div>{{$store.state.rootCount}}</div> //Render the root store (there should be no doubt about this) <div>{{$store.state.countModule.count}}</div> //Render the count in the count module correctly (any questions?) //Why not write it in the following way? <div>{{$store.countModule.state.count}}</div> //It consumes more performance. The internal data structure of the source code is not designed in this way
Answer: merge in state
Data structure of merge operation in vuex:
//It is as follows: const store = createStore({ state() { return { rootCount: 0, countModule: { //The state of the module is directly added here count: 1 } } } })
2. The difference between the mutation in the root store and that in the module
Question: doubts in mutations
- How to change the changes in the root store?
- How do I use mutations in a module?
- If the method in the root store is consistent with the method in the changes in the module, how is it handled
Test case:
import { useStore } from 'vuex' export default defineComponent({ setup() { const store = useStore() const btn = () => { store.commit('increment') } } })
We use the commit method in the store to trigger the increment in the changes. However, since there is the increment method in the internal changes in the root store and the increments method in the changes in the module, what will happen when clicking?
Phenomena in initial state:
Effect after clicking the button
Through the above tests, we know that the increment with the same name in the commit will be triggered, whether in the module or in the root store.
So how do you merge the mutation in the root store and the mutation in the module?
//Will be concentrated in the changes in the root store const store = createStore({ mutations: { increment(){}, //Root store increment(){}, //In module } })
For example, like the example code above (we can imagine this understanding).
getters and actions have the same effect.
You want to modify the increment method in the trigger module instead of triggering the increment in the root store; Or, if we want to trigger the increment method in the root store instead of the increment method in the module, what should we do?
In vuex4, to handle similar effects above, you need to use namespaces
Use of namespaces
When analyzing the type of module created, there is an attribute named, which is a boolean value to remind us whether we need to open the namespace.
- By default, the action changes in the module are still registered in the global namespace:
- This enables multiple modules to respond to the same action or mutation
- getters are also registered in the global namespace by default
- If we want the module to have high encapsulation and reusability, we can add named: true to make it a module to be namespace:
- After the module is registered, all its getters, actions and mutation will be automatically named according to the path of module registration.
Access to each attribute method in the module:
//state (of course, it was accessed in this way) store.state.countModule.count //getters store.getters['countModule/coderAllCount'] //mutations store.commit('countModule/increment') //actions store.dispatch('countModule/incrementAction')
The module triggers the mutation in the root store
Is it possible to modify the mutation in the root store after the action in the module gets the data asynchronously? This should be the case. So how to deal with this situation?
When analyzing the type of action above, one type is specific to the module
export interface ActionObject<S, R> { //For the action in the module, trigger the store in the root node root?: boolean; handler: ActionHandler<S, R>; }
There is a root attribute here, which is used to tell whether to modify the mutation in the root store
actions: { incrementAction({commit}, payload) { setTimeout(() => { //By default, the increment in the module's internal mutation is triggered commit('increment', payload) //Add root: true, then the increment in the root store will be triggered commit('increment', payload, {root: true}) }, 1000) } }
Summary:
The basic usage of vuex is roughly like this. It is not very different from vuex3. After learning vue2 before, you can easily accept it.
Like it, like it!