likes
comments
collection
share

pinia的简单了解和购物车小案例

作者站长头像
站长
· 阅读数 17

简单介绍一下pinia

Pinia(发音为 /piːnjʌ/,类似于英语中的“peenya”)是最接近有效包名 piña(西班牙语中的_pineapple_)的词。 菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。 与 Store 类似,每一家都是独立诞生的,但最终都是相互联系的。 它也是一种美味的热带水果,原产于南美洲。Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。

与Vuex的区别

Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。

与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

与 Vuex 3.x/4.x 的比较

Pinia API 与 Vuex ≤4 有很大不同,即:

  • mutations 不再存在。他们经常被认为是 非常 冗长。他们最初带来了 devtools 集成,但这不再是问题。
  • 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
  • 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系
  • 没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。

安装

yarn add pinia
// 或者使用 npm
npm install pinia

购物车案例

该案例是来源于vue3+pinia购物车小例子练习,这里仅仅记录一下,方便自己使用

main.ts引入pinia

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'   // 引入pinia
const app = createApp(App) // 创建实例
app.use(createPinia()); // 使用pinia
app.mount('#app')

创建Car和Shops 的store文件

这里我和原作者省略了模拟数据那一个文件,我直接将数据放在了Shops.ts里面,主要感觉这关系不大,因为我只是想要大概使用,还有就是原作者用setup()模式,我是用< script setup>的。

store/Car.ts store/Shops.ts

Car.ts

import { defineStore } from 'pinia';
import { shopStore } from './Shops';
interface food {
    id: string;
    title: string;
    price: number;
    nums: number;
}

interface cars {
    [index: string]: food;
}

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const carStore = defineStore('car', {

    state: () => {
        return {
            cars: {} as cars,
            price: 0,
        };
    },
    getters: {
        /**
        * 对象转数组 便于展示
        */
        carsList(): Array<food> {
            let cars = [];
            if (!this.cars) {
                return [];
            }

            for (var key in this.cars) {
                if (this.cars.hasOwnProperty(key)) {
                    cars.push(this.cars[key]);
                }
            }
            return cars;
        },
        /**
        * 计算总价
        */
        totalPrice() {
            let cars: Array<food> = this.carsList;
            let total = cars.reduce((all, item) => {
            return all + item.price * item.nums;
            }, 0);
            return total;
        },
    },

    actions: {
        /**
        * 添加到购物车
        * @param id
        */
        addToCar(id: string) {
            const shop = shopStore();
            // 获取商品
            let foods: cars = shop.getShopObj;
            if (foods[id].nums <= 0) {
                return;
            }

            // 商品数量减少
            shop.joinCard(id);
            // 购物车如果存在商品 数量加1 否则新增
            if (this.cars[id]) {
            this.cars[id].nums++;
            } else {
            // 简单深拷贝
            this.cars[id] = JSON.parse(JSON.stringify(foods[id]));
            this.cars[id].nums = 1;
            }
        },
        /**
        * 从购物车减少
        * @param id
        */

        cudCar(id: string) {
            const shop = shopStore();
            // 如果只剩下一个就移除 否则就减少一个
            if (this.cars[id].nums === 1) {
            Reflect.deleteProperty(this.cars, id);
            } else {
            this.cars[id].nums--;
            }
            // 商品数量加1
            shop.cardToShop(id);
        },
    },
});

Shop.ts

import { defineStore } from 'pinia';
interface food {
    id: string;
    title: string;
    price: number;
    nums: number;
}
interface foods {
    [index: string]: food;
}

export const shopStore = defineStore('shop', {

    state: () => {
        return {
            foods: [] as Array<food>,
            isLoading: true,
        };
    },
    getters: {
        /**
        * 数组转对象 方便操作
        * @returns foods:{id:{food}}
        */
        getShopObj() {
            let foods: foods = {};
            this.foods.forEach((item) => {
                foods[item.id] = item;
            });
            return foods;
        },
    },

    actions: {
        /**
        * 异步加载数据
        */
        async loadFoods() {
        // this.foods = await getFood();
            this.foods = [
            { id: 'husky', title: '哈士奇狗', price: 50, nums: 10 },
            { id: 'car', title: '玩具车', price: 10, nums: 15 },
            { id: 'milk', title: '牛奶', price: 30, nums: 5 },
            ]
            this.isLoading = false;
        },
        /**
        * 加入购物车 商品数量减1
        * @param id
        */
        joinCard(id: string) {
            this.foods.forEach((item, index) => {
                if (item.id === id) {
                    if (this.foods[index].nums > 0) {
                        this.foods[index].nums--;
                    }
                }
            });
        },

        /**
        * 购物车商品减少 商品数量加1
        * @param id
        */
        cardToShop(id: string) {
            this.foods.forEach((item, index) => {
                if (item.id === id) {
                    this.foods[index].nums++;
                }
            });
        },
    },
});

使用

页面Shop.vue

<template>
    <div>
        <Shops />
        <Cars />
    </div>
</template>
  
<script setup>
    import Shops from './components/Shops.vue'
    import Cars from './components/Car.vue'
</script>

组件Shops.vue

<template>
    <h2>购物-Shops</h2>
    <ul v-if="!isLoading">
        <li v-for="item in foods" :key="item.id">
        <span>{{item.title}}</span>
        <span>单价{{item.price}}</span>
        <span>数量{{item.nums}}</span>
        <button @click="addToCar(item.id)" :disabled="item.nums===0">加入购物车</button>
        </li>
    </ul>
    <p v-if="isLoading">加载中</p>
</template>

<script setup>
    import {computed} from 'vue'
    import {shopStore} from '../../../store/Shops'
    import {carStore} from '../../../store/Car'
    const shops = shopStore();
    shops.loadFoods()
    const car = carStore()
    // 加入购物车 需要改变购物车中的数据
    const addToCar = (id) => {
        car.addToCar(id)
    }
    // 该列表是根据pinia中的异步加载数据
    const foods = computed(() => {
        return shops.foods
    });
    const isLoading = computed(() => {
        return shops.isLoading
    });
</script>

组件Car.vue

<template>
    <h1>购物车</h1>
    <ul v-if="carList.length>0">
        <li v-for="item in carList" :key="item.id">
        <span>{{item.title}}</span>
        <span>单价{{item.price}}</span>
        <span>数量{{item.nums}}</span>
        <button @click="addToCar(item.id)">+</button>
        <button @click="cudCar(item.id)">-</button>
        </li>
    </ul>
    <p>总价:{{total}}</p>
</template>

<script setup>
    import {carStore} from '../../../store/Car'
    import {toRaw,computed} from 'vue'
    const car = carStore()
    // 购物车+1
    const addToCar = (id) => {
        car.addToCar(id)
    }

    // 购物车-1
    const cudCar = (id) => {
        car.cudCar(id
    }
    const carList = computed(() => {
        return car.carsList
    });
    const total = computed(() => {
        return car.totalPrice
    });
</script>

总结:看了官网以及根据小例子弄了一下,感觉pinia相较于vuex的确方便简洁很多。

参考文章:

Pinia官网

vue3+pinia购物车小例子练习