Pinia
pinia is now the official state library of Vue. It is applicable to vue2 and vue3. This paper only describes the writing method of vue3.
image-20220217155244154.png
Advantages of pinia
Compared with the previous vuex, pinia has the following advantages
-
Simpler writing method, clearer and concise code, and support the syntax of "composition api" and "options api"
-
More perfect TypeScript support. There is no need to create custom complex wrapper types to support TypeScript. All contents are typed, and the API design method uses TS type inference as much as possible
-
Very lightweight, only 1kb in size
-
There is no need to inject magic string for calling
install
yarn add pinia // or npm install pinia Copy code
Define and use store
Create a pinia and pass it to the vue application
import { createPinia } from 'pinia' import { createApp } from 'vue' import App from './app.vue' createApp(App).use(createPinia()).mount('#app') Copy code
Define store
store is defined through the function defineStore,
It requires a unique name that can be passed as the first parameter or as an id.
import { defineStore } from 'pinia' export const useMainStore = defineStore('main', { // other options... }) Copy code
import { defineStore } from 'pinia' export const useMainStore = defineStore({ id: 'main' // other options... }) Copy code
This id is necessary and is mainly used for vue devtools
Using store
import { useMainStore } from '@/stores/main' export default defineComponent({ setup() { const store = useMainStore() return { store, } }, }) Copy code
In the above code, after useMainStore is instantiated, we can access state, getters, actions, etc. on the store (there are no mutations in pinia).
The store is a reactive object, so ". value" is not required and cannot be deconstructed. Otherwise, it will lose responsiveness (similar to props).
storeToRefs
If you must deconstruct it, you can use storeToRefs, similar to # toRefs in vue3
import { storeToRefs } from 'pinia' export default defineComponent({ setup() { const store = useMainStore() const { user, company } = storeToRefs(store) return { user, company } }, }) Copy code
state
Define state
In pinia, the definition of state is to return the initial state of state in the function
import { defineStore } from 'pinia' const useMainStore = defineStore('main', { state: () => ({ teacherName: 'Allen', userList: [ { name: 'Xiao Ming', age: 18 }, { name: 'petty thief', age: 15 }, { name: 'Xiaobai', age: 16 }, ], }), }) export default useMainStore Copy code
Access state
It can be accessed directly through the store instance
import useMainStore from '@/store/main' export default defineComponent({ setup() { const mainStore = useMainStore() const teacherName = computed(() => mainStore.teacherName) const userList = computed(() => mainStore.userList) return { teacherName, userList, } }, }) Copy code
You can also modify the status directly
import useMainStore from '@/store/main' export default defineComponent({ setup() { const mainStore = useMainStore() function change() { mainStore.teacherName = 'Miley' mainStore.userList.push({ name: 'Xiao Qi', age: 19 }) } return { change } }, }) Copy code
Although it can be modified directly, for the sake of the code structure, the global state management should not modify the state directly at each component. It should be modified in a unified way in action (there is no mutation)
reset state
You can reset the state to the initial state by calling the method on the store
const mainStore = useMainStore() mainStore.$reset() Copy code
$patch
You can also modify the state by using the $patch method
$patch can modify multiple values at the same time, for example
import useMainStore from '@/store/main' export default defineComponent({ setup() { const mainStore = useMainStore() mainStore.$patch({ teacherName: 'Depp', userList: [ { name: 'Xiao Ming', age: 18 }, { name: 'petty thief', age: 15 }, ] }) return {} }, }) Copy code
However, when modifying the array, for example, I only want to change the age of the first item "Xiaoming" in the userList to 20, and I also need to pass in the entire array including all members, which undoubtedly increases the writing cost and risk. Therefore, the following writing method of passing in a function is generally recommended
mainStore.$patch((state)=>{ state.teacherName = 'Depp' state.userList[0].age = 20 }) Copy code
Subscribe to listening state
Via store$ Method of subscribe(),
The first parameter of the method accepts a callback function that can be triggered when the state changes
const subscribe = mainStore.$subscribe((mutation, state) => { console.log(mutation) console.log(state) }) Copy code
As shown above, the callback function has two parameters
Where state is the instance of mainStore, and mutation is printed as follows
Screenshot of enterprise wechat_ sixteen trillion and four hundred and fifty-one billion five hundred and fifty-six million four hundred and thirty-two thousand three hundred and sixty-nine png
It can be found that the mutation object of the print result mainly contains three attributes
-
events: the specific data of this state change, including the value before and after the change
-
storeId: the id of the current store
-
Type: type indicates how this change is generated. There are three main reasons
-
"direct": changes through action
-
”patch object ": the method of passing objects through $patch is changed
-
"patch function": it is changed by passing the function of $patch
-
Stop listening
In the above code, call mainStore.$. The value returned by subscribe (i.e. the subscribe variable in the above example) can stop the subscription
subscribe() Copy code
store.$ The second parameter options object of the subscribe () method is various configuration parameters, including
The value of the detached property is a Boolean value, which is false by default. Under normal circumstances, when the component where the subscription is located is unloaded, the subscription will be stopped from being deleted. If the detached value is set to true, the subscription can still take effect even if the component is unloaded.
Other attributes mainly include immediate, deep, flush, etc., which have the same effect as the corresponding parameters of vue3 watch.
getter
Define getter
getter is the calculated value of state in store, which is defined by getters attribute in defineStore
The value of the getters attribute is a function whose first parameter is state
const useMainStore = defineStore('main', { state: () => ({ user: { name: 'Xiao Ming', age: 7, }, }), getters: { userInfo: (state) => `${state.user.name}this year ${state.user.age}Years old`, //Here, if you want to correctly infer the type of the parameter {state}, you need to use the arrow function to define {state} }, }) Copy code
In the above code, the value of getters is an arrow function. When the value of getters is an ordinary function, you can access the entire store instance through this (as follows)
However, if it is an ordinary function, we need to declare the return type of the function if we want to obtain the value of state through this and hope that the type of this can be inferred correctly, and also hope that the return value type of the function can be inferred correctly.
getters: { userDesc: (state) => `${state.user.name}this year ${state.user.age}Years old`, userBesidesDesc(): string{ //Type to be indicated return `${this.user.age}Year old ${this.user.name}` //You can use , this , to get the value }, returnUserInfo() { return this.userDesc //You can also use this to get other getters }, }, Copy code
Access getter
import useMainStore from '@/store/main' export default defineComponent({ setup() { const mainStore = useMainStore() const userDesc = computed(() => mainStore.userDesc) const userBesidesDesc = computed(() => mainStore.userBesidesDesc) const returnUserInfo = computed(() => mainStore.returnUserInfo) return { userDesc, userBesidesDesc, returnUserInfo, } }, }) Copy code
action
Define action
action is a method in the store and supports synchronization or asynchrony.
The function defined by action can be an ordinary function, so you can access the entire store instance through this. At the same time, the function can pass in any parameter and return any data
const useMainStore = defineStore('main', { state: () => ({ count: 0, }), actions: { add() { this.count++ }, addCountNum(num: number) { this.count += num }, }, }) Copy code
Call action
setup() { const mainStore = useMainStore() function mainAction() { mainStore.addCount() } function addCountTwo() { mainStore.addCountNum(2) } return { mainAction, addCountTwo } }, Copy code
Listening subscription action
Via store$ Onaction(), which can monitor the action and result of action
This function can receive a callback function as a parameter. There are five properties in the parameters of the callback function, as follows:
const unsubscribe = mainStore.$onAction(({ name, //Name of action function store, //store instance, here is mainStore args, //action function parameter array after, //The hook function is executed after the action function returns or resolves onError, //The hook function is executed after the action function reports an error or rejects }) => {}) Copy code
for instance,
First, define a store
import { defineStore } from 'pinia' const useMainStore = defineStore('main', { state: () => ({ user: { name: 'Xiao Ming', age: 7, }, }), actions: { subscribeAction(name: string, age: number, manualError?: boolean) { return new Promise((resolve, reject) => { console.log('subscribeAction Function execution') if (manualError) { reject('Manual error reporting') } else { this.user.name = name this.user.age = age resolve(`${this.user.name}this year ${this.user.age}Years old`) } }) }, }, }) export default useMainStore Copy code
Then use it in setup
import useMainStore from '@/store/main' import { ref, defineComponent, computed } from 'vue' export default defineComponent({ setup() { const mainStore = useMainStore() function subscribeNormal() { mainStore.subscribeAction('petty thief', 18, false) } function subscribeError() { mainStore.subscribeAction('Xiaobai', 17, true) } const unsubscribe = mainStore.$onAction(({ name, //Name of action function store, //store instance, here is mainStore args, //action function parameter array after, //The hook function is executed after the action function returns or resolves onError, //The hook function is executed after the action function reports an error or rejects }) => { console.log('action Function name for', name) console.log('Parameter array', args) console.log('store example', store) after((result) => { console.log('$onAction after function', result) }) onError(error => { console.log('Error capture', error) }) }) return { subscribeNormal, subscribeError, } }, }) Copy code
As above, in setup, after calling the subscribeNormal function, the page is printed as follows
image-20220218105411157.png
After calling the subscribeError function, the page prints as follows
image-20220218105452580.png
Similarly, you can call mainstore$ The value returned by onAction is used to manually stop the subscription. In the example of the above code, it is
unsubscribe() //Manually stop subscription Copy code
store.$onAction will be automatically deleted by default when the component is unloaded. You can separate the action subscription from the component by passing the second parameter true (that is, the subscription is still valid when the component is unloaded)
mainStore.$onAction(callback, true) Copy code
store usage location
When used in components, useStore() can be used out of the box after calling in most cases.
When using in other places, make sure to use useStore() after pinia is activated (app.use(createPinia()))
For example, in routing guard
import { createRouter } from 'vue-router' import useMainStore from '@/store/main' const router = createRouter({ // ... }) //Error reporting const mainStore = useMainStore() router.beforeEach((to) => { //Normal use const mainStore = useMainStore() }) Copy code
You can also access other stores in the store
import { defineStore } from 'pinia' import { useUserStore } from './user' export const useMainStore = defineStore('main', { getters: { otherGetter(state) { const userStore = useUserStore() return userStore.data + state.data }, }, actions: { async fetchUserInfo() { const userStore = useUserStore() if (userStore.userInfo) { ... } }, }, }) Copy code
pinia plug-in
pinia store supports extensions. Through pinia plug-in, we can achieve the following
-
Add a new attribute to the store
-
Add new options to the store
-
Add a new method to the store
-
Packaging existing methods
-
Modify or even delete actions
...
For example, you can write a simple plug-in to add a static attribute to all store s
import { createPinia } from 'pinia' const pinia = createPinia() //Pass a return function pinia.use(() => ({ env: 'dev' })) app.use(pinia) Copy code
Then, the env attribute added above can be accessed in all other store s
setup() { const mainStore = useMainStore() console.log(mainStore.env) // dev } Copy code
Plug in function
As can be seen from the above code, pinia plug-in is a function with an optional parameter
import { PiniaPluginContext } from 'pinia' function myPiniaPlugin(context: PiniaPluginContext) { console.log(context) } Copy code
image-20220222165025712.png
The main contents of context printing are
-
app: current application Vue app created by createapp()
-
Options: definestore configured data
-
pinia: pinia instance currently created by createPinia()
-
Store: current store instance
Through context, we can set properties on the store
pinia.use(({ store }) => { store.env = 'dev' }) Copy code
In this way, the env attribute added above can be accessed in all other store s
pinia's store is wrapped through reactive, which can automatically unpack any ref objects it contains
pinia.use(({ store }) => { store.env = ref('dev') }) Copy code
Through the above plug-in, you do not need to access the env of the store value, you can access it directly
setup() { const mainStore = useMainStore() console.log(mainStore.env) //There is no need to add value } Copy code
Add external properties
When you need to add data from other libraries or do not need a response, you should wrap the passed object with markRaw(), such as
markRaw comes from vue3 and can mark an object so that it will never be converted to proxy. Returns the object itself.
import { markRaw } from 'vue' import { router } from './router' import { axios } from 'axios' pinia.use(({ store }) => { store.router = markRaw(router) store.axios = markRaw(axios) }) Copy code
Use and onAction inside the plug-in
pinia.use(({ store }) => { store.$subscribe(() => { // react to store changes }) store.$onAction(() => { // react to store actions }) }) Copy code
typescript support for new properties
When adding new properties through the plug-in, you can extend the} PiniaCustomProperties interface
You can safely write and read new properties by setting get, set, or simply declaring the type of value
import 'pinia' declare module 'pinia' { export interface PiniaCustomProperties { set env(value: string | Ref<string>) get env(): string //Or env: string } } Copy code