官网:https://pinia.vuejs.org/
牛逼教程:https://www.bilibili.com/video/BV11Y411b7nb
一、环境初始化
1.创建项目
$ yarn create vite
2.安装pinia
$ yarn add pinia
二、基本使用
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.基本使用
import { defineStore } from "pinia"// 1.定义并导出容器// 参数1:容器的ID,必须唯一,将来Pinia会把所有的容器挂载到根容器,每个容器的名字就是这里的IDexport const useMainStore = defineStore('main', {/*** 类似与组件的data, 用来存储全局状态* 1.必须是函数:这样是为了在服务端渲染的时候避免交叉请求导致的数据状态污染(客户端其实无所谓)* 2.必须是箭头函数:为了更好的ts类型推导* 返回值:一个函数,调用该函数即可得到容器实例*/state: () => {return {count: 100,foo: 'bar',arr: [1, 2, 3]}},/*** 类似于组件的computed,用来封装计算属性,有【缓存】功能*/getters: {// 每个函数接受一个可选参数:state状态对象// count10(state) {// console.log('count10()调用了');// 具有缓存功能// return state.count + 10// }// (不建议)如果不使用state而使用this,此时就不能对返回值类型做自动推导了,必须手动指定count10(): number {return this.count + 10}},/*** 完全类比于Vue2组件中的methods(可以直接用this),用来【封装业务逻辑】,修改state*/actions: {/*** 注意!!不能使用箭头函数定义actions!!一定要用普通函数!!!* why?因为箭头函数绑定了外部this*/changeState(num: number) {// 可以直接使用this,像极了Vue2// this.count++// this.foo = 'hello'// this.arr.push(4)// 对于批量修改,建议使用patch做优化this.$patch((state) => {state.count += numstate.foo = 'hello'state.arr.push(4)})}}})// 2.容器中的state// 3.修改state// 4.actions的使用
打开App.vue,砍掉没用的,我们直接使用项目中HelloWorld.vue组件
<script setup lang="ts">import HelloWorld from './components/HelloWorld.vue'</script><template><HelloWorld /></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;}</style>
下面是HelloWorld.vue的内容
<template><p>{{ mainStore.count }}</p><p>{{ mainStore.foo }}</p><p>{{ mainStore.arr }}</p><p>{{ mainStore.count10 }}</p><p>{{ mainStore.count10 }}</p><p>{{ mainStore.count10 }}</p><hr /><p>{{ count }}</p><p>{{ foo }}</p><p><button @click="handleChangeState">修改数据</button></p></template><script lang="ts" setup>import { storeToRefs } from 'pinia'import { useMainStore } from '../store'// 【哪里使用写哪里】,此时要在HelloWorld组件中用,那就写这里。这很Composition APIconst mainStore = useMainStore()// 禁止!这样会丧失响应性,因为pinia在底层将state用reactive做了处理// const { count, foo } = mainStore// 解决方案:将结构出的数据做ref响应式代理const { count, foo } = storeToRefs(mainStore)const handleChangeState = () => {// ==============数据修改的几种方式=============// 方式一:直接修改// mainStore.count++// 方式二:使用 $patch(对象) 批量修改,建议使用,底层做了性能优化// mainStore.$patch({// count: mainStore.count + 1,// foo: 'hello',// arr: [...mainStore.arr, 4] // 这就不优雅了,所以有了方式三// })// 方式三:使用 $patch(回调函数),这个更优雅,墙裂推荐!!!// 回调函数中的state参数,就是Store定义时里面的state!// mainStore.$patch((state) => {// state.count++// state.foo = 'hello'// state.arr.push(4)// })// 方式四:逻辑较为复杂时,应封装到Store的actions中,并对外暴露接口mainStore.changeState(10)}</script>
从以上几种修改store数据的方式,可以看出pinia的使用非常的简便+灵活,也非常的Composition API。推荐使用后两种方式。
三、购物车案例
1.准备工作
需求说明
```vue<template><ul><li>商品名称 - 商品价格<button>添加到购物车</button></li><li>商品名称 - 商品价格<button>添加到购物车</button></li><li>商品名称 - 商品价格<button>添加到购物车</button></li></ul></template><script lang="ts" setup></script>
<template><div class="cart"><h2>我的购物车</h2><p><i>请添加一些商品到购物车</i></p><ul><li>商品名称 - 商品价格 × 商品数量</li><li>商品名称 - 商品价格 × 商品数量</li><li>商品名称 - 商品价格 × 商品数量</li></ul><p>商品总价</p><p><button>结算</button></p><p>结算成功 / 失败.</p></div></template><script lang="ts" setup></script>
数据接口
/*** src/api/shop.ts* Mocking client-server processing*/export interface IProduct {id: numbertitle: stringprice: numberinventory: 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.开始开发
定义Store
以下代码学习点:
- as类型断言
- 如何在actions中写异步操作 ```typescript 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— } } } })
下面的代码,有以下学习点:- type类型合并与过滤- 跨容器通信的极致优雅操作```typescriptimport { 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)// 如果有则让商品数量+1if (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 = [] // 清空购物车}}}})
重写组件
<template><ul><li v-for="item in productsStore.all">{{ item.title }} - {{ item.price }}<br><button :disabled="!item.inventory" @click="cartStore.addProductToCart(item)">添加到购物车</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>
<template><div class="cart"><h2>我的购物车</h2><p><i>请添加一些商品到购物车</i></p><ul><li v-for="item in cartStore.cartProducts">{{ item.title }} - {{ item.price }} × {{ item.num }}</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 { useCartStore } from '../store/cart'const cartStore = useCartStore()</script>
