30_ Use of vuex's alternative tool Pinia

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
    }
}

Keywords: Front-end Vue Vue.js

Added by olaf on Mon, 07 Mar 2022 08:11:16 +0200