It’s a New Year and still suffering from a bad genre experience with Vex4? 🙅, we’re going to replace Vuex with Pinia 💪; Pinia, vue.js official development, next generation Vuex, simplified concepts, better TypeScript support. Come and experience it

Introduction to the

Pinia is a Vue repository. Pinia was originally created as an experiment to test the Vue 5 proposal by a member of the vue.js core team. The Vue state management was redesigned around November 2019 to accommodate the Composition API. It supports Vue3 as well as Vue2’s Options API and is the next generation of lightweight state management libraries.

Liverpoolfc.tv: pinia.vuejs.org/

We know that the Vuex version of the state management library needs to be used in different Vue versions, otherwise errors will occur.

Vue2. X we use — vuex 3.x version

Vue3. X we use — vuex 4.x version

Pinia supports both Vue3 and Vue2, and doesn’t have to be used with Composition API, which is the same in both cases. ★,°:.☆( ̄▽ ̄)/$:. 🌹 🌹 🌹

The core concept

  1. State: Used to store data, somewhat similardataThe concept of;
  2. Getters: used to get data, somewhat similarcomputedThe concept of;
  3. Actions: used to modify data, somewhat similarmethodsThe concept of;
  4. Plugins: Pinia plugin.

❓ Why Pinia is the next generation of lightweight state management libraries

Let’s take a look at the official versus official Rfcs proposal for VEX5 as shown below: github.com/vuejs/rfcs/…

And are these similar to not pinia’s functions? And you can see it in the comments below

Why use Pinia

Previously, IT was also introduced that Pinia was developed by the official core developer of VUE, which is naturally supportive of VUE. The design is also very similar to the proposal of VUE 5, and even part of the inspiration of VUE 5 comes from Pinia. What are its advantages? What’s better than vuex 4.x?

So let’s look at the disadvantages of vuex 4.x:

  • 1. Change onestateIs required if synchronous updates are requiredmutationsIn the asynchronous caseactions
  • Adding typescript to Vuex state requires custom complex types to support TS
  • 3. To divide vUE’s state into parts, use module
  • 4. Start with VUe3.gettersResults are no longer cached like computed attributes
  • Vex4.x has some type safety issues

Modules without namespaces, or all stores are namespaces

advantage

  • The completeTypeScriptSupport; Born with perfect type inference
  • Two syntax are supported for creating stores:Options ApiComposition Api;
  • analogyvuex 4.x.Piniadeletemutations; Use actions to operate state. Although you can directly operate Store by accessing the corresponding state through this.xx, it is recommended to operate in actions to ensure that the state is not accidentally changed
  • The Actions configuration item in the Store can perform synchronous or asynchronous methods, and the action is called for a regular function call instead of using the Dispatch method or the MapAction helper function
  • Multiple stores can be built, and packaging management is automatically split
  • Modular design, easy to split state, can well support code segmentation;
  • With no nested modules, just the concept of stores that can be declared multiple stores,Pinia by design provides a flat structure that is still able to cross and combine between storage Spaces,
  • There is no need to add stores dynamically, they are all dynamic by default;
  • Modules without namespacesNamespaced modulesOr all stores are namespaces
  • Pinia is free to extend the Plugins’ official documentation
  • Extremely light, only 1 KB
  • Support Vue DevTools, SSR and Webpack code split
  • Pinia can be used in both Vue2 and Vue3, and it does not have to be used in conjunction with the Composition API, which is used in the same way in both.

Pinia and Vuex code splitting mechanism

Vuex code segmentation: When packaging, Vuex will combine three stores and package them. When Vuex is used on the home page, this package will be introduced to the home page and packaged together. Finally, one JS chunk will be output. The problem is that the home page only needs one of the stores, but the other two unrelated stores are also packed in, resulting in a waste of resources.

Pinia’s code segmentation: When packaging, Pinia will check reference dependencies. When the home page uses main Store, packaging will only combine the store and page used to output 1 JS chunk, and the other 2 stores are not coupled in it. Pinia was able to do this because its design was store decouple, solving the coupling problem of the project.


Using Pinia will make you really fragrant !!!!

Quick learning

The installation

We can install Pinia directly in the existing VUE project to use state management, and there is no dependency conflict with the vuex already installed. Here we will create a new project to see step by step, here is an example of vue3 project, so that we can better combine ts to see

Use vite to create a vuE-TS project. (Since Pinia is specially used for VUE, I will choose VUE. Finally, I will use IT for TS because it smells better.

Vite2 +vue3+typescript+ AXIos + Vant Mobile Framework

Step 1: Create a vuE-TS project using Vite

npm init @vitejs/app <project-name> --template
Copy the code

Step 2: Install state management relies on Pinia

Yarn add pinia@next # or with NPM NPM install pinia@next // This version is compatible with Vue 3. If you are looking for a Vue 2.x compatible version, check out the V1 branch.Copy the code

Initial Configuration

Create a pinia (root storage) in the entry file main.ts and pass it to the application:

Import {createApp} from 'vue' import App from './ app. vue' import {createPinia} from 'pinia' // createPinia instance const pinia = createPinia() // instantiate Vue createApp(App).use(pinia) // mount to Vue root instance. Mount ('# App ') // mount to real DOMCopy the code

In the code above, you added Pinia to the vue.js project. We can see that there is a method called createPinia that is mounted to the Vue root instance, so that you can use Pinia’s global object in your code.

use

Traditionally we would create a store for state management under the project SRC, and then import that data where it is needed. Pinia is the same way.

Basic usage and parsing

Steps:

  • 1. Define the container
  • 2. Use state in the container
  • 3. Change the state
  • 4. State of the action in the container

1. Define the container

Declare a store in a file

Create SRC /store/index.ts to store status management

The first mode is Store

State is constructed in a vuex manner

import { defineStore } from "pinia"; // Parameter 1: the container ID, which must be unique in the future pinia will easily mount to the root container The option object // returns the value of a function call that gets exposed externally to a use method, This method exports the state export const useMainStore = defineStore("main", {/** * the component-like data used to store global state */ state: () => { return { countPinia: 100 }; }, // state: () => ({countPinia: 100}), getters: {double () {// This in the getter points to 👉 state return this.countpinia * 2}, // We can get state double2 in the first argument of the function: (state) => {return state.count * 2}}, // actions {increment() {// This in action refers to 👉 state this.countpinia ++},});Copy the code

Or we could write it this way

export const useMainStore = defineStore({
    id: 'main',
    state: () => {
        return {
            countPinia: 100
        }
    },
  ...
})
Copy the code

The second way is pattern

Use the form function to create a store, somewhat similar to setup in Vue3

Import {ref, computed} from "vue" import {defineStore} from "pinia" // Expose a use method externally, This method exports the state const useMainStore = defineStore('main', function () { const countPinia = ref(0) const double = computed(() => countPinia.value * 2) function increment() { countPinia.value++ } return { countPinia, double, increment } }) export default useMainStoreCopy the code
👨 🎓Parsing code:

Pinia creates a store through the defineStore function, which receives an ID to identify the store, along with the Store option

To create a store, you call the defineStore method with an object containing the States, Actions, and getters needed to create a base store.

This method returns a function that exposes the method externally, which exports the state we defined;

defineStoreThe method takes two parameters

  • Parameter 1: the container ID, which must be unique in the future pinia will easily mount to the root container
  • Parameter 2: Option object

2. Use state in the container

As mentioned above in Pinia advantage, Pinia provides two ways to use store,Options ApiComposition ApiPerfect support from Both Sides

The biggest difference between Vue2 and Vue3 is that Vue2 uses the Options API while Vue3 uses the Composition API

Using the Options API pattern definition, this approach is similar to vue2 component model form, is also a vue2 technology stack developer friendly programming mode.

There is also the Composition Api, we use the Setup mode definition, in line with Vue3 Setup programming mode, to make the structure more flat, I recommend using this method.

Options Api

In the Options Api, you can directly use the official mapActions and mapState methods to export the state, getter, and action in the store. Its usage is basically the same as Vuex, and it is easy to use.

import { mapActions, mapState } from 'pinia' import { useMainStore } from '@/store' export default { name: 'HelloWorld', computed: { ... mapState(useMainStore, ['countPinia', 'double']) }, methods: { ... mapActions(useMainStore, ['increment']) }Copy the code

Composition Api pattern usage

<script setup lang="ts"> import { useMainStore } from './store' const useMain = useMainStore() const { countPinia} = useMain const clickEdit = ()=>{ useMain.increment() } </script> <template> <h2>{{ useMain }}</h2> <h2>{{ </p> {{useMain. Double}}</p> <button @click="clickEdit"> </button> </template>Copy the code

It can also be combined with computed acquisition.

const countPiniaComputed = computed(() => useMainStore.countPinia)
Copy the code
👨 🎓Parsing code:
  • 1.const { countPinia} = useMainThere are some problems with this kind of deconstruction. The data can’t be responsive. What do you need to do? You can use pinia’sstoreToRefs. The following
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useMainStore } from './store'
const useMain = useMainStore()
const { countPinia } = storeToRefs(useMain)
</script>
​
<template>
  <h2>{{ useMain }}</h2>
  <h2>{{ useMain.countPinia }}</h2>
  <p>{{countPinia}}</p>
</template>
Copy the code

UseMain is an object wrapped in Reactive, which means there is no need to write.value after getters, but if you want to structure it that way you break the responsiveness as in props. So we will use the storeToRefs in Pinia to wrap it

Pinia has already made state data reactive

Ps: At this time, we can actually feel that pinia supports TS, so there will be a good hint, rapid development, quite excellent; If we want these hints, we need to override the vuex 4.x method and declare the types manually

  • Pinia’s setup mode is called by install.
  • You can define as many as you wantstores, in the realuseMainStore()The store is not created before execution, and once the store is instantiated, you can directly invoke any properties defined in “state”, “getters”, and “Actions” in the store.
  • 4. If we use Pinia in the project using Vue 2, instateThe data created in theData is in the Vue instanceSame rule, namelyThe state object must be normal, and you need to call it when you add a new attribute vue.set () to it.

3. Change the state

In the Composition Api, both the state and the getter need to listen for changes through computed methods, just as in the Options Api, you need to put changes in computed objects.

The state value obtained from the Options Api can be directly modified. Of course, it is recommended to write an action to operate the state value, which is convenient for later maintenance.

Method 1: The simplest way:

The state value obtained from the Options Api can be directly modified.

// useMain. CountPinia++ in the componentCopy the code

Method 2: $path Batch update 1

If need to modify multiple data, it is recommended to use the path batch updates, compared with the way a: this kind of writing is not a simple optimization, writing on the path batch updates, compared with the way a: this kind of writing is not a simple optimization, writing on the path batch updates, compared with the way a: this kind of writing is not a simple optimization, writing on the path is a performance optimization

$patch({countPinia: useMain. CountPinia +1})Copy the code

Method 3: $patch uses a function

Also batch update (advantage, compared with method 2, when dealing with some complex data, such as groups, objects, etc., it is simpler)

$patch(state=>{state.countpinia ++})Copy the code

Method 4: Encapsulate action

For more logic, you can change the state by encapsulating it into actions (the most common), which can be accessed directly through this.

// Usemain.changestate ()Copy the code

SRC /store/index.ts add a method to the action

//src/store/index.ts import { defineStore } from "pinia"; // Parameter 1: the container ID, which must be unique in the future pinia will easily mount to the root container The option object // returns the value of a function call that gets exposed externally to a use method, This method exports the state export const useMainStore = defineStore("main", {/** * the component-like data used to store global state */ state: () => { return { countPinia: 100 }; }, /** * A computed object similar to a component is used to encapsulate computed attributes and has a caching function */ getters: {}, /** * A method similar to a component that encapsulates faulty logic changes state */ actions: { changeState() { // this.countPinia++ this.$patch(state => { state.countPinia++; }); }}});Copy the code
👨 🎓Parsing code note:

You cannot use the arrow function to define an action because the this point inside the function is external

Good programming practice: State changes are handled by actions

Detailed explanation of core concepts

State

In Pinia, the State State is defined as a function that returns the initial State. Component-like data is used to store global state

Options API

  • 1. Must be a function: this is to avoid cross-request data state contamination during server rendering

  • 2. Arrow functions are recommended for better TS type derivation

Import {defineStore} from 'pinia' const useMainStore = defineStore('main', {// Recommend arrow function state for full type inference: () => {return {// All these attributes will automatically infer their type counter: 0, name: 'lee'}},})Copy the code

Skills:

If you use Vue 2, the data you create in state follows the same rules as data does in the Vue instance, namely that the state object must be normal and you need to call it when you add a new attribute Vue.set() to it.

See also: Vue#data.

Composition Api schema definition

Use the SETUP pattern definition

import { ref } from 'vue'; import { defineStore } from 'pinia'; Export const useCounterStoreForSetup = defineStore('counter', () => {const count = ref<number>(1); return { count}; });Copy the code

1. Access state

Introduces a created Store container that is called to read and write state directly through instance access state

<script setup lang="ts">
import { useMainStore,useCounterStoreForSetup } from './store'
const useMain = useMainStore()
const useCounterForSetup = useCounterStoreForSetup()
</script>
​
<template>
  <h2>{{ useMain.countPinia++ }}</h2>
  <hr>
  
  <p>{{useCounterForSetup.count++}}</p>
</template>
Copy the code

2. Reset state

The state can be reset to its initial value $reset() by calling a method on the store:

<script setup lang="ts">
import { useMainStore,useCounterStoreForSetup } from './store'
const useMain = useMainStore()
const useCounterForSetup = useCounterStoreForSetup()
useMain.$reset()
</script>
Copy the code

3, modify,

See changing state in use above for details

  • Directly modifying
  • By calling the$patchMethod allows you to view partsstateObject batch change
  • Or change it in the Action method
  • You can do this by puttingstore$stateProperty to replace with a new objectstoreThe whole state of;useMain.$state = { countPinia: 666 }
  • You can change the overall state of your applicationstateIn thepiniaInstance.pinia.state.value = {}

4. Subscription status

States and their changes can be observed through the $SUBSCRIBE ()store method, similar to Vuex’s Subscribe method.

The advantage of using $subscribe() over regular watch() is that the subscription is triggered only once after patch (for example, when using the above version of the function).

useMain.$subscribe((mutation, state)=>{
  localStorage.setItem('useMain-state', JSON.stringify(state))
})
Copy the code

👨🎓

When the state in the useMain changes, the entire state of those useMain is stored in the local cache

By default, status subscriptions are bound to the component to which they are added (if stored inside the component setup()). This means that when components are uninstalled, they are automatically removed. Detached: true If you want to keep them after the component is uninstalled, use {detached: true} as the second parameter to separate the state of the subscription from the current component:

useMain.$subscribe((mutation, state)=>{
  localStorage.setItem('useMain-state', JSON.stringify(state))
}, { detached: true })
Copy the code

Ps: You can view the entire state on the Pinia instance:

Watch (pinia. State, (state) => { Localstorage.setitem ('piniaState', json.stringify (state))}, {deep: true})Copy the code

Getters

Computed, a component like this, is used to encapsulate computed properties and has a caching function, and the first parameter of getters is state.

Declare a method in getters:

/** * A computed object similar to a component is used to encapsulate computed attributes and has caching capabilities */ getters: The {/** ** @param state function takes an optional argument: If this is used, the type of the return value of the function must be specified manually otherwise the type cannot be deduced * @returns */ getCount(state){return State.countpinia +10}, // return value type ** must ** be explicitly specified getCount2(): Return this.countPinia +10},},Copy the code
👨 🎓Parsing code note:

The function in getters takes an optional argument: a state state object

  • You can also use this without using state
  • If you use this you have to manually specify the type of the return value of the function otherwise the type cannot be derived

2. Use the same as state in components

<template> <! -- state --> <p>{{useMain. CountPinia}}</p> <! -- getters - > < p > {{useMain. GetCount}} < / p > < / template >Copy the code

** 3, pass parameters: use higher-order functions to pass parameters **

/** * A computed object similar to a component is used to encapsulate computed attributes and has caching capabilities */ getters: The {/** ** @param state function takes an optional argument: If this is used, the type of the return value of the function must be specified manually otherwise the type cannot be deduced * @returns */ getCount(state){return state.countPinia +10 }, getCount2(state):Number{ return (num)=> { return state.countPinia + num } } },Copy the code

Use in components

 <p>{{useMain.getCount2(3)}}</p>
Copy the code

4. Access getters in other stores

For example, we declare two stores

export const useMainStore = defineStore({
  id: "main",
  state: ()=>({
    return {countMain: 2}
  }),
  getters: {
    getCountMain(state){
      return state.countMain + 2
    }
  }
})
​
​
  export const useStore = defineStore({
    id: "user",
    state: ()=>({
         return { countUser: 2}
    }),
    getters: {
      addMain(state){
        const store = useMainStore()
        return state.countUser + store.countMain
      }
    }
 })
​
Copy the code

5. Use Composition Api in VUE3 components

<script setup lang="ts"> import { storeToRefs,mapState } from 'pinia' import { useMainStore } from '@/store' const store  = useStore() const { countPinia,getCountMain } = storeToRefs(store) </script> <template> <p>{{countPinia}}</p> <p>{getCountMain}}</p> </template>Copy the code

6. The usage of options API in VUe2

import { mapState } from 'pinia' import { useMainStore } from '@/store' export default { computed: {// Allow access to this.doubleCounter inside the component // same as reading from store.doubleCounter //... mapState(useMainStore, ['getCountMain']), // same as above but registers it as this.myOwnName ... MapState (useMainStore, {myOwnName: 'doubleCounter',})Copy the code

Action

For modifying data, somewhat similar to the concept of methods;

They can be defined using the Actions attribute defineStore(),

Both asynchronous and synchronous methods can be used, and methods can be used in the business logic of the project. (Here, unlike Vuex, Pinia only provides a method to define how to change the rules of the state, instead of relying on Actions.)

import { defineStore } from "pinia"; // Parameter 1: container ID, must be unique in the future pinia will easily mount all to the root container // parameter 2: The option object // return value is a function call that gets export const useMainStore = defineStore("main", {/** * The component-like data used to store global state * 1. Must be a function: this is to avoid cross-request data state contamination during server rendering * 2. */ state: () => {return {countPinia: 100, title: "pinia", arr: [1, 2],}; }, /** * Similar to the component's methods encapsulation error logic change state */ actions: {// Note: ChangeState () {// this.countpinia ++ // this.title = "Change the title in the action" // this.arr.push(3) this.$patch(state => { state.countPinia++; State. title = "modify "; state.arr.push(3); }); }, changeState2(num:number){ this.countPinia += num; }}});Copy the code
👨 🎓Parsing code note:

The actions method can access the entire storage instance through this, so you can’t use the arrow function at this point, because the arrow function inside this points to the outside.

Use is used in the Composition Api pattern

<script setup lang="ts"> import { storeToRefs,mapState } from 'pinia' import { useMainStore } from '@/store' const store  = useStore() const { countPinia } = storeToRefs(store) const clickEdit =()=>{ store.changeState() } </script> <template> <p>{{countPinia}}</p> <button @click="clickEdit"> </button> </template>Copy the code

Options API

import { mapActions } from 'pinia' import { useMainStore } from '@/store' export default { methods: {// The component internally allows access to this.changestate () // just like calling from store.changEstate ()... MapActions (useMainStore, ['changeState']), // Same as above but register it as this.myownName ()... mapActions(useMainStore, { myOwnName: 'changeState' }), }, }Copy the code

Invoke other actions

export const useAppStore = defineStore({
  id: 'app',
  actions: {
    setData(data) {
      console.log(data)
      return data
    }
  }
})
export const useMainStore = defineStore({
  id: 'main',
  actions: {
    setData(data) {
        const appStore = useAppStore()
        return appStore.setData(data)
    }
  }
})
​
Copy the code

Subscribe to the actions

$onAction() : pinia.vuejs.org/core-concep…

The plug-in

Pinia stores (Stores) can be fully extended through low-level apis. Here are the actions you can execute:

  • tostorage(store) Add a new attribute;
  • When definingstorage“, add a new option;
  • tostorageAdd new methods;
  • Existing methods of packaging;
  • Change or even cancel actions;
  • Implement side effects such as local storage;
  • Applies only tospecificstorage.

Details can be found at pinia.vuejs.org/core-concep…

Persistent storage

The plug-in pinia-plugin-persist assists in data persistence.

The installation

npm i pinia-plugin-persist --save
Copy the code

use

Step 1: Insrc/store/index.ts

import { createPinia } from 'pinia'
import type   { App } from "vue";
import piniaPluginPersist from 'pinia-plugin-persist'
​
​
export function setupStore(app: App<Element>) {
    const pinia = createPinia();
    pinia.use(piniaPluginPersist);
    app.use(pinia);
}
Copy the code

Step 2: Modify the initial configuration of Pinia in the entry main.ts

import { createApp } from 'vue'
import App from './App.vue'
​
import { setupStore } from "@/store";
​
const app = createApp(App)
​
setupStore(app)
​
app.mount('#app')
Copy the code

Step 3: Create the corresponding store

In the SRC/store/app. Ts

import { defineStore } from "pinia"; export const useAppStore = defineStore("app",{ state: () => { return { name: "app store state: name", }; }, actions: { changeName() { this.$patch((state: { name: string; }) => {state. Name = "modify "; }); },}, // Enable data cache persist: {enabled: true,},});Copy the code

Step 4: Use

<script setup lang="ts"> import {useAppStore} from '@/store/app' const appStore = useAppStore(); </script> <template> <button @click="clickEdit"> </button> <hr> <div>{{appStore.name}}</div> </template>Copy the code

Data is stored in sessionStorage by default, and the store ID is used as the key.

Q:What if you don’t want to use the id in the store as the store?

You can customize this key; persist has a strategy that defines the key and changes the storage location from sessionStorage to localStorage.

persist: {
  enabled: true,
  strategies: [
    {
      key: 'my_store_app',
      storage: localStorage,
    }
  ]
}
Copy the code

Q: What if you don’t want to store left and right state states, but only parts?

//src/store/app.ts import { defineStore } from "pinia"; export const useAppStore = defineStore("app",{ state: () => { return { name: "app store state: Name ", title:' title ', sub:'pinia', content:'... '}; }, actions: { changeName() { this.$patch((state: { name: string; }) => {state. Name = "modify "; }); },}, // Enable data cache persist: {enabled: true, strategies: [{key: 'my_store_app', storage: localStorage, paths: ['name', 'sub'] } ] }, });Copy the code

Thank you

At this point, this article is over, if there are any mistakes or deficiencies, welcome to the comment section correction! Thanks for reading ~~