1、创建项目(vite+vue+ts)
pnpm create vite
2、安装pinia
yarn add pinia
3、使用
1、引入注册main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
const app=createApp(App)
app.use(pinia).mount('#app')
2、初始化仓库Store(存储是使用定义的defineStore()
,并且它需要一个唯一的名称,作为第一个参数传递)
新建store/main.ts
import { defineStore } from 'pinia'
export const mainStore = defineStore('main',{
state:()=>{
return {
count:100,
price:250
}
},
getters:{
doubleCount():number{
return this.count*2
},
doublePrice():number{
return this.price*2
}
},
actions:{
changeStoreData(){
this.count+=1
this.price+=1
},
}
})
3、页面使用
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia';
import { mainStore } from '../store/main';
const store = mainStore()
let { count,doubleCount,price,doublePrice } = storeToRefs(store)
defineProps<{ msg: string }>()
const changeData = ()=>{
//1、直接修改count
// count.value++
// price.value++
//2、$patch批量修改State的值
// store.$patch({
// count:500,
// price:1000
// })
//3、$patch批量修改函数形式
// store.$patch((state)=>{
// state.count++
// state.price++
// })
//4、通过原始对象修改整个实例
//$state您可以通过将store的属性设置为新对象来替换store的整个状态
//缺点就是必须修改整个对象的所有属性
// store.$state={
// count:0,
// price:1
// }
//5、通过actions修改
store.changeStoreData()
}
</script>
<template>
<h1>{{ msg }}</h1>
<h1>数量</h1>
<h2>pinia-count数据{{count}}</h2>
<h3>pinia- getters-2倍数量{{doubleCount}}</h3>
<h1>价格</h1>
<h2>pinia-price数据{{price}}</h2>
<h3>pinia- getters-2倍价格{{doublePrice}}</h3>
<el-button size='large' type="primary" @click="changeData">改变数据</el-button>
</template>
<style scoped>
</style>
4、actions(异步及调用其他actions方法)
新建store/use.ts模块
import { defineStore } from 'pinia'
import { ElLoading } from 'element-plus'
type Res = {
nickname:string,
age:number
}
const Login = ()=>{
return new Promise<Res>((resolve,reject)=>{
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
setTimeout(() => {
resolve({
nickname:'张三',
age:22
})
loading.close()
}, 5000);
})
}
export const userStore = defineStore('user',{
state:()=>{
return {
nickname:'',
age:0
}
},
getters:{
},
actions:{
async getUserInfo(){
const res = await Login();
this.nickname=res.nickname;
//this.age=res.age;
//调用其他actions
this.setAge(res.age)
},
setAge(age:number){
this.age=age
}
}
})
<template>
<h1>用户组件</h1>
<h2>昵称{{nickname}}--年龄{{age}}</h2>
<el-button type="primary" size="large" @click="getUserInfo">获取用户信息</el-button>
</template>
<script setup lang='ts'>
import { ref,reactive} from 'vue'
import { userStore } from '../store/user';
import { storeToRefs } from 'pinia';
const user = userStore()
let {nickname,age} = storeToRefs(user)
const getUserInfo = ()=>{
user.getUserInfo()
}
</script>
<style lang='scss' scoped>
</style>
5、getters (主要作用类似于computed 数据修饰并且有缓存)
getters 可以互相调用 普通函数形式可以使用this 使用箭头函数不能使用this this指向已经改变指向undefined 修改值请用state
getters:{
doubleCount():number{
//return this.count*2
return this.doublePrice*2
},
doublePrice:(state)=>state.price*2
},
6、 常用api
1、$reset(重置store
到他的初始状态)
<el-button size='large' type="primary" @click="resetData">重置数据</el-button>
const resetData =()=>{
main.$reset()
}
2、订阅state改变(类似于Vuex 的abscribe 只要有state 的变化就会走这个函数)
main.$subscribe((args,state)=>{
console.log(args,state,'数据改变')
})
第二个参数
如果你的组件卸载之后还想继续调用请设置第二个参数
main.$subscribe((args,state)=>{
console.log(args,state,'数据改变')
},{
detached:true
})
3、订阅Actions的调用(只要有actions被调用就会走这个函数)
main.$onAction((args)=>{
console.log(args,'action调用');
})
7、 pinia插件 --数据持久化
1、安装
cnpm i pinia-plugin-persist --save
2、新建store/index.ts
import { createPinia } from 'pinia'
//pinia 持久化插件
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia()
store.use(piniaPluginPersist)
export default store
3、修改main.ts
import { createApp } from 'vue'
import App from './App.vue'
import 'element-plus/dist/index.css'
import store from './store/index';
import piniaPluginPersist from 'pinia-plugin-persist';
store.use(piniaPluginPersist)
const app=createApp(App)
app.use(store).mount('#app')
4、保存nickname,修改store/user.ts
import { defineStore } from 'pinia'
import { ElLoading } from 'element-plus'
type Res = {
nickname:string,
age:number
}
const Login = ()=>{
return new Promise<Res>((resolve,reject)=>{
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
setTimeout(() => {
resolve({
nickname:'张三',
age:22
})
loading.close()
}, 5000);
})
}
export const userStore = defineStore('user',{
state:()=>{
return {
nickname:'',
age:0
}
},
getters:{
},
actions:{
async getUserInfo(){
const res = await Login();
this.nickname=res.nickname;
//this.age=res.age;
//调用其他actions
this.setAge(res.age)
},
setAge(age:number){
this.age=age
}
},
persist:{
enabled:true,
strategies:[
{
storage:localStorage,paths:['nickname']
}
]
}
})
4、购物车案例
1、新建src/api/shop.ts
/**
* src/api/shop.ts
* Mocking client-server processing
*/
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(1000)
return _products
}
export const buyProducts = async () => {
await wait(1000)
return Math.random() > 0.5
}
/**
* 封装了Promise版本的定时器
* @param delay 延迟时间
* @returns Promise
*/
async function wait(delay:number) {
return new Promise(resolve => setTimeout(resolve, delay))
}
2、新建components/ProductList.vue
<template>
<ul>
<li class="item" v-for="item in productsStore.all">
<div class="title">商品名称:{{item.title}}</div>--
<div class="price">商品价格:{{item.price}}</div>
<div class="price">商品库存:{{item.inventory}}</div>
<el-button type="primary" :disabled="!item.inventory" @click="cartStore.addProductToCart(item)">添加到购物车</el-button>
</li>
</ul>
</template>
<script lang="ts" setup>
import { useCartStore } from '../store/cart';
import { useProdunctsStore } from '../store/products'
const productsStore = useProdunctsStore()
const cartStore = useCartStore()
productsStore.loadAllProducts() // 加载所有数据
</script>
<style lang="scss" scoped>
.item{
display: flex;
align-items: center;
justify-content: center;
.title{
margin: 0 15px;
}
.price{
margin: 0 15px;
}
}
</style>
3、新建components/Cart.vue
<template>
<div class="cart">
<h2>我的购物车</h2>
<ul v-if="cartProducts&&cartProducts.length>0">
<li v-for="item in cartProducts">商品名称:=:{{ item.title }} - 商品价格:{{ item.price }} × 商品数量:{{ item.num }}</li>
</ul>
<p v-else>
<i>请添加一些商品到购物车</i>
</p>
<p>商品总价: {{ totalPrice }}</p>
</div>
</template>
<script lang="ts" setup>
import { useCartStore } from '../store/cart'
import { storeToRefs } from 'pinia';
const cartStore = useCartStore()
let { cartProducts,totalPrice } = storeToRefs(cartStore)
</script>
4、组件在App.vue中引用
<script setup lang="ts">
import ProductList from './components/ProductList.vue';
import Cart from './components/Cart.vue';
</script>
<template>
<ProductList />
<hr>
<Cart />
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
ul li{
list-style: none;
line-height: 50px;
}
</style>
5、新建store/products.ts
import { defineStore } from "pinia"
import { getProducts, IProduct } from "../api/shop"
export const useProdunctsStore = defineStore('products', {
state: () => {
return {
all: [] as IProduct[] // 所有商品列表(学习类型断言的使用)
}
},
getters: {},
actions: {
//actions既支持异步操作
async loadAllProducts() {
const ret = await getProducts()
this.all = ret
},
// 也支持同步操作
decrementProduct(product: IProduct) {
const ret = this.all.find(item => item.id === product.id)
if (ret) {
ret.inventory--
}
}
}
})
6、新建store/cart.ts
import { defineStore } from "pinia";
import { buyProducts, IProduct } from "../api/shop";
import { useProdunctsStore } from "./products";
/**
* {id, title, price, quantity}
*/
type CartProduct = { // 合并
num: number
} & Omit<IProduct, 'inventory'> // Omit是过滤
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.price * item.num
}, 0)
}
},
actions: {
/**
* 往购物车添加商品
* 这是一个相对复杂的逻辑,与容器中的数据强相关,所以肯定要定义在actions里面!
* @param product 需要添加的商品
*/
addProductToCart(product: IProduct) {
// 先看商品有没有库存
if (product.inventory <= 0) {
return
}
// 检查购物车中是否已有该商品
const cartItem = this.cartProducts.find(item => item.id === product.id)
// 如果有则让商品数量+1
if (cartItem) {
cartItem.num++
} else {
// 如果没有则添加到购物车列表中
this.cartProducts.push({
id: product.id,
title: product.title,
price: product.price,
num: 1
})
}
// 跟新商品库存(应该由商品容器暴露API)
const productsStore = useProdunctsStore()
productsStore.decrementProduct(product)
// 跨容器通信!!!!!竟然如此简单!!!
},
/**
* 结算
*/
async checkOut() {
const ret = await buyProducts()
this.checkoutStatus = ret ? '成功' : '失败'
if (ret) {
this.cartProducts = [] // 清空购物车
}
}
}
})