This article is an original article, please quote the source, welcome everyone to collect and share πŸ’πŸ’

The opening

If you are using Vue ecological development projects, you should have heard about Pinia status management library or you are using Pinia status management library. If you have not come into contact with Pinia, this article can help you quickly start, and how to use more elegant encapsulation in enterprise projects.

This article first explains how to understand, use Pinia, and finally how to integrate Pinia into the project, suitable for most readers, as for reading Pinia source code and other advanced popular science, will open another article details. In addition, all the demo of this article has been specially opened a GitHub project to save, students who need to take it down to practice. 🌹 🌹

Know Pinia

Pinia, pronounced ‘PI ːnΙ™, is a lightweight status management library recommended by the official Vue team to replace Vuex. The Vue Store was originally designed to have a state management library in the form of a Composition API and support both the Option API of VE2. X and the Setup Composition API of Vue3. It is fully compatible with Typescript writing (which is one of the key advantages over Vuex) for all VUE projects.

Compared to Vuex, Pinia has the following advantages:

  • Full TypeScript support: Adding TypeScript is easier than adding TypeScript to Vuex
  • Extremely lightweight (about 1KB)
  • Store actions are scheduled as regular function calls, rather than using the Dispatch method or MapAction helper functions, which is common in Vuex
  • Support for multiple stores
  • Support Vue DevTools, SSR and Webpack code splitting

Pinia and Vuex code splitting mechanism

Part of Pinia’s lightness is reflected in its code splitting mechanism.

For example, a project has three stores “User, job and Pay”, and two routing pages “home page and personal center page”. Job Store is used in the home page, and User Store is used in the personal center page. Pinia and Vuex are used to manage their state respectively.

Let’s take a look at the Vuex code split: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, and finally output one JS chunk. 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 code segmentation:When packing, Pinia will check the reference dependency. When the home page uses the Job Store, the packing will only combine the store and the page to output one JS chunk, and the other two 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.

The conventional use of Pinia

Without further ado, go straight to Pinia. “This article uses Vue3’s Setup Composition API development mode by default.”

If you are familiar with Pinia, you can skip this partπŸ‘»

1. Install

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

2. Mount the global instance

import { createPinia } from 'pinia'

app.use(createPinia())
Copy the code

Create the first Store

In the SRC/store/counterForOptions. Ts to create your store. There are two ways to define the Store schema:

  • 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.

    import { defineStore } from 'pinia';
    
    // Use the Options API schema definition
    export const useCounterStoreForOption = defineStore('counterForOptions', {
      / / define the state
      state: () = > {
        return { count1: 1 };
      },
      / / define the action
      actions: {
        increment() {
          this.count1++; }},/ / define getters
      getters: {
        doubleCount(state) {
          return state.count1 * 2; }}});Copy the code
  • Using the SETUP mode definition, which conforms to the programming mode of Vue3 Setup, makes the structure more flat. I recommend using this method.

    import { ref } from 'vue';
    import { defineStore } from 'pinia';
    
    // Use the setup mode definition
    export const useCounterStoreForSetup = defineStore('counterForSetup'.() = > {
      const count = ref<number> (1);
      function increment() {
        count.value++;
      }
    
      function doubleCount() {
        return count.value * 2;
      }
    
      return { count, increment, doubleCount };
    });
    Copy the code

The above two definitions have the same effect. We defineStore a store, which defines 1 count state, 1 increment Action and 1 doubleCount getters respectively. Where state is the global state to be shared, action is the entry for the business to call to change state, and getters is to get the calculation result of state.

In the options API mode, mapState(), mapActions() and other methods can be used to obtain the corresponding items. But the second method can be obtained directly (more on that below).

4. Calls to Store by business components

In the SRC/components/PiniaBasicSetup. Vue directory to create a component.

<script setup lang="ts" name="component-PiniaBasicSetup">
import { storeToRefs } from 'pinia';
import { useCounterStoreForSetup } from '@/store/counterForSetup';

// Setup Composition API mode
const counterStoreForSetup = useCounterStoreForSetup();
const { count } = storeToRefs(counterStoreForSetup);
const { increment, doubleCount } = counterStoreForSetup;
</script>

<template>
  <div class="box-styl">
    <h1>The Setup mode</h1>
    <p class="section-box">Pinia's state: count =<b>{{ count }}</b>
    </p>
    <p class="section-box">Pinia's getters: doubleCount() =<b>{{ doubleCount() }}</b>
    </p>
    <div class="section-box">
      <p>Pinia action: increment ()</p>
      <button @click="increment">Am I</button>
    </div>
  </div>
</template>

<style lang="less" scoped>
  .box-styl {
    margin: 10px;
    .section-box {
      margin: 20px auto;
      width: 300px;
      background-color: #d7ffed;
      border: 1px solid # 000; }}</style>
Copy the code
  • Pinia’s call mechanism in Setup mode is install and then call.
  • Install writes:const counterStoreForSetup = useCounterStoreForSetup();, includinguseCounterStoreForSetupThat’s the variable that you define store;

  • The call is used directlycounterStoreForSetup.xxx(XXX includes: state, getters, Action).
  • In order to preserve the responsive nature of state, we need to usestoreToRefsWrap up.

The vue2-compatible Options API calls can be made here.

5. Good programming habits

State changes are handled by the action: in the example above, counterStoreForSetup has a pinia instance property called $state that can directly change the value of state, but it is not recommended to do so. First, it is difficult to maintain. In the case of various components, a hidden state change can be checked by the whole development team. The other is to break store encapsulation and make it difficult to port to other places. So, for the sake of your reputation and safety, please stop coding outside of dissociation πŸ˜‡πŸ˜‡.

Replace pinia instance attributes with hooks: The counterStoreForSetup object after install has a number of $methods, most of which can be replaced by hooks.

Other thought to add…

Enterprise project encapsulation walkthrough

1. Global registration machine

Double packing problem

As you can see from the above example, when using store, import the store definition, and then execute the definition function to instantiate it. But as the project grows larger, does every component need to be instantiated for use? As mentioned at the beginning of this article, Pinia’s code splitting mechanism combines and packages pages that reference it, which can be problematic in examples like the following, where user is referenced by multiple pages and the User Store is repeatedly packaged.To solve this problem, we can introduceGlobal RegistrationThe concept of. The practice is as follows:

Create a master entry

SRC /store create an entry named index.ts, which contains a registerStore() function. RegisterStore () is used to register the entire project’s stores in advance, and finally to upload all store instances to the appStore. In the future, whenever we want to use Pinia in any component of the project, we just import appStore and get the corresponding store instance.

// src/store/index.ts
import { roleStore } from './roleStore';
import { useCounterStoreForSetup } from '@/store/counterForSetup';
import { useCounterStoreForOption } from '@/store/counterForOptions';

const appStore: any = {};

/** * Register app status library */
export const registerStore = () = > {
  appStore.roleStore = roleStore();
  appStore.useCounterStoreForSetup = useCounterStoreForSetup();
  appStore.useCounterStoreForOption = useCounterStoreForOption();
};

export default appStore;
Copy the code

Bus registered

Perform registration on the SRC /main.ts project bus:

import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
import { registerStore } from '@/store';

const app = createApp(App);

app.use(createPinia());
// Register pinia status management library
registerStore();

app.mount('#app');
Copy the code

Used directly within business components

// src/components/PiniaBasicSetup.vue
<script setup lang="ts" name="component-PiniaBasicSetup">
import { storeToRefs } from 'pinia';
import appStore from '@/store';

// Setup Composition API mode
const { count } = storeToRefs(appStore.useCounterStoreForSetup);
const { increment, doubleCount } = appStore.useCounterStoreForSetup;
</script>

<template>
  <div class="box-styl">
    <h1>The Setup mode</h1>
    <p class="section-box">Pinia's state: count =<b>{{ count }}</b>
    </p>
    <p class="section-box">Pinia's getters: doubleCount() =<b>{{ doubleCount() }}</b>
    </p>
    <div class="section-box">
      <p>Pinia action: increment ()</p>
      <button @click="increment">Am I</button>
    </div>
  </div>
</template>
Copy the code

Packaging decoupling

In order to decoupled the appStore instance from the project, the appStore should be extracted to the public chunk during construction, and the following configuration should be done in viet.config. ts

export default defineConfig(({ command }: ConfigEnv) = > {
  return {
    / /... Other configuration
    
    build: {
      / /... Other configuration
      
      rollupOptions: {
        output: {
          manualChunks(id) {
            // Package pinia's global library instances into Vendor to avoid repeated resource imports caused by packaging with pages
            if (id.includes(path.resolve(__dirname, '/src/store/index.ts'))) {
              return 'vendor'; }}}}}}; });Copy the code

After this encapsulation, the Pinia state library is decoupled and the final project structure looks like this:

2. Store group management

Scenario analysis

How often do you find yourself in a project where a method updates multiple stores? For example, if you want to make a game, there are three classes “warrior, mage, Taoist”, in addition, the player character has three stores to control “character attributes, equipment, skills”, the page has a “transfer” button, you can transfer to other classes. When a player changes classes, the state of each store changes. How do you do that?

  • Method 1: Create a function in the business component and click “Transfer” to get the three stores and update their values.
  • Method 2: Abstract a new Pinia Store. The store has an action that says “change jobs”. When the player changes jobs, the action updates the values of the three stores.

In contrast, approach 2 is clearly better, both in terms of encapsulation and business decoupling. To achieve this, pinia also benefits from the independent store management feature. We only need to take the abstract store as the parent store, and the three stores of “character attributes, equipment and skills” as the unit store, and let the action of the parent store manage its own unit store.

Create a group-level Store

Go to a talent to the parent store: SRC/store/roleStore/index. Ts

import { defineStore } from 'pinia';
import { roleBasic } from './basic';
import { roleEquipment } from './equipment';
import { roleSkill } from './skill';
import { ROLE_INIT_INFO } from './constants';

type TProfession = 'warrior' | 'mage' | 'warlock';

// Role group, which integrates three stores of "character attributes, equipment and skills" for unified management
export const roleStore = defineStore('roleStore'.() = > {
  // Register group stores
  const basic = roleBasic();
  const equipment = roleEquipment();
  const skill = roleSkill();

  / / to turn professional
  function changeProfession(profession: TProfession) {
    basic.setItem(ROLE_INIT_INFO[profession].basic);
    equipment.setItem(ROLE_INIT_INFO[profession].equipment);
    skill.setItem(ROLE_INIT_INFO[profession].skill);
  }

  return { basic, equipment, skill, changeProfession };
});
Copy the code

Unit Store

3 unit stores:

  • Character attributes
  • equipment
  • skills

Business component invocation

<script setup lang="ts" name="component-StoreGroup">
import appStore from '@/store';
</script>

<template>
  <div class="box-styl">
    <h1>Store group management</h1>
    <div class="section-box">
      <p>Current Occupation:<b>{{ appStore.roleStore.basic.basic.profession }}</b>
      </p>
      <p>Name:<b>{{ appStore.roleStore.basic.basic.name }}</b>
      </p>
      <p>Gender:<b>{{ appStore.roleStore.basic.basic.sex }}</b>
      </p>
      <p>Equipment:<b>{{ appStore.roleStore.equipment.equipment }}</b>
      </p>
      <p>Skills:<b>{{ appStore.roleStore.skill.skill }}</b>
      </p>
      <span>To change your job:</span>
      <button @click="appStore.roleStore.changeProfession('warrior')">A warrior</button>
      <button @click="appStore.roleStore.changeProfession('mage')">The mage</button>
      <button @click="appStore.roleStore.changeProfession('warlock')">Taoist</button>
    </div>
  </div>
</template>

<style lang="less" scoped>
.box-styl {
  margin: 10px;
  .section-box {
    margin: 20px auto;
    width: 300px;
    background-color: #d7ffed;
    border: 1px solid # 000; }}</style>
Copy the code

The effect

ending

For a project, a good state management scheme plays an important role in it. It can not only make the project clear, but also facilitate the future maintenance and iteration of the project.

Item portal

Finally, all the demos in this article have been saved by a GitHub project. If you need it, you can take it down and practice it. 🌹 🌹