一、Pinia介绍
Pinia的设计主要是服务于Composite API(组合式API)
因为Vuex主要是为了vue2种的选项是API服务的所以,并不适用于Vue3。当Vue3伴随着组合式API来到时,Pinia的诞生也就成为了必然,一个全新的用于 Vue的状态管理库Pinia。
官方文档:https://pinia.vuejs.org/
中文文档:https://pinia.web3doc.top/
1.1 Pinia 核心特性
- Pinia 没有 Mutations
- Actions 支持同步和异步
- 没有模块的嵌套结构
- Pinia 通过设计提供扁平结构,就是说每个 store 都是互相独立的,谁也不属于谁,也就是扁平化了,更好的代码分割且没有命名空间。当然你也可以通过在一个模块中导入另一个模块来隐式嵌套 store,甚至可以拥有 store 的循环依赖关系
- 更好的 TypeScript 支持
- 不需要再创建自定义的复杂包装器来支持 TypeScript 所有内容都类型化,并且 API 的设计方式也尽可能的使用 TS 类型推断
- 不需要注入、导入函数、调用它们,享受自动补全,让我们开发更加方便
- 无需手动添加 store,它的模块默认情况下创建就自动注册的
- Vue2 和 Vue3 都支持
- 除了初始化安装和SSR配置之外,两者使用上的API都是相同的
- 支持 Vue DevTools
- 跟踪 actions, mutations 的时间线
- 在使用了模块的组件中就可以观察到模块本身
- 支持 time-travel 更容易调试
- 在 Vue2 中 Pinia 会使用 Vuex 的所有接口,所以它俩不能一起使用
- 但是针对 Vue3 的调试工具支持还不够完美,比如还没有 time-travel 功能
- 模块热更新
- 无需重新加载页面就可以修改模块
- 热更新的时候会保持任何现有状态
- 支持使用插件扩展 Pinia 功能
- 支持服务端渲染
1.2 Pinia和Vuex对比
二、快速入门
2.1 安装依赖
--npm init vite@latest
--npm install pinia
2.2 基础store
/**
* 注册容器
*/
import { defineStore } from "pinia"
// 定义容器
// 参数1: 容器id, 必须唯一, 可以理解为命名容器
// 参数2: 选项对象
export const useMainStore = defineStore('main', {
// 类似组件的data, 用来存储全局状态
// 注意:
// 1. 必须是一个函数【避免多个请求交叉污染】
// 2. 必须时箭头函数,方便TS的类型推断
state: () => {
return {
count: 0
}
},
// 类似组件的computed, 用来计算状态,也有缓存功能
// 参数1 就是stat而对象
getters: {
doubleCount: (state) => state.count * 2,
// 可以在getters中使用this,需手动标记getter的返回值类型
getCount() : number {
return this.count + 10
}
},
// 类似组件的methods,主要用来封装操作逻辑
actions: {
increment() {
this.count++
},
incrementA(a: number) {
this.count += a
}
}
})
<template>
<h2>count计算</h2>
<p>{{ mainStore.count }}</p>
<p>
<button @click="handleChangeState">修改state</button>
</p>
</template>
<script setup lang="ts">
import { useMainStore } from '../store'
// 调用得到容器对象
const mainStore = useMainStore()
const handleChangeState = () => {
// 方式一:直接修改state
mainStore.count++
// 重置状态
mainStore.$reset()
// 方式二:批量更新
mainStore.$patch({
count: mainStore.count + 1
})
// 接收函数
mainStore.$patch((state) => {
state.count += 1
})
// 方法三:action中修改
// Vuex中调用mutation先commit,再dispatch
// action同步异步都支持
mainStore.increment()
}
</script>
2.3 Demo场景
1)案例场景
案例场景:https://github.com/vuejs/vuex/tree/main/examples
购物车场景:
2)业务api
export interface IProduct {
id: number
title: string
price: number
inventory: number
}
const _products: IProduct[] = [
{ 'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01, 'inventory': 2 },
{ 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99, 'inventory': 10 },
{ 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 19.99, 'inventory': 5 }
]
export const getProducts = async () => {
await wait(100)
return _products
}
export const buyProducts = async () => {
await wait(100)
return Math.random() > 0.5
}
const wait = async (delay:number) => {
return new Promise((resolve) => setTimeout(resolve, delay))
}
3)store场景
import { defineStore } from "pinia";
import { IProduct, buyProducts } from "../api/shop";
import { useProductsStore } from "./products";
type CartProduct = {
quantity: number
} & Omit<IProduct, 'inventory'>
export const useCartStore = defineStore('cart', {
state: () => {
return {
cartProducts: [] as CartProduct[],
checkoutStatus: null as null | string
}
},
getters: {
totalPrice (state) {
return state.cartProducts.reduce((total, product) => {
return total + product.price * product.quantity
}, 0)
}
},
actions: {
addProductToCart(product : IProduct) {
// 商品是否有库存
if (product.inventory <= 0) {
return
}
// 购物车是否有该商品
const cartProduct = this.cartProducts.find(item => item.id === product.id)
if (cartProduct) {
cartProduct.quantity++
} else {
this.cartProducts.push({
...product,
quantity: 1
})
}
// 更新下商品的库存
const productsStore = useProductsStore()
productsStore.decrementProductInventory(product)
},
async checkout() {
this.checkoutStatus = null
const res = await buyProducts()
res ? this.checkoutStatus = '成功' : this.checkoutStatus = '失败'
}
}
})
import { defineStore } from "pinia"
import { IProduct, getProducts } from "../api/shop"
export const useProductsStore = defineStore('products', {
state: () => {
return {
all: [] as IProduct[]
}
},
getters: {},
actions: {
async loadAllProducts() {
const res = await getProducts()
this.all = res
},
async decrementProductInventory(product: IProduct) {
const result = this.all.find(item => item.id === product.id)
if (result) {
result.inventory--
}
}
}
})
4)业务场景
<template>
<ul>
<li v-for="product in all" :key="product.id">
{{ product.title }} - {{ product.price }}
<button
:disabled="!product.inventory"
@click="cartStore.addProductToCart(product)"
>添加购物车</button>
</li>
</ul>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useProductsStore } from './../store/products'
import { useCartStore } from './../store/cart'
const productsStore = useProductsStore()
const cartStore = useCartStore()
// 异步加载更新数据
productsStore.loadAllProducts()
// 数据解构
// 这种写法存在问题,只会拿到第一次的数据,之后非响应式
// 提供storeToRefs api , 被ref包裹
const { all } = storeToRefs(productsStore)
console.log(all.value) // ref结构。需要.value才能拿到数据
</script>
<template>
<div class="cart">
<h2>购物车</h2>
<ul>
<li v-for="product in cartStore.cartProducts" :key="product.id">
{{ product.title }} - {{ product.price }} x {{ product.quantity }}
</li>
</ul>
<p> 总价:{{ cartStore.totalPrice }}</p>
<button @click="cartStore.checkout">结算</button>
<p v-show="cartStore.checkoutStatus"> 结算结果 {{ cartStore.checkoutStatus }}</p>
</div>
</template>
<script setup lang="ts">
import { useCartStore } from './../store/cart'
const cartStore = useCartStore()
</script>