Write in the beginning

On January 20, UVU announced on Zhihu that Vue3 would become the default version starting from February 7:

This article gives you a taste of the “next generation of Vuex” — Pinia.

What is a Pinia

Pinia introduction

Around November 2019, Pinia’s authors began to try to redesign the Store state management for Composition API, with the same design principles and ideas as Vuex.

Different versions of Vue use different versions of Vuex. The version of Vuex 3.x is required in Vue2 and Vuex 4.x is required in Vue3. However, Pinia does not have this limitation. Pinia can be used in both Vue2 and Vue3, and does not have to be used with the Composition API, which is used in the same way in both.

Pinia is now officially incorporated into the Vue system. As OF this writing, the version has been updated to V2.0.9.

Why Pinia

Pinia is a state manager for Vue that allows you to share state across components/pages.

If you’re already familiar with the Composition API, you might want to use something like this to share the global state:

export const state = reactive({})
Copy the code

In SPA applications, it is ok to do so, but if it is used in SSR, there will be security risks. So Pinia is still recommended.

Using Pinia has the following benefits:

  1. Debugging tool support

    I currently have a beta version of Vue DevTool installed. You can download the beta version by searching for Vue DevTool in the Google App Store.

  1. The debugging tool supports tracking the timeline of actions, mutations;
  2. The store information used in the current component and all store containers can be displayed. You can directly modify the data for debugging.
  3. Support for Time Travel for easy debugging.
  1. Hot module replacement: You do not need to manually refresh the page when changing the status.
  2. Plug-in support: You can extend Pinia’s capabilities through plug-ins.
  3. Good Typescript support: good code hints and automatic code replenishment.
  4. Support for SSR.

Pinia versus Vuex

Pinia has the following differences compared to vex4.x below:

  1. 2. remove mutations: In Vuex, to change the status, it is necessary to submit mutations, but mutations cannot perform asynchronous operations. Therefore, Actions are born. After obtaining data asynchronously, mutations are invoked through actions to modify data. Now in Pinia, actions supports both synchronous and asynchronous operations, so mutations appear redundant, and are no longer needed;
  2. Support for Typescript is friendlier: With Pinia, you no longer need to define complex wrappers to support Typescript;
  3. No magic strings are injected: This is often the case when using Vuex in componentsthis.$store.commit("xxxxx")Such magic strings, although these strings will be extracted as constants exposed, but it is not very convenient to maintain. In Pinia you no longer need to use this approach, but call directlydefineStoreAfter the method is exposed, then through$patch.action, or directly modify the value in store;
  4. No need to add stores dynamically: You can define stores whenever you want, and they are dynamic by default;
  5. Modules with nested structures are no longer needed;
  6. No modules with namespaces.

The use of destined

The installation

We first set up a local Vue3 project to demonstrate the use of Pinia. Here we use Vite to build:

Vite requires node.js version >= 12.0.0. However, some templates need to rely on higher versions of Node to function properly, so upgrade your Node version when your package manager warns.

Vite provides a template for building local projects. Here we use PNPM to build a Vite + Vue local project. Vue3 and Pinia support for Typescript is now very good, so it is recommended that you choose Typescript templates when building projects using templates:

pnpm create vite pinia-useage-examples -- --template vue-ts
Copy the code

After that, we install Pinia in the project:

 pnpm add pinia
Copy the code

Now that we’re ready, let’s try Pinia

use

First, we need to initialize Pinia via createPinia and mount it to an instance of Vue:

// src/main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

/ / create pinia
const pinia = createPinia()

const app = createApp(App)
// Mount to Vue instance
app.use(pinia)
app.mount('#app')
Copy the code

SRC /store/index.ts SRC /store/index.ts

// src/store/index.ts

import { defineStore } from 'pinia'

// Define store, myFirstStore is the name of store, the name must be unique, cannot be repeated
export const useStore = defineStore('myFirstStore', {})Copy the code

The important thing to note here is,defineStoreThe first argument to set the store container name, which must be unique and unique!

As mentioned above, compared with Vuex, mutations are removed from Pinia, and the remaining contents to be understood mainly include three parts:

  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.

Now let’s take a look at each of them in turn.

State

Define the State

In Vuex, state is defined as an object, whereas in Pinia state is defined as a function, somewhat similar to the concept of data in components:

// src/store/index.ts

import { defineStore } from 'pinia'

// Define store, myFirstStore is the name of store, the name must be unique, cannot be repeated
export const useStore = defineStore('myFirstStore', {
  state: () = > {
    return {
      count: 0.name: 'foo'.list: [1.2.3]}}})Copy the code

Read the State

Once state is defined, we can read the data in state in the page by introducing exposed useStore:

// src/App.vue

<template>
  <p>count: {{ myStore.count }}</p>
  <p>name: {{ myStore.name }}</p>
  <p>list: {{ myStore.list }}</p>
</template>

<script setup lang="ts">
import { useStore } from './store'

const myStore = useStore()
</script>
Copy the code

Write myStore. XXX in the process of use, is not very troublesome? Can it be used in a deconstructive way?

Let’s try it out and see if we can get a value from a store by deconstructing it:

// src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p>count: {{ count }}</p>
  <p>name: {{ name }}</p>
  <p>list: {{ list }}</p>
</template>

<script setup lang="ts">
import { useStore } from './store'

const { count, name, list } = useStore()
</script>
Copy the code

Through this experiment, we found that it is indeed possible to obtain store values through deconstruction. But there is a problem with this approach: getting the value of state directly by deconstructing it is non-responsive! This means that subsequent changes to the value in the Store will not change the page.

So how do we solve this problem? The answer is to convert the structured value to a responsive value using the storeToRefs method provided in Pinia:

// src/App.vue

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useStore } from './store'

const { count, name, list } = storeToRefs(useStore())
</script>
Copy the code

In addition to using these two methods, Pinia also provides a combination of mapState and computed to obtain values in the Store, similar to the mapState in Vuex:

// src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p>count: {{ res.count }}</p>
  <p>name: {{ res.name }}</p>
  <p>list: {{ res.list }}</p>
</template>

<script setup lang="ts">
import { mapState } from 'pinia'
import { useStore } from './store'
import { computed } from 'vue'

const res = computed(() = > {
  constdata = { ... mapState(useStore, ['count'.'name'.'list'])}return {
    count: data.count(),
    name: data.name(),
    list: data.list()
  }
})
</script>
Copy the code

Because the values are obtained through computed, the values behind the structure are reactive and do not need to be converted using the stateToRefs method.

Within computed, you can also rename attributes or redefine new return values:

<script setup lang="ts">
import { mapState } from 'pinia'
import { useStore } from './store'
import { computed } from 'vue'

const res = computed(() = > {
  constdata = { ... mapState(useStore, {// Rename count to myCount and return count + 1
      myCount: state= > state.count += 1.// Rename name to no name and return the value of name
      myName: 'name'.// Rename list to myList and return list after 4 is inserted
      myList: state= > {
        state.list.push(4)
        return state.list
      }
    })
  }
  return {
    count: data.myCount(),
    name: data.myName(),
    list: data.myList()
  }
})
</script>
Copy the code

Note:

  1. The examples used in this article aresetupGrammar sugar if not usedsetupGrammar sugar,mapStateThe use of 🌰 is somewhat different from that of 🌰 in this articlebindThe way of chestnut, specific can refer toThis article). Details are available atThe official documentation. If there is any improper use in this article, please correct it.
  2. throughcomputedThe returned state cannot be structurally retrieved because it is not a pure object, but a single objectComputedRefImplType.
  3. throughmapStateThe value we get is zeroread-onlyIf you want it writable, you can use itmapWritableStateThe way.

Change the State

Now that we can retrieve the values stored in State, how can we modify the values in State? In fact, it is very simple, mainly divided into the following ways:

The first: direct modification

Change the value of State directly:

// src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p>count: {{ count }}</p>
  <button @click="handleChangeStore">change store</button>
</template>

<script lang="ts" setup>
import { useStore } from './store'

const store = useStore()
// Modify the value in store
const handleChangeStore = () = > {
  // Modify directly
  store.count += 1
}
</script>
Copy the code

You can even change all values by overwriting $state by force:

// src/App.vue

// omit a lot of code...

// Empty the store directly
store.$state = {}
Copy the code

Second: pass$patchTo modify the way

When we need to change multiple values at once, we can use the $patch method to change multiple values in batches:

// src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p>name: {{ name }}</p>
  <p>list: {{ list }}</p>
  <button @click="handleChangeStore">change store</button>
</template>

<script lang="ts" setup>
import { useStore } from './store'

const store = useStore()

// Modify the value in store
const handleChangeStore = () = > {
  // Change the value by $patch
  store.$patch({
    name: 'test'.list: [...store.list, 4]})}</script>
Copy the code

$patch merges the incoming object with the state in the Store.

In addition to passing the object directly, if there is a more complex operation, the $patch method also accepts a callback function that operates on the data that needs to be modified:

// SRC/app.vue // omit lots of code... $patch(state => {state.name = 'mY name is: ${state.name}'})Copy the code

Third: pass$resetMethod to restore the original value

When we modify the values in state, we can use the $reset method if we want to restore them to their original values.

Fourth: through callactionsTo modify the value

First, we need to define actions in the Store container:

// src/store/index.ts

/ / define the store
export const useStore = defineStore('myFirstStore', {
  // Omit some code
  actions: {
    changeCount () {
      this.count ++
    }
  }
})
Copy the code

Methods defined in Actions can access properties in State directly through this, rather than passing in a context like Vuex, using a similar approach to Methods.

Once the definition is complete, we can use it directly in the reference:

// src/App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p>count: {{ count }}</p>
  <button @click="handleChangeStore">change store</button>
</template>

<script setup lang="ts">
import { useStore } from './store'

const store = useStore()

// Modify the value in store
const handleChangeStore = () = > {
  // Use actions
  store.changeCount()
}
</script>
Copy the code

Of course, actions can also be used with income parameters, which is the same as methods, so I won’t go into details here.

Debug State

Open the console, switch to Vue DevTool, and select Pinia to see the store container information:

And click the edit button behind the data, you can also modify the value in store for debugging, which is very convenient:

Getters

Getters is used a bit like computed, returns a value (or none) and can be used directly on a page. If the data in state is used in getters, the value of getters will change accordingly when the corresponding data changes:

// src/store/index.ts

/ / define the store
export const useStore = defineStore('myFirstStore', {
  // Omit some code
  getters: {
    countPlusOne (state) {
      console.log('------countPlusOne------')
      return state.count + 1}}})Copy the code

Getters is cached and only computed once when used multiple times in a page:

// src/App.vue

<template>
  <p>countPlusOne: {{ store.countPlusOne }}</p>
</template>

<script setup lang="ts">
import { useStore } from './store'

const store = useStore()
</script>
Copy the code

We can see that the log is printed only once, indicating that the next two times are fetching data from the cache.

Note:

  1. gettersMethod defined in thestateThis parameter is optional if not passedstate, can be directly usedthisaccessstateData in;
  2. If it’s not passed instateTypescript generates an error message because it cannot be derivedgettersThe return value type of the method in the.

Actions

As mentioned above, you can change the value of State by Actions, but synchronously. As mentioned in the introduction to Pinia, Actions supports both synchronous and asynchronous modification of data. Let’s look at how to asynchronously modify data through Actions.

// src/store/index.ts

// omit a lot of code...
actions: {
  async changeName () {
    const newName: string = await new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve('newName')},1000)
    }).catch(err= >{})this.name = newName
  }
}
Copy the code

Through the above code, it is not difficult to find that the asynchronous modification of data by Actions is similar to the asynchronous modification of data by methods, which is much easier than Vuex.

Plugins

The plugin is introduced

Stores can be extended with custom Plugins, which can do the following:

  1. newstoreAttributes in;
  2. definestoreIs passed in a new configuration item.
  3. newstoreMethod in;
  4. packagingstoreIn the existing method;
  5. Modify or even cancelactions;
  6. Implement some side effects, such as setting the local storage cache and so on;
  7. It only applies to specificstore: After Pinia is mounted to the app, Plugins are mounted to Stores created later. Plugins are not mounted until then.

Plug-in definition & usage

A plug-in is essentially a function. The plug-in takes an optional parameter: context, which is an object containing four things:

  1. context.pinia:throughcreatePinia()Method to create Pinia instances
  2. context.app:throughcreateApp()App instance created in Vue 3 only
  3. The context. Store: store objects
  4. Context. options: Gets the configuration item passed in when defining a store

The function can bind the value of an object to all stores by returning an object:

// src/main.ts

function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie'}}Copy the code

Once the definition is complete, use the plug-in with pinia.use() :

// src/main.ts
import { createPinia } from 'pinia'

const pinia = createPinia()

pinia.use(SecretPiniaPlugin)
Copy the code

Later, when you print the Secret property in Store on the page, you’ll see the value we set. And attributes added to the store in this way can be captured by Vue devTool:

Of course you can choose to bind the value directly to the store object, but this is not captured by Vue devTool:

// src/main.ts

// Define the plug-in
function SecretPiniaPlugin({ store }) {
  // Bind attributes directly to store
  store.secret = 'the cake is a lie'
}
Copy the code

At this point, you can easily see that the customProperties property from the previous step is missing. If you need to see store content defined in the plug-in in Vue devTool during debugging, you need to add it manually:

// src/main.ts

// Define the plug-in
function SecretPiniaPlugin({ store }) {
  // Bind attributes directly to store
  store.secret = 'the cake is a lie'
  // Add this attribute manually in the development environment so that Vue devTool can capture it
  if (process.env.NODE_ENV === 'development') {
    // add any keys you set on the store
    store._customProperties.add('secret')}}Copy the code

After adding the attributes, when we use them directly in the page, we find that the page does not display the attributes that the plug-in inserted into the store. This is because the inserted attributes are non-responsive. In this case, we just need to change the values of the inserted attributes to responsive data:

import { createApp, ref } from 'vue'
import { createPinia } from 'pinia'

/ / create pinia
const pinia = createPinia()

// Define the plug-in
function SecretPiniaPlugin({ store }) {
  // Bind attributes directly to store
  store.secret = ref('the cake is a lie')
  // Add this attribute manually in the development environment so that Vue devTool can capture it
  if (process.env.NODE_ENV === 'development') {
    // add any keys you set on the store
    store._customProperties.add('secret')}}// Use plugins
pinia.use(SecretPiniaPlugin)
Copy the code

After setting, you can see the data on the page when you use it again

In addition, you can listen for actions in the plugin via the store.$SUBSCRIBE and store.$onActions methods.

See the official documentation for more gameplay details on plug-ins.

Write in the last

So far, I believe you have a preliminary impression of Pinia. If there is any improper description in the article, please correct it.

A good memory is better than a bad pen, and there are many other ways to learn about Pinia. Roll it up and taste it

Note: this article has been synchronized to soy bean curd essence station