banner
isolcat

isolcat

I am not afraid of storms, for I am learning how to sail my ship
github

A 2000-word Easy Introduction to Pinia, a Tutorial Even Monkeys Can Understand

Introduction#

image.png

What era are we in, still using traditional state management libraries? Come learn about Pinia!

The origin of the name Pinia is also interesting. In Spanish, Pinia is the closest English pronunciation of the word pineapple, and a pineapple is made up of clusters of individual flowers combined together, creating multiple fruits. Similar to stores, each one is independent, but they ultimately have connections.

When we open the vuex GitHub repository, we see the official prompt Pinia is now the new default. As the new official state management library for Vue, Pinia has many advantages, solving many issues left by Vuex, and it is more logical to write. Let's try to understand it!

Advantages of Pinia#

  • Supports both vue3 and vue2
  • Discards Mutation operations, only state, getter, and action
  • Actions support both synchronous and asynchronous
  • Supports using plugins to extend Pinia's functionality
  • No need for nested modules, more in line with Vue3's Composition API
  • Supports TypeScript
  • Code is more concise

Creating a Store with Pinia#

First, let's quickly create an empty project and install Pinia:

npm install pinia

Although Pinia supports vue2, if your vue version is below Vue2.7, you need to install the composition API separately: @vue/composition-api * (It is recommended to upgrade to Vue2.7 directly, as the transition won't be too large compared to Vue3, but it will support Vue's ecosystem better) *

Import Pinia in main.ts:

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

import App from './App.vue'


const app = createApp(App)

app.use(createPinia())

app.mount('#app')

Next, we create counter.ts in src/store and write the basic template:

import { defineStore } from "pinia";

export const mainStore = defineStore('main', {
  state: () => {
    return {
      helloWord: 'HelloWorld'
    }
  },
  getters: {

  },
  actions: {

  }
})

After creating the store mainStore, we will use it in the component.

<template>
  <div class="">{{ store.helloWord }}</div>
</template>

<script lang="ts" setup>
import { mainStore } from "../store/counter";
const store = mainStore();
</script>

<style scoped></style>

When the page displays helloWorld, it indicates that the store has been created successfully.

Pinia Changes Data State#

We add the data count to state in counter.ts:

import { defineStore } from "pinia";

export const mainStore = defineStore('main', {
  state: () => {
    return {
      count: 0,
      helloWord: 'HelloWorld'
    }
  },
  getters: {

  },
  actions: {

  }
})

Create a button with a click event:

<template>
  <div>
    <button @click="handleClick">Modify Data State</button>
  </div>
</template>

<script setup lang="ts">
import { mainStore } from "@/stores/counter";
const store = mainStore();
const handleClick = () => {
  store.count++;
};
</script>

After importing it into App.vue, modify the data state:

<template>
  <Click />
  <CountButton />
</template>

<script lang="ts" setup>
import Click from "./components/Click.vue";
import CountButton from "./components/CountButton.vue";
</script>

At this point, you will find that clicking the button changes the value of count.

In actual development, we often need to call data in the store multiple times. If we need to change its value each time with {{store.****}}, it can be cumbersome. We can destructure it:

<template>
  <div class="">{{ store.helloWord }}</div>
  <div class="">{{ store.count }}</div>
  <hr />
  <!-- After destructuring, we can directly omit store, reducing code volume -->
  <div>{{ helloWord }}</div>
  <div>{{ count }}</div>
</template>

<script lang="ts" setup>
import { mainStore } from "../store/counter";
import { storeToRefs } from "pinia";
const store = mainStore();

// Destructure
const { helloWord, count } = storeToRefs(store);
</script>

<style scoped></style>

Note that destructuring must use the storeToRefs() function!

Four Methods to Modify Data in Pinia#

  • The first method:
const handleClick = () => {
  store.count++;
};
  • The second method $patch
const handleClickPatch=()=>{
	store.$patch({
		count:store.count+2
	})
}

Although the second method is not as simple as the first, it is more suitable for changing multiple data.

  • The third method $patch passing a function
const handleClickMethod = () => {
  // Here state refers to the state in the store
  store.$patch((state) => {
    state.count++;
    state.helloWord = state.helloWord === "jspang" ? "Hello World" : "jspang";
  });
};
  • The fourth method action

When the business logic is complex, write the method in the action of the store.

actions: {
    changeState() {
      this.count++
      this.helloWord = 'jspang'
    }
  }

Getters#

Getters in Pinia are similar to those in Vuex, equivalent to computed properties in Vue. However, when we look at the Vuex documentation, we find this prompt:

Note

Since Vue 3.0, the results of getters are no longer cached like computed properties. This is a known issue that will be fixed in version 3.1. See PR #1878.

This prompt still exists today, which is one of the reasons I recommend using Pinia.

In Pinia, Getters can be cached internally, verified with code:

getters: {
    phoneHidden(state) {
      // Regular expression
      console.log('getters called');
      return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
    }
  },

When we call phone (data) twice in the component, check the console.

image.png

It will only appear once, confirming its caching functionality, which is beneficial for performance optimization.

Inter-Store Calls in Pinia#

In actual development, we often do not use just one store; there is usually a dispatch between stores. Here we create another store:

import { defineStore } from "pinia";

export const nameStore = defineStore('name', {
    state: () => {
        return {
            list: ['Xiao Hong', 'Xiao Mei', 'Fat Girl']
        }
    }
})

Next, we import it in counter.ts, import {jspangStore} from './jspang', and call it in action:

 actions: {
    getList() {
      console.log(nameStore().list);
    }
  }

Check the console, and you will find that you have successfully called another store's state, achieving inter-store calls.

Support for VueDevtools#

Although Pinia is a newcomer, it fully supports VueDevtools, which is very helpful for debugging in actual project development. It is worth mentioning that when you open VueDevtools, you can see a playful pineapple logoimage.png

It makes the development mood even better, haha.

Note: If you are using Pinia v2, please upgrade your Vue Devtools to version 6.

Pinia Practical Application: Modifying Avatar#

After all this, let's put it into practice and use pinia in an actual project. Development background: When we create a webpage, we often involve a registration feature, where the user avatar is different before and after logging in. If we only write a click event to modify the avatar, once refreshed or navigated, it will revert to its original state, and the status cannot be saved. This is where we can bring in our pinia.

Note: To persist data storage in pinia (store it in localstorage or sessionstorage), we also need to install a plugin: pinia-plugin-persist, which makes our operations more convenient. This will not be elaborated here; for details, please refer to the official documentation.

1. Create a Store#

The first step is to create a store (* Here it is assumed that you have configured the relevant environment *), create store/user.ts, with the following code:

import { defineStore } from 'pinia';

export const mainStore = defineStore('main', {
    state: () => {
        return {
            login: require('../assets/images/login.png')
        }
    },
    // Enable persistence
    persist: {
        enabled: true,
        strategies: [
            { storage: localStorage, paths: ['login'] }
        ],
    },
    getters: {

    },
    actions: {
       
    }
})

After successfully creating it, we store the user avatar in the store, and next, we will use it in the component.

2. Call in the Component#

<!-- Avatar -->
<a class="face" href="#/login">
   <img :src="store.login" alt="" />
</a>

Open the browser to check:image.png

Successfully called! Next is the most important step, ensuring that the avatar can be smoothly stored locally after logging in. We will directly add the operation to modify the data in actions:

actions: {
        changeHeadShot() {
            console.log('Data stored successfully');
            this.login = require('../assets/images/head.png')
        }
    }

We will use the action we wrote in the component:

<template>
  <!-- Omit unnecessary code (using vant's component here)-->
      <van-col span="8" @click="headerC">
        <van-button class="btn2" plain hairlin type="primary" to="/">
          <p class="text">Login</p>
        </van-button>
      </van-col>
</template>

<script setup>
// Import store
import { mainStore } from '@/store/user'
const login = mainStore()

// Implement the function to modify the avatar on click
function headerC() {
  login.changeHeadShot()
}
</script>

<style scoped>
/* Omit unnecessary code */
</style>

Next is the moment to "witness the miracle": after clicking the login button, the avatar will change:image.png

At this point, no matter how we refresh, the avatar will not change. We open the console and check the storage in the application, and we can see that our login avatar has been stored in the local browser:

image.png

Conclusion#

In summary, whether you have previously encountered Vuex or not, I recommend using Pinia. Compared to Vuex, it has better compatibility, removes Mutation from the basis of Vuex, making the syntax more concise and more in line with Vue3's Composition API. Vuex will no longer be updated and is now in maintenance mode, while Pinia, as the next generation of Vuex, what reason do we have not to learn and use it?

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.