Simple use of Vuex's alternative tool Pinia
Introduction and advantages of Pinia
Pinia is the replacement of Vuex in Vue ecology, a new Vue state management library. After Vue3 became the official version, Pinia was the project strongly recommended by you Yuxi. Let's take a look at the five advantages of Pinia over Vuex.
- Vue2 and Vue3 can be well supported, that is, Pinia can also be used for old projects.
- Discarding the operations of movements, only state, getters and actions It greatly simplifies the use of state management library and makes code writing easier and intuitive.
- There is no need for nested modules, which conforms to Vue3's Composition api, making the code more flat.
- Full TypeScript support. One of the major advantages of Vue3 is the support for TypeScript, so Pinia has also achieved complete support. If you make complaints about Vuex, you must know that Vuex's support for TS is not complete.
- The code is more concise and can realize good automatic code segmentation. In the era of Vue2, writing code requires scrolling the screen back and forth to find variables, which is very troublesome. Vue3's Composition api perfectly solves this problem. Automatic code segmentation can be realized, and pinia also inherits this advantage.
If you say these five points are a little too much, you can't remember them. It can be summarized that Pinia has the advantages of more concise syntax, perfect support for Vue3's Composition api and perfect support for TypesCcript. With these advantages and the strong recommendation of you Yuxi, I think Pinia will completely replace Vuex and become the most suitable state management library for Vue3 soon.
Here's a point. In fact, pinia's development team is Vuex's development team.
Pinia installation
After installing the development environment of Vue3, you can install the Pinia state management library. The installation method can be npm or yarn.
npm install pinia # or with yarn yarn add pinia
After the installation of. Package into the project, you can Check the version of pinia in the JSON file.
{ "name": "pinia-demo", "version": "0.0.0", "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview" }, "dependencies": { "pinia": "^2.0.11", "vue": "^3.2.25" }, "devDependencies": { "@vitejs/plugin-vue": "^2.0.0", "typescript": "^4.4.4", "vite": "^2.7.2", "vue-tsc": "^0.29.8" } }
You can see that the version of pinia I installed is 2.0.11. In order to prevent different versions and different use methods, you can use the following method to install the fixed version.
npm install pinia@2.0.11
Create a Store in Pinia's way
In main Introducing pinia into TS
After installing Pinia, the first thing you need to do is in / SRC / main Pinia is introduced into ts. Here, we use import directly.
import { createPinia } from 'pinia'
After the introduction, the instance of pinia is obtained and mounted on the Vue root instance through the createPinia() method. In order to facilitate your study, main is directly given here All codes of TS.
import { createApp } from 'vue' import App from './App.vue' import { createPinia } from 'pinia' // Create pinia instance const pinia = createPinia() const app =createApp(App) // Mount to Vue root instance app.use(pinia) app.mount('#app')
In this way, we introduced Pinia into the project, that is, we can use it for programming in the project.
Create a store instance
After introducing Pinia, you can create a state management library, which is often called store. Directly create a new store folder in the / src directory. Once you have a folder, create an index TS file.
The code in this file, we generally do only three things.
- Define status container (warehouse)
- Modify the state in the container (warehouse)
- Use of action s in the warehouse
After making these three things clear, let's write the code. First, define the container. This writing method is fixed. You can even define a code fragment in VSCode. When you use it later, you can directly generate such code.
Because it's study here, I'll write it from scratch.
import { defineStore} from 'pinia' export const mainStore = defineStore('main',{ state:()=>{ return {} }, getters:{}, actions:{} })
After writing this code, you will feel like a Vue widget, which is also an advantage of Pinia.
- The first parameter of the defineStore() method: equivalent to giving a name to the container. Note: the name here must be unique and cannot be repeated. This is a matter specially stated by the official.
- The second parameter of the defineStore() method can be simply understood as a configuration object, which contains the configuration description of the container warehouse. Of course, this description is in the form of objects.
- State attribute: used to store the global state. If it is defined here, it can be the global state in SPA.
- getters attribute: it is used to monitor or calculate the change of state. It has the function of caching.
- actions attribute: for the business logic of data changes in state, the requirements are different, and the writing logic is different. To put it bluntly, it is to modify the state global status data.
If you know Vuex, the above content may not be difficult for you. But if you don't know Vuex, now just know the general meaning of this code. Don't delve into it. With the deepening of our study, you will have a more specific understanding.
We define a State in the Store, and here we write Hello World!.
state:()=>{ return { helloWorld:'Hello World' } },
At this time, the helloWorld is the global state data, which can be read by every page and component through the Pinia method.
Using global variables in pinia in Vue3 components
In \ src\components, create a new one vue components. Write the following code:
<template> <h2 class="">{{ store.helloWorld }}</h2> </template> <script lang="ts" setup> import { mainStore } from "../store/index"; const store = mainStore(); </script> <style lang="scss" scoped></style>
First introduce the mainStore, then get the store instance through the mainStore, and then you can call the state data defined by the state in the store in the component.
After writing this component, go to app Vue, you can use it. Of course, here we delete some useless code generated automatically.
<script setup lang="ts"> import home from "./components/home.vue"; </script> <template> <home></home> </template> <style> </style>
After writing these, open the terminal in VSCode, then enter yarn dev or npm run dev to run the Vue service, and enter in the browser[ http://localhost:3000/ ]View the results.
Additional details:
The created store is a reactive object, so ". value" is not required and cannot be deconstructed. Otherwise, it will lose responsiveness (similar to props).
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 } }, })
Pinia four ways to change status data
1. Access state and modify it directly
You can modify the status directly through the store instance
import useMainStore from '@/store/main' export default defineComponent({ setup() { const mainStore = useMainStore() function change() { mainStore.teacherName = 'coder' mainStore.userList.push({ name: 'kobe', age: 30 }) } return { change } }, })
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)
2.$patch - the parameter is an object
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: 'Ula', userList: [ { name: 'Xiao Ming', age: 18 }, { name: 'petty thief', age: 15 }, ] }) return {} }, })
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
3.$patch - parameter is a function
mainStore.$patch((state)=>{ state.teacherName = 'Ula' state.userList[0].age = 20 })
4. Modify through actions
If you have a very complex modification process, you can first define the function in actions in the store, and then call the function in the component. 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
Let's go to \ SRC \ store \ index TS file, write a changeState() method in the place of actions to change the data state. The code is as follows:
actions:{ changeState(){ this.count++ this.helloWorld='How do you do,Li Yinhe' } }
With this changeState() function, you can call this function in the component to modify the state data. Go to \ SRC \ components \ countbutton Vue file. Write a new method, handleClickActions(). Then you can call the changeState() method with store.
const handleClickActions = ()=>{ store.changeState() }
Then add a button and call this method.
<div> <button @click="handleClickActions">Modify data( actions)</button> </div>
Note: this function cannot be used when the arrow is bound to the action function. This little partner needs to pay attention.
Summary: there are four ways to change state data in Pinia. These ways have their own advantages and disadvantages. It's good to choose according to the actual development situation.
Listen to subscription 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 = store.$subscribe((mutation, state) => { console.log(mutation) console.log(state) })
As shown above, there are two parameters of the callback function, where state is the store instance and mutation is printed as follows
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 store.$. The value returned by subscribe (i.e. the subscribe variable in the above example) can stop the subscription
subscribe()
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.
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 = store.$onAction(({ name, // The name of the action function store, // Store instance, here is store 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 }) => {})
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
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, // The name of the 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, } }, })
As above, in setup, after calling the subscribeNormal function, the page is printed as follows
After calling the subscribeError function, the page prints as follows
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
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)
Getters in Pinia
The Getter in Pinia is almost the same as the calculation attribute in Vue, which is to do some processing when obtaining the value of state. For example, we have a requirement that there is a status data in the state, which is the phone number. When we want to output, the middle four digits are displayed as * * * * At this time, getters is a very good choice.
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 the state }, })
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 }, }
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, } }, })
Mutual call of Store in Pinia
We always use only one Store warehouse. In real projects, we often have multiple stores. When there are multiple stroes, the problem of calling each other inside the Store will be involved.
Create a new person under \ src\store TS file. Then enter the following code.
import { defineStore} from 'pinia' export const personStore = defineStore('person',{ state:()=>{ return { list:['Xiao Hong','Xiaomei','Fat ya'] } } })
This is a very simple warehouse. There is only state data. It should be noted that the ID should be unique. With this warehouse, you can go back to index TS is called in this repository.
First introduce the personalstore
import {personStore} from './person'
Then add a getList() method to the actions section. This part is very simple, just use console Log() can be printed on the console.
actions:{ changeState(){ this.count++ this.helloWorld='How do you do,Li Yinhe' }, getList(){ console.log(personStore().list) } }
This enables the two store s to call each other. To see the effect, we still go to \ SRC \ components \ countbutton In the Vue file, write a new method called getList().
const getList = () => { store.getList(); };
With the getList() method, write a button to trigger in the template section.
<div> <button @click="getList">display List</button> </div>
Go to the browser to view the effect. Press F12 to open the console. After clicking the button, you can see that the cross Store status data call has been successful.
Detail supplement
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({ // ... }) // report errors const mainStore = useMainStore() router.beforeEach((to) => { // Normal use const mainStore = useMainStore() })
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)
Then, the env attribute added above can be accessed in all other store s
setup() { const mainStore = useMainStore() console.log(mainStore.env) // dev }
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) }
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' })
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') })
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 }
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) })
Use and onAction inside the plug-in
pinia.use(({ store }) => { store.$subscribe(() => { // react to store changes }) store.$onAction(() => { // react to store actions }) })
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 // perhaps env: string } }