After reading this first hand guide to Pinia, will you still choose Vuex?

Vuex, as the official state management tool for Vue front-end framework, has been evolving with The Times. However, its cumbersome actions, mutations, and module nesting have been criticized by developers, and we’ve always wanted to use a simple state manager. However, the appearance of Pinia removed the multi-layer nesting of modules and the complex mutation operation, which greatly met our demands.

[TOC]

A, install,

Since we decided to use Pinia, we did not choose Vuex when creating a Vue project. Then install Pinia using NPM or YARN.

# with yarn
yarn add pinia --save
# or with npm
npm install pinia --save
Copy the code

2. Create pinia and mount it to app

Import createPinia by app.use(createPinia()) or assign to the variable pinia before app.use(pinia).

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";

const app = createApp(App),
  pinia = createPinia();
app.use(pinia).use(router).mount("#app");
Copy the code

Define store

Pinia’s stores are flat, so they are simple to use, unlike Vuex’s module nesting. Declare a store via defineStore. The first variable passed in is the ID of the store, which should be guaranteed to be unique. The second parameter is the initial configuration and contains options such as state, getters, and Actions, all similar to Vuex. Note that state is a function that returns an object, just like the data option in vue’s Options API. In addition, Pinia believes that mutations will increase the operational complexity, so there are many ways to modify the value of state, or even directly, after removing the mutations option, which will be introduced later.

Since I accidentally chose typescript when creating the Vue project, I wrote interface in a random way that javascript can ignore.

// src/store/userStore.ts

// Import and use defineStore to define the store
import { defineStore } from "pinia";

interface UserState {
  username: string;
  password: string;
  role: string;
}

export const useUserStore = defineStore("user", {
  state: (a) :UserState= > {
    return {
      username:"Amy".// ...
    };
  },
  getters: {
    // ...
  },
  actions: {
    // ...}});Copy the code

Iv. Introduction of each property of store

1.state

State is a function that returns an object to store common state. Modification of state or an attribute of state is called mutation. In Pinia, the mutations option is not available. There are several mutation behaviors for modifying state:

  • Directly modifying

    • // xxx.vue
      import { ref, computed, defineComponent } from "vue";import { useUserStore } from "@/store/userStore";
      import { useUserStore } from "@/store/userStore";
      export default defineComponent({
        setup() {
          const userStore = useUserStore();
          const username = computed(() = >userStore.username)
          userStore.username = "Zhang";
          return {username}
        }
      })
      Copy the code
  • Modify in action (also direct)

    • Declare the corresponding action method in actions. Use this to get the store instance:

    • // src/store/userStore.ts
      // Import and use defineStore to define the store
      import { defineStore } from "pinia";
      
      interface UserState {
        username: string;
        password: string;
        role: string;
      }
      
      export const useUserStore = defineStore("user", {
        state: (a) :UserState= > {
          return {
            username: "".password: "".role: ""}; },actions: {
          // Use this to refer to the store instance
          setUserInfo(username: string): void {
            this.username = username;
          },
          setRole(role: string): void {
            this.role = role; ,}}});Copy the code
    • Then call the appropriate action in setup:

      import { useUserStore } from "@/store/userStore";
      setup(){
        const userStore = useUserStore()
        userStore.setRole("Supreme Treasure");
      }
      Copy the code
  • Use $patch to modify part of the state property by passing in an object or a function that returns the corresponding object

    import { useUserStore } from "@/store/userStore";
    setup(){
      const userStore = useUserStore();
      // Use $patch to pass in the object to modify part of the state property
      // no mutation. Type = 'patch object'
      userStore.$patch({
        username: "Bill".password: "Li Si loves cake.".role: "father"
      })
    Copy the code
  • Pass in the function $patch to change the state

    // no mutation. Type = 'patch function'
    userStore.$patch((state) = > {
      state.username = "Purple xia"
      state.password = "Supreme Treasure"
    })
    Copy the code
  • Use $state to reset the entire state, in which case the object passed in must contain all properties of state

    setup(){
    // no mutation. Type = 'patch function'
      userStore.$state = {
        username: "The dog egg".password: "gogogo".role: "son"}}Copy the code

2. Listen for state changes

Use $SUBSCRIBE to monitor state changes (similar to watch) and receive a callback function as the first parameter. The received callback mainly contains mutation and state parameters. The mutation contains information such as the mode of modifying the state and storeId. The state can be processed based on the corresponding information.

userStore.$subscribe((mutation, state) = >{mutation contains information to modify state// Modify the state event information
  console.log(mutation.events);
  // The first argument passed when creating a store
  console.log(mutation.storeId);
  / / 'direct' | 'patch object' | 'patch function' three
  // Both direct modification and direct modification in action correspond to 'direct'.
  // $patch an object is 'patch object'
  // $patch a function or $state reset to correspond to 'patch function'
  console.log(mutation.type);

  // state is the state Proxy after modification
  if (mutation.storeId === "user") {
    console.log(state)
    // Implement automatic persistent storage
    localStorage.setItem("userInfo".JSON.stringify(state))
  }
})
Copy the code

Note that $SUBSCRIBE only applies to the vUE component in use and will no longer listen for state changes once the component is uninstalled. Detached :true if we want $SUBSCRIBE to work even after the page is detached, we just give it the second parameter {detached:true} to keep it separate from the component, so it can continue to work after the component is detached:

export default {
  setup() {
    const userStore = useUserStore()

    // this subscription will be kept after the component is unmounted
    userStore.$subscribe(callback, { detached: true })
    // ...}}Copy the code

3.getters

  • Getters, like the computed option in VUE, relies on the property of state and returns a new value:

    import { defineStore } from "pinia";
    
    export const useUserStore = defineStore("user", {
      state: (a) :UserState= > {
        return {
          username: "".password: "".role: ""}; },getters: {
        // Pass in the argument state, destructible
        authorityLevel: ({ role }) = > {
          return role === "elder" ? 0 : role === "father" ? 1 : role === "son" ? 2 : null; }}});Copy the code

    The getters implementation receives parameters. It simply returns a function that receives parameters, and the getter is no longer cached.

    export const useUserStore = defineStore('user', {
      getters: {
        getUserById: (state) = > {
          return (userId) = > state.users.find((user) = > user.id === userId)
        },
      },
    })
    Copy the code

    We can pass the corresponding getter in the vue component:

    <script>
    import {useUserStore} from "@/store/userStore.js";
    export default {
      setup() {
        const store = useUserStore()
    
        return { getUserById: store.getUserById }
      },
    }
    </script>
    
    <template>
      <p>User 8: {{ getUserById(8) }}</p>
    </template>
    Copy the code
  • In a getter you can get the value of another getter, even the value of a getter or state in another store

    // cartStore.ts
    
    import { defineStore } from "pinia";
    import { useUserStore } from "./userStore";
    
    interface Goods {
      name: string,
      price: number,
      count: number,
    }
    
    type GoodsList = Goods[]
    
    interface CartState {
      items: GoodsList,
    }
    
    export const useCartStore = defineStore("cart", {
      state: (a) :CartState= > {
        return {
          items: []}},getters: {
        owner: () = > {
          // Get the state and getters of other stores
          const userStore = useUserStore();
          return ` name:${userStore.username}, permission level:${userStore.authorityLevel}`}}})Copy the code

4.actions

Actions is similar to the Methods option in VUE, which defines methods for changing state that can be manipulated asynchronously. Because there is no mutations option, you can change state directly in actions, greatly simplifying operations.

// src/store/userStore.ts

// Import and use defineStore to define the store
import { defineStore } from "pinia";

interface UserState {
  username: string;
  password: string;
  role: string;
}

export const useUserStore = defineStore("user", {
  state: (a) :UserState= > {
    return {
      username: "".password: "".role: ""}; },actions: {
    // Use this to refer to the store instance
    setUserInfo(username: string): void {
      this.username = username;
    },
    // Asynchronous operations can be performed
    setPassword(password: string): void {
      const timer = setTimeout(() = > {
        this.password = password;
        clearTimeout(timer);
      }, 1000);
    },
    setRole(role: string): void {
      this.role = role; ,}}});Copy the code
  • You can invoke your own or other store actions from actions:
import { defineStore } from "pinia";
import { useUserStore } from "./userStore";
export const useCartStore = defineStore("cart", {
  state: (a) :CartState= > {
    return {
      items: []}},getters: {
    owner: () = > {
      // Get the state and getters of other stores
      const userStore = useUserStore();
      return ` name:${userStore.username}, permission level:${userStore.authorityLevel}`}},actions: {
    // Use actions from other stores
    resetOwnerRole() {
      const userStore = useUserStore();
      userStore.setRole("elder");
    }
    // Use other actions in your store, using this to refer to the store instance
    callOtherAction(){
      this.resetOwnerRole()
    }
  },
})
Copy the code

Use store in setup

Here’s how to use the Store in the Composition API. Support for setup syntax sugar.

Similar to the useStore function in Vuex, Pinia provides a similar use. We import our custom Store function into the component’s script tag and assign it to the corresponding variable. State and getters are directly accessible, and you can use computed to make assigned variables responsive.

// xxx.vue

import { ref, computed, defineComponent } from "vue";
import { useUserStore } from "@/store/userStore";
export default defineComponent({
  setup() {
    const userStore = useUserStore(),
      // state
      username = computed(() = > userStore.username),
      // With computed, the password becomes responsive, but the username is not.
      password = computed(() = > userStore.password),
      // getters
      authority = computed(() = > userSore.authorityLevel)
    return {
      username,
      password,
      authority
    }
  }
})
Copy the code

Use store outside of setup

Note the timing of useStore, which can only be used after app mounts Pinia. Take routing guard as an example:

// src/router/index.ts

/ /! Invalid, an error will be reported that pinia is not installed yet
// const userStore = useUserStore();

router.beforeEach((to, from, next) = > {
  Vue has already mounted the router, so pinia is also mounted
  const userStore = useUserStore();
  to.path === "/about" && userStore.role === "" && next("/login");
  next();
});
Copy the code

Seven, summary

  1. Installation: NPM or YARN can be used for installation. Vuex is not selected during project creation.

  2. Mount: Create and mount Pinia in main.js.

  3. Definition: import and defineStore with defineStore in store/ mystore.js, pass a string as storeId, initialize state, getters, actions, export useMyStore function.

  4. state

  • State is a function that returns an object, similar to data in the Options API;

  • The behavior of modifying state is called mutation. There are no mutation options in Pinia, but there are several ways to modify state.

    • Access and modify the properties of the Store instance directly from Setup. The mutation type is ‘direct’.

    • Access and modify the state property using this in actions and invoke the appropriate action in setup. The mutation type is ‘direct’.

    • An object is passed in setup to modify state locally, via $patch() of the store instance. The mutation type is ‘patch object’.

    • In setup, via $patch() of the store instance, you pass in a function that returns an object to locally modify state. Mutation type belongs to ‘patch function’.

    • Setup resets state as a whole with $state in the Store instance, passing in a new object that contains all the properties of state. Mutation type belongs to ‘patch function’.

  • Listen for state changes: The store instance’s $SUBSCRIBE method monitors state changes and accepts a callback with mutation and state parameters. The mutation contains information about the modified state. The state parameter is the modified state. You can perform operations such as persistent storage of state here.

  1. getters
  • Getters is similar to vUE’s computed, getters relies on state and has a caching effect, receives state as a parameter, and accesses its attributes through state

  • Getters can use this to access other Getters in the store instance

  • Getters can access getters and state from other store instances. You need to import other stores

  • Getters can receive parameters by returning a function, but it loses its caching effect

  1. actions
  • Similar to methods in VUE, you can modify the value of state and perform asynchronous operations

  • You can use this to access getters, actions, and so on for this store instance

  • You can call actions from other stores, you need to import other stores

  1. Access store in Setup

Import the useMyStore function and instantiate the store to access its state, getters, and call actions or other apis.

  1. Use store in route guard

Need to be used inside the routing hook function (such as beforEach) when Pinia has been successfully mounted to the app instance. If used outside the routing hook function, an error is reported indicating that Pinia is not properly installed.

That’s the end of the Pinia hands-on experience. Pinia plugin and use in SSR. Those who are interested can refer to Pinia’s official documentation for detailed explanation.