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, supportcomposition apioptions apigrammar
  • 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