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
- State: Used to store data, somewhat similar
data
The concept of; - Getters: used to get data, somewhat similar
computed
The concept of; - Actions: used to modify data, somewhat similar
methods
The concept of; - 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 one
state
Is required if synchronous updates are requiredmutations
In 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.
getters
Results are no longer cached like computed attributes - Vex4.x has some type safety issues
Modules without namespaces, or all stores are namespaces
advantage
- The complete
TypeScript
Support; Born with perfect type inference - Two syntax are supported for creating stores:
Options Api
和Composition Api
; - analogy
vuex 4.x
.Pinia
deletemutations
; 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 namespaces
Namespaced modules
Or 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;
defineStore
The 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 Api
和Composition Api
Perfect support from Both SidesThe 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} = useMain
There 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 want
stores
, 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, in
state
The 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
$patch
Method allows you to view partsstate
Object batch change - Or change it in the Action method
- You can do this by putting
store
的$state
Property to replace with a new objectstore
The whole state of;useMain.$state = { countPinia: 666 }
- You can change the overall state of your application
state
In thepinia
Instance.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:
- to
storage
(store) Add a new attribute; - When defining
storage
“, add a new option; - to
storage
Add new methods; - Existing methods of packaging;
- Change or even cancel actions;
- Implement side effects such as local storage;
- Applies only tospecific
storage
.
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 ~~