I removed Vuex from Vue3 project and used Pinia instead

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

Keywords: Javascript TypeScript Vue.js

Added by Mow on Wed, 09 Mar 2022 03:09:03 +0200