
官网:
bilibili 教学视频
一、环境初始化
1.创建项目
1 2 3 4 5
| $ yarn create vite # or $ npm create vite@latest # or $ pnpm create vite
|
然后跟着提示一步步走,使用 ts
2.安装 Pinia
1 2 3
| yarn add pinia # or with npm npm install pinia
|
二、基本使用
1.创建 Pinia 示例并挂载
main.js1 2 3 4 5 6 7
| import { createApp } from "vue"; import App from "./App.vue"; import { createPinia } from "pinia";
const pinia = createPinia();
createApp(App).use(pinia).mount("#app");
|
如果使用的是 Vue2,还需要安装一个插件,并将创建一个Pinia
注入到应用的 root:
main.js1 2 3 4 5 6 7 8 9 10 11 12
| import { createPinia, PiniaVuePlugin } from "pinia";
Vue.use(PiniaVuePlugin); const pinia = createPinia();
new Vue({ el: "#app", pinia, });
|
2.基本使用
** `store` 是使用`defineStore()` 定义的,第一个参数是整个应用中 store 的唯一名称(id)**
建议: > 可以为**defineStore()**
的返回值任意命名,但是最好使用**use**
加上**store**
的名称和**Store**
,例如:**useUserStore**
、**useCartStore**
、**useProductStore**
类似于 Vue 的选项 API,也可以传递一个带有**state**
、**actions**
和**getters**
属性的选项对象.
**state**
就类似于组件的 **data**
,用来存储全局状态的,**getters**
就类似于组件的 **computed**
,用来封装计算属性,有缓存功能,**actions**
类似于组件的 **methods**
,用来封装业务逻辑,修改 **state**
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import { defineStore } from "pinia";
export const useMainStore = defineStore("main", {
state: () => { return { count: 100, foo: "bar", arr: [1, 2, 3], }; },
getters: { count10(state) { console.log("count10()调用了"); return state.count + 10; },
},
actions: {
changeState(num: number) {
this.$patch((state) => { state.count += num; state.foo = "hello"; state.arr.push(4); }); }, }, });
|
打开 App.vue,砍掉没用的,我们直接使用项目中HelloWorld.vue
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <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
的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <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 API const 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.准备工作
需求说明
- 商品列表
- 购物车
- 展示购物车商品列表
- 展示总价格
- 订单结算
- 展示结算状态
页面模板
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <h1>Pinia - 购物车展示</h1> <hr /> <h2>商品列表</h2> <ProductList></ProductList> <hr /> <ShoppingCart></ShoppingCart> </template>
<script setup lang="ts"> import ProductList from "./components/ProductList.vue"; import ShoppingCart from "./components/ShoppingCart.vue"; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <ul> <li> 商品名称 - 商品价格 <button>添加到购物车</button> </li> <li> 商品名称 - 商品价格 <button>添加到购物车</button> </li> <li> 商品名称 - 商品价格 <button>添加到购物车</button> </li> </ul> </template>
<script lang="ts" setup></script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <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>
|
数据接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
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; };
async function wait(delay: number) { return new Promise((resolve) => setTimeout(resolve, delay)); }
|
2.开始开发
定义 Store
以下代码学习点:
- as 类型断言
- 如何在 actions 中写异步操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { defineStore } from "pinia"; import { getProducts, IProduct } from "../api/shop";
export const useProdunctsStore = defineStore("products", { state: () => { return { all: [] as IProduct[], }; },
getters: {},
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 类型合并与过滤
- 跨容器通信的极致优雅操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import { defineStore } from "pinia"; import { buyProducts, IProduct } from "../api/shop"; import { useProdunctsStore } from "./products";
type CartProduct = { num: 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, item) => { return total + item.price * item.num; }, 0); }, },
actions: {
addProductToCart(product: IProduct) { if (product.inventory <= 0) { return; }
const cartItem = this.cartProducts.find((item) => item.id === product.id); if (cartItem) { cartItem.num++; } else { this.cartProducts.push({ id: product.id, title: product.title, price: product.price, num: 1, }); }
const productsStore = useProdunctsStore(); productsStore.decrementProduct(product); },
async checkOut() { const ret = await buyProducts(); this.checkoutStatus = ret ? "成功" : "失败"; if (ret) { this.cartProducts = []; } }, }, });
|
重写组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <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>
|