Pinia
Pinia is now vUE’s official status library. It applies to vue2 and vue3. This article only describes the writing method of VUe3.
The advantage of pinia
Pinia has the following advantages over the previous Vuex
- More simple writing, code more clear and concise, support
composition api
和options api
grammar - Better typescript support, no need to create custom complex wrapper types to support typescript, everything is typed, and the API is designed to take advantage of TS type inference as much as possible
- Very lightweight, only 1KB in size
- No need to inject magic strings, etc
The installation
yarn add pinia
// or
npm install pinia
Copy the 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 the code
Define the store
A store is defined by defineStore,
It requires a unique name, which can be passed as the first argument or familiar with id.
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
// other options...
})
Copy the code
import { defineStore } from 'pinia'
export const useMainStore = defineStore({
id: 'main'
// other options...
})
Copy the code
This ID is required, primarily for vue DevTools
The use of the store
import { useMainStore } from '@/stores/main'
export default defineComponent({
setup() {
const store = useMainStore()
return {
store,
}
},
})
Copy the code
In the code above, once useMainStore is instantiated, we can access state, getters, actions, etc. (not mutations in Pinia) on the store.
The store is a Reactive object, so it doesn’t need a “.value “and can’t be used to deconstruct it because it’s not responsive (similar to props).
storeToRefs
If you must destruct 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 the code
state
Define the state
In Pinia, state is defined by returning the initial state of state in a function
import { defineStore } from 'pinia'
const useMainStore = defineStore('main', {
state: () = > ({
teacherName: 'Alan'.userList: [{name: 'Ming'.age: 18 },
{ name: 'xiao li'.age: 15 },
{ name: 'white'.age: 16},]})})export default useMainStore
Copy the code
Access to the 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 the code
You can also change the status directly
import useMainStore from '@/store/main'
export default defineComponent({
setup() {
const mainStore = useMainStore()
function change() {
mainStore.teacherName = '米利'
mainStore.userList.push({
name: 'little girl'.age: 19})}return {
change
}
},
})
Copy the code
The global state management should not be used to modify the state of each component directly, but should be used to modify the state in the action method.
Reset the state
The state can be reset to its initial state by calling a method on the Store
const mainStore = useMainStore()
mainStore.$reset()
Copy the code
$patch
State can also be changed using the $patch method
$patch can change multiple values at the same time, for example
import useMainStore from '@/store/main'
export default defineComponent({
setup() {
const mainStore = useMainStore()
mainStore.$patch({
teacherName: '德普'.userList: [{name: 'Ming'.age: 18 },
{ name: 'xiao li'.age: 15]})},return{}}})Copy the code
However, when I modify the array in this way, for example, I just want to change the age of the first item of the userList “Xiaoming” to 20, I also need to pass in the entire array including all members, which increases the writing cost and risk. Therefore, it is generally recommended to use the following method of passing in a function
mainStore.$patch((state) = >{
state.teacherName = '德普'
state.userList[0].age = 20
})
Copy the code
Listen to subscription state
With the store.$subscribe() method,
The first argument to the method takes a callback function that can be fired when state changes
const subscribe = mainStore.$subscribe((mutation, state) = > {
console.log(mutation)
console.log(state)
})
Copy the code
Two arguments to the callback function, as shown above
Where state is the mainStore instance and mutation is printed as follows
You can see that the mutation object for the printed result mainly contains three properties
- Events: is the specific data of the state change this time, including the values before and after the change and so on
- StoreId: indicates the ID of the current store
- Type: Type indicates what the change is caused by. There are three main ones
- Direct: Changes through action
- “Patch object” : changed by passing the object through $patch
- “Patch function” : changed by way of $patch transfer function
Stop listening
In the above code, the value returned by a call to mainStore.$SUBSCRIBE (the SUBSCRIBE variable in the above example) stops the subscription
subscribe()
Copy the code
The options object, the second argument to the store.$subscribe() method, is a variety of configuration arguments, including
Detached: The detached property, whose value is a Boolean. The default is false. Normally, the subscription will be stopped and deleted when the component in which the subscription is detached is detached.
Other attributes include immediate, deep, flush, etc., which have the same effect as vue3 Watch.
getter
Define getter
Getters are state computed values in the store, defined by the getters property in defineStore
The value of the getters property is a function whose first argument is state
const useMainStore = defineStore('main', {
state: () = > ({
user: {
name: 'Ming'.age: 7,}}),getters: {
userInfo: (state) = > `${state.user.name}This year,${state.user.age}At the age of `.// To correctly infer the type of the argument state, define state using the arrow function definition}})Copy the code
In the above code, the value of getters is an arrow function. When the value of getters is a normal function, the entire store instance can be accessed through this (as shown below).
But if it’s a normal function, and we want this to get the value of state and we want the type of this to be inferred correctly, and we want the return type of the function to be inferred correctly, we need to declare the return type of the function.
getters: {
userDesc: (state) = > `${state.user.name}This year,${state.user.age}At the age of `,
userBesidesDesc(): string{ // Type required
return `The ${this.user.age}At the age ofThe ${this.user.name}` // We can use this to get the value
},
returnUserInfo() {
return this.userDesc // You can also use this to get other getters}},Copy the code
Visit the 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 the code
action
Define the action
Actions are methods in the Store that can be synchronous or asynchronous.
The function defined by action can be a normal function that accesses the entire store instance through this, and it can pass in any arguments and return any data
const useMainStore = defineStore('main', {
state: () = > ({
count: 0,}).actions: {
add() {
this.count++
},
addCountNum(num: number) {
this.count += num
},
},
})
Copy the code
Call to action
setup() {
const mainStore = useMainStore()
function mainAction() {
mainStore.addCount()
}
function addCountTwo() {
mainStore.addCountNum(2)}return {
mainAction,
addCountTwo
}
},
Copy the code
Listen to subscribe action
With store.$onAction(), you can listen for action, result, etc
This function can take a callback function as an argument that has five attributes, as shown below
const unsubscribe = mainStore.$onAction(({
name, //Name of the action function store,//Store instance, this is mainStore args,//Action function parameter array after,//Hook function, onError is invoked after action function execution completes or?//A hook function that executes after the action function returns an error or rejects}) = > {})
Copy the code
So for example,
First, define a store
import { defineStore } from 'pinia'
const useMainStore = defineStore('main', {
state: () = > ({
user: {
name: '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')}else {
this.user.name = name
this.user.age = age
resolve(`The ${this.user.name}This year,The ${this.user.age}At the age of `)}})},},})export default useMainStore
Copy the 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('xiao li'.18.false)}function subscribeError() {
mainStore.subscribeAction('white'.17.true)}const unsubscribe = mainStore.$onAction(({
name, //Name of the action function store,//Store instance, this is mainStore args,//Action function parameter array after,//Hook function, onError is invoked after action function execution completes or?//A hook function that executes after the action function returns an error or rejects}) = > {
console.log('Action function name', name)
console.log('Parameter array', args)
console.log('store instance', store)
after((result) = > {
console.log('$onAction after ', result)
})
onError(error= > {
console.log('Error capture', error)
})
})
return {
subscribeNormal,
subscribeError,
}
},
})
Copy the code
As shown above, in setup, after calling the subscribeNormal function, the page is printed as follows
After calling the subscribeError function, the page is printed as follows
Again, you can manually stop the subscription by calling the value returned by mainStore.$onAction, which in the example of the code above is
unsubscribe() // Stop the subscription manually
Copy the code
Store.$onAction is automatically deleted when the component is uninstalled by default. You can separate the action subscription from the component by passing the second parameter true.
mainStore.$onAction(callback, true)
Copy the code
Store Location
When used in components, useStore() can be called out of the box in most cases.
For use elsewhere, be sure to use useStore() only after Pinia has been activated (app.use(createPinia()))
For example, in route guard
import { createRouter } from 'vue-router'
import useMainStore from '@/store/main'
const router = createRouter({
// ...
})
/ / an error
const mainStore = useMainStore()
router.beforeEach((to) = > {
// Normal use
const mainStore = useMainStore()
})
Copy the code
You can also access other stores within 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 the code
Pinia plug-in
The Pinia Store supports extensions, with the Pinia plugin we can implement the following
-
Add new properties to store
-
Add new options to store
-
Add a new method to store
-
Packaging existing methods
-
Modify or even delete actions
.
For example, you could write a simple plug-in to add a static attribute to all stores
import { createPinia } from 'pinia'
const pinia = createPinia()
// Pass a return function
pinia.use(() = > ({ env: 'dev' }))
app.use(pinia)
Copy the code
The env attribute added above is then accessible in all other stores
setup() {
const mainStore = useMainStore()
console.log(mainStore.env) // dev
}
Copy the code
Plug-in function
As you can see from the code above, the Pinia plug-in is a function that takes an optional argument
import { PiniaPluginContext } from 'pinia'
function myPiniaPlugin(context: PiniaPluginContext) {
console.log(context)
}
Copy the code
The main thing that the context prints out is
- App: Current app vue.createApp () created app
- Options: defineStore configuration data
- Pinia: Pinia instance currently created through createPinia()
- Store: current store instance
Using context we can set properties on store
pinia.use(({ store }) = > {
store.env = 'dev'
})
Copy the code
In this way, all other stores can access the env attribute added above
Pinia’s Store is wrapped in Reactive and can automatically unpack any ref objects it contains
pinia.use(({ store }) = > {
store.env = ref('dev')})Copy the code
The store env can be accessed directly without requiring.value
setup() {
const mainStore = useMainStore()
console.log(mainStore.env) // Do not add.value
}
Copy the code
Adding external attributes
When you need to add data from other libraries or when you don’t need reactive data, you should wrap the passed object with markRaw(), for example
MarkRaw comes from VUe3 and marks an object so that it will never be converted to a 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 the code
Use $SUBSCRIBE, $onAction inside the plugin
pinia.use(({ store }) = > {
store.$subscribe(() = > {
// react to store changes
})
store.$onAction(() = > {
// react to store actions})})Copy the code
Typescript support for new properties
The PiniaCustomProperties interface can be extended when new properties are added through the plug-in
You can safely write and read new attributes by setting get, set, or simply declaring the type of the value
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
set env(value: string | Ref<string>)
get env() :string/ / orenv: string}}Copy the code