1.介绍
Pinia
最初是一个实验,目的是在2019年11月左右重新设计Vue状态管理 Composition API
上的样子,也就是下一代Vuex
- 之前的Vuex主要服务于
Vue2
,选项式API
- 如果想要在
Vue3
中使用Vuex
,需要使用Vuex4.0
版本 - 所以在
Vue3
伴随着组合式API诞生之后,也设计了全新的Vuex:Pinia,也就是Vuex 5.0
- 在
pinia
中去除了mutations
配置项只保留了actions
2.安装
yarn add pinia
# 或者使用 npm
npm install pinia
3.基本使用
1.创建pinia示例并挂载
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
// 创建pinia实例
const pinia = createPinia()
// 挂载到根实例上
createApp(App).use(pinia).mount('#app')
2.创建一个pinia
import { defineStore } from "pinia"
// 1.定义容器
/**
* 参数1:容器的ID,必须唯一,pinia会把所有的容器挂载到根容器上
* 参数2:配置项
*/
export const useMainStore = defineStore('main', {
/**
*
* state:类似vue2中的data函数
* 1. 必须是函数,这样是为了在服务端渲染的时候避免状态污染
* 2. 必须是箭头函数
*/
state: () => {
return {
sum: 0
}
},
/**
* 类似计算属性跟vuex中的getters一样
*/
getters: {
},
/**
* 业务逻辑处理,修改state中的数据
*/
actions: {
}
})
3.App.vue中测试一下
<template>
<div>
<h2> mainStore.sum:{{ mainStore.sum }}</h2>
<h2>sum:{{ sum }}</h2>
<button @click="handleClick">修改数据</button>
</div>
</template>
<script setup lang="ts">
import { useMainStore } from '../src/store/index'
import { storeToRefs } from 'pinia';
const mainStore = useMainStore()
/**
* const { sum } = mainStore
* 直接解构会丢失数据的响应式
*/
//使用storeToRefs进行解构,这样的数据是响应式的
const { sum } = storeToRefs(mainStore)
const handleClick = () => {
mainStore.sum++ //可以成功修改
}
</script>
4. 修改state中的数据
//页面中的配置
const handleClick = () => {
//方式1:最简单的方式
mainStore.sum++ //可以成功修改
//方式2:使用$patch批量修改
mainStore.$patch({
sum: mainStore.sum + 1
})
//方式3:$patch传入一个函数,接受state为参数,批量更新
mainStore.$patch(state => {
state.sum++
})
//方式4:使用action修改,直接调用action中定义的函数
mainStore.changeState(1)
}
// actions 中的配置
actions:{
changeState(val:number) {
this.sum+=val
}
}
5. getters
getters: {
sumBig(state) {
/*
state和this是相同的
getters使用方式就跟vue2中的计算属性一样
*/
console.log('state === this :>> ', state === this); //true
return state.sum * 10
}
},
// App.vue内
import { useMainStore } from '../src/store/index'
import { storeToRefs } from 'pinia';
const mainStore = useMainStore()
const { sum ,sumBig } = storeToRefs(mainStore)
6. 完整代码
index.ts中
import { defineStore } from "pinia"
// 1.定义容器
/**
* 参数1:容器的ID,必须唯一,pinia会把所有的容器挂载到根容器上
* 参数2:配置项
*/
export const useMainStore = defineStore('main', {
/**
*
* state:类似vue2中的data函数
* 1. 必须是函数,这样是为了在服务端渲染的时候避免状态污染
* 2. 必须是箭头函数
*/
state: () => {
// 2.使用容器中的state
return {
sum: 0
}
},
/**
* 类似计算属性跟vuex中的getters一样
*/
getters: {
sumBig(state) {
console.log('state === this :>> ', state === this); //true
return state.sum * 10
}
},
/**
* 业务逻辑处理,修改state中的数据
*/
actions: {
// 3.修改state
changeState(val: number) {
this.sum += val
}
}
})
App.vue中
<template>
<div>
<h2>App组件</h2>
<h2> mainStore.sum:{{ mainStore.sum }}</h2>
<h2>sum:{{ sum }}</h2>
<h2>sumBig :{{ sumBig }}</h2>
<button @click="handleClick">修改数据</button>
<hr>
<Demo></Demo>
</div>
</template>
<script setup lang="ts">
import Demo from './components/Demo.vue'
import { useMainStore } from '../src/store/index'
import { storeToRefs } from 'pinia';
const mainStore = useMainStore()
/**
* const { sum } = mainStore
* 直接解构会丢失数据的响应式
*/
//使用storeToRefs进行解构
const { sum ,sumBig } = storeToRefs(mainStore)
const handleClick = () => {
//方式1:最简单的方式
// mainStore.sum++ //可以成功修改
//方式2:使用$patch批量修改
// mainStore.$patch({
// sum: mainStore.sum + 1
// })
//方式3:$patch传入一个函数,接受state为参数,批量更新
// mainStore.$patch(state => {
// state.sum++
// })
//方式4:使用action修改,直接调用action中定义的函数
mainStore.changeState(1)
}
</script>
<style scoped>
</style>
4.购物车小案例
页面模板
/src/App.vue
<template>
<div id="app">
<h1>Shopping Cart Example</h1>
<hr>
<h2>Products</h2>
<ProductList/>
<hr>
<ShoppingCart/>
</div>
</template>
<script lang="ts" setup>
import ProductList from './components/ProductList.vue'
import ShoppingCart from './components/ShoppingCart.vue'
</script>
src\components\ProductList.vue
<template>
<ul>
<li v-for="product in all" :key="product.id">
{{ product.title }} - {{ product.price | currency }}
<button :disabled="!product.inventory" @click="addProductToCart(product)">
加入购物车
</button>
</li>
</ul>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useProductStore } from '../store/products'
import { useCartStore } from '../store/cart'
const ProductStore = useProductStore()
const CartStore = useCartStore()
const { all } = storeToRefs(ProductStore)
const currency: number = 0
//从接口中获取所有的商品信息数据
ProductStore.loadAllProducts()
const addProductToCart = (product: any) => {
CartStore.addProductToCart(product)
}
</script>
src\components\ShoppingCart.vue
<template>
<div class="cart">
<h2>Your Cart</h2>
<p>
<i>请添加一些商品到购物车</i>
</p>
<ul>
<li v-for="cartItem in cartProducts" :key="cartItem.id">
商品名称:{{ cartItem.title }} - 商品价格:{{ cartItem.price | 0 }} - 商品数量:{{ cartItem.quantity }}
</li>
</ul>
<p>商品总价: {{ CartStore.totalPrice }}</p>
<p><button @click="CartStore.checkOut">结算</button></p>
<p v-show="CartStore.checkOutStatus">结算 【{{ CartStore.checkOutStatus }}】!!!</p>
</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useCartStore } from '../store/cart'
const CartStore = useCartStore()
const { cartProducts } = storeToRefs(CartStore)
</script>
模拟数据
src/api/shop.ts
//定义接口
export interface IProduct {
id: number,
title: string,
price: number,
inventory: number //库存
}
//泛型数组 约束数组中每个对象
const _products: IProduct[] = [
{ id: 1, title: 'iphone 14', price: 900, inventory: 5 },
{ id: 2, title: 'iphone 13', price: 1000, inventory: 5 },
{ id: 3, title: 'iphone 12', price: 2000, inventory: 5 },
]
//获取所有商品数据
export const getProducts = async () => {
await wait(100)
return _products
}
//模拟结算
export const buyProducts = async () => {
await wait(100)
return Math.random() > 0.5
}
//没啥用的延迟效果
async function wait(delay: number) {
return new Promise((reslove) => setTimeout(reslove, delay))
}
定义store数据
src\store\products.ts
import { defineStore } from 'pinia';
import { getProducts, IProduct } from '../api/shop'
export const useProductStore = defineStore('products', {
state: () => {
return {
all: [] as IProduct[], // 类型推导
}
},
getters: {
},
actions: {
async loadAllProducts() {
const res: IProduct[] = await getProducts()
this.all = res
},
decrementProduct(product: IProduct) {
//如果商品已经在购物车内那么就数量-1
const res = this.all.find(item => item.id === product.id)
if (res) {
res.inventory--
}
}
}
})
src\store\cart.ts
import { IProduct, buyProducts } from './../api/shop';
import { defineStore } from 'pinia';
import { useProductStore } from '../store/products'
type CartProduct = {
quantity: number,
} & Omit<IProduct, 'inventory'>
//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, item) => {
return total + (item.quantity * item.price)
}, 0)
}
},
actions: {
//添加
addProductToCart(product: IProduct): void {
if (product.inventory < 1) {
return
}
const cartItem = this.cartProducts.find((item) => item.id === product.id)
if (cartItem) { //如果商品重复了 那么就数量+1
cartItem.quantity++
} else {
this.cartProducts.push({
id: product.id,
title: product.title,
price: product.price,
quantity: 1 //初次加入购物车的商品数量为1
})
}
const ProductStore = useProductStore()
ProductStore.decrementProduct(product)
},
//结算
async checkOut() {
const res = await buyProducts()
this.checkOutStatus = res ? '成功' : '失败'
}
}
})