• Day44:Pinia和购物车案例准备


    1.Pinia基本介绍

    1.1 基本介绍

    一直以来VueX都是官方为Vue提供的状态管理库,但是在Vue3的版本中,尽管VueX4也能提供组合式API,但是自身存在的很多缺陷让官方重新编写了一个新的库,也就是我们今天要学习的Pinia,结果发现这个新的状态库比起VueX要更加实用,它已经正式成为了Vue的官方推荐,而VueX仍然可以使用,但不会继续维护了,显而易见,Pinia才是未来的主流,所以必须学习它,它的设计思路和VueX非常类似,所以对于已经熟练掌握了VueX的开发者而言,学习Pinia非常轻松!

    1.2 Pinia vs VueX

    Pinia和VueX设计思想是一致的,但Pinia要简洁的多,它对于TypeScript的支持非常好,总结一下它们的区别:

    • Pinia没有mutations。修改状态更加简便。
    • 没有模块嵌套,Pinia支持扁平结构,以更好的方式支持store之间的结合。
    • 完美支持typescript。
    • 移除命名空间。Pinia的定义方式内置了命名空间。
    • 无需动态添加stores。

    2.Pinia快速入门

    Pinia对于Vue2和Vue3是都支持的,我们以Vue3为例来讲解它的使用:

    2.1 Pinia安装

    通过命令在Vue3项目中安装Pinia,最新的版本是V2版本。

    npm i pinia

    然后在入口中使用Pinia

    1. import { createApp } from 'vue'
    2. import App from './App.vue'
    3. import { createPinia } from 'pinia'
    4. const pinia = createPinia() // 调用方法,创建pinia实例,将pinia引入项目中
    5. createApp(App).use(pinia).mount('#app')
    2.2 快速入门

    和VueX类似,使用Pinia同样需要创建store,找到src目录,创建一个store。

    通过defineStore方法创建一个store,这个方法接受2个参数,第一个参数是store的唯一id,第二个参数是store的核心配置,这里和VueX几乎一样,但是没有了mutations,而且注意state必须写成箭头函数的形式。defineStore方法返回的是一个函数,通过调用这个返回值函数可以得到具体的store实例。

    1. // store/index.js 创建store(pinia创建的是小store
    2. import { defineStore } from 'pinia'
    3. // 接收函数/方法,用use开头
    4. const useMainStore = defineStore('main', { // 起名,配置对象
    5. state: () => {
    6. return {
    7. count: 0,
    8. }
    9. },
    10. getters: {},
    11. actions: {},
    12. })
    13. export { useMainStore }

    在组件中可以引入store中导出的函数来得到store实例,从而访问其中的状态:

    1. <template>
    2. {{ mainStore.count }}
    3. </template>
    4. <script setup>
    5. import { useMainStore } from './store'
    6. const mainStore = useMainStore()
    7. </script>
    8. <style></style>

    其中,mainStore.$state.count === mainStore.count为true

    1. import useMainStore from './store'
    2. const mainStore = useMainStore()
    3. console.log(mainStore.$state.count === mainStore.count)

    在pinia中,如果希望在Store中访问store实例的话,可以通过this

    3.Pinia核心概念

    3.1 状态修改

    VueX是规定修改状态必须通过mutation实现,Pinia中移除了mutation概念,那应该如何访问和修改状态呢,一起来看看。

    首先在state中初始化一些状态:

    1. const useMainStore = defineStore('main', {
    2. state: () => {
    3. return {
    4. count: 0,
    5. name: '张三',
    6. hobby: ['抽烟', '喝酒', '烫头'],
    7. }
    8. },
    9. getters: {},
    10. actions: {},
    11. })

    然后在组件中来访问和修改状态,如果希望通过解构的方式来访问状态里的数据,可以调用storeToRefs方法来保持数据的响应性。

    1. <template>
    2. <p>{{ count }}</p>
    3. <p>{{ name }}</p>
    4. <p>{{ hobby }}</p>
    5. <button>修改数据</button>
    6. </template>
    7. <script setup>
    8. import { storeToRefs } from 'pinia'
    9. import { useMainStore } from './store'
    10. const mainStore = useMainStore()
    11. const { count, name, hobby } = storeToRefs(mainStore)
    12. </script>

    点击button按钮后,如果希望修改store中数据,vuex要求修改数据必须通过触发Mutation

    pinia要求不那么严格,可以直接在组件中修改数据,但不推荐这么做:

    • 直接修改 但是这样做从性能层面不够好
    1. <template>
    2. <p>{{ count }}</p>
    3. <p>{{ name }}</p>
    4. <p>{{ hobby }}</p>
    5. <button @click="changeStore">修改数据</button>
    6. </template>
    7. <script setup>
    8. import { storeToRefs } from 'pinia'
    9. import { useMainStore } from './store'
    10. const mainStore = useMainStore()
    11. const { count, name, hobby } = storeToRefs(mainStore)
    12. const changeStore = () => {
    13. mainStore.count++
    14. mainStore.name = '李四'
    15. mainStore.hobby.push('敲代码')
    16. }
    17. </script>
    • 通过$patch方法传递一个对象 利用这个方法改变数据可以优化数据更新的性能
    1. const changeStore = () => {
    2. mainStore.$patch({
    3. count: mainStore.count + 1,
    4. name: '李四',
    5. hobby: [...mainStore.hobby, '敲代码'],
    6. })
    7. }
    • 通过$patch方法传递一个函数 可以优化传递对象的代码
    1. mainStore.$patch((state) => { //state即是store中的状态
    2. state.count++
    3. state.name = '李四'
    4. state.hobby.push('敲代码')
    5. })
    • 通过触发action中的方法来修改数据 可以进行参数传递 推荐使用这个方法
    1. //store
    2. actions: {
    3. changeState(n) { //action中可以通过this访问到store实例
    4. this.$patch((state) => {
    5. state.count+=n
    6. state.name = '李四'
    7. state.hobby.push('敲代码')
    8. })
    9. },
    10. },
    1. //组件中
    2. mainStore.changeState(10)
    1. import { defineStore } from 'pinia'
    2. const useMainStore = defineStore('main', {
    3. actions: {
    4. addCount(number) {
    5. this.count += number
    6. }
    7. },
    8. });
    9. export default useMainStore
    1. <template>
    2. <div>
    3. <button @click="add">{{ mainStore.count }}</button>
    4. {{ mainStore.count10 }}
    5. </div>
    6. </template>
    7. <script setup>
    8. import useMainStore from './store'
    9. const mainStore = useMainStore()
    10. function add() {
    11. mainStore.addCount(10)
    12. }
    13. </script>

    不能使用解构的写法来在响应式对象中简化掉mainStore.的内容,否则会导致响应性缺失

    如果希望简化写法,需要用storeToRefs(mainStore)来包裹mainStore,即可简化不写mainStore.

    1. <template>
    2. <div>
    3. <button @click="add">{{ count }}</button>
    4. {{ count10 }}
    5. <p>{{ name }}</p>
    6. <p v-for="(item, index) in friends" :key="index">{{ item }}</p>
    7. </div>
    8. </template>
    9. <script setup>
    10. import useMainStore from './store'
    11. import { storeToRefs } from 'pinia'
    12. const mainStore = useMainStore()
    13. const { count, name, friends, count10 } = storeToRefs(mainStore)
    1. const useMainStore = defineStore('main', { // 起名,配置对象
    2. state: () => {
    3. return {
    4. count: 1,
    5. name:'peiqi',
    6. friends: ['qiaozhi','suxi','peideluo']
    7. }
    8. },
    9. actions: {
    10. addCount(number) {
    11. // this.count += number
    12. // this.name = 'xiaozhu'
    13. // this.friends.push('lingyangfuren')
    14. // $patch有两种传参形式,一种传参数,一种传函数
    15. // this.$patch({
    16. // count: this.count+number,
    17. // name:'xiaozhu',
    18. // friends:[...this.friends,"lingyangfuren"]
    19. // })
    20. this.$patch((state) => {
    21. state.count += number
    22. state.name = 'xiaozhu'
    23. state.friends.push("lingyangfuren")
    24. })
    25. })
    26. }
    27. },
    28. });
    3.2 getters

    Pinia中的getters几乎和VueX的概念一样,可以视作是Pinia的计算属性,具有缓存功能。

    1. getters: {
    2. bigCount(state) { //接受的第一个参数即是state
    3. return state.count + 100
    4. },
    5. },

    store的setup语法糖写法

    上面写的是类似于vue2的配置选项写法,以下写法为类似于vue3的setup语法糖写法,可以选择自己喜欢的语法

    1. const useMainStore = defineStore('main', () => {
    2. let count = ref(1)
    3. let name = ref('peiqi')
    4. let friends = ref(['qiaozhi','suxi','peideluo'])
    5. let count10 = computed(() => {
    6. return count.value *10
    7. })
    8. function addCount() {
    9. count.value++
    10. name.value = 'xiaozhu'
    11. friends.value.push('lingyangfuren')
    12. }
    13. function addAsync() {
    14. setTimeout(() => {
    15. this.count += 5
    16. }, 3000);
    17. }
    18. return{
    19. count, name, friends, count10, addCount, addAsync
    20. }
    21. })
    22. const useLoginStore = defineStore('login', {})
    23. export {useMainStore, useLoginStore}

    4.购物车案例

    4.1 案例准备

    重新创建一个目录初始化一个Vue3项目,编写2个基本的组件(商品列表组件 购物车结算组件),模拟一个接口数据。

    1. 商品列表组件
    2. <template>
    3. <ul>
    4. <li>
    5. 商品名称-商品价格
    6. <br />
    7. <button>添加到购物车</button>
    8. </li>
    9. <li>
    10. 商品名称-商品价格
    11. <br />
    12. <button>添加到购物车</button>
    13. </li>
    14. <li>
    15. 商品名称-商品价格
    16. <br />
    17. <button>添加到购物车</button>
    18. </li>
    19. </ul>
    20. </template>
    1. 购物车结算组件
    2. <template>
    3. <div class="cart">
    4. <h2>购物车</h2>
    5. <p>
    6. <i>请添加一些商品到购物车</i>
    7. </p>
    8. <ul>
    9. <li>商品名称-商品价格 * 商品数量</li>
    10. <li>商品名称-商品价格 * 商品数量</li>
    11. <li>商品名称-商品价格 * 商品数量</li>
    12. </ul>
    13. <p>商品总价:XXX</p>
    14. <p>
    15. <button>结算</button>
    16. </p>
    17. <p>结算成功/失败</p>
    18. </div>
    19. </template>
    1. App.vue
    2. <template>
    3. <h1>Pinia-购物车</h1>
    4. <hr>
    5. <h2>商品列表</h2>
    6. <ProductionList></ProductionList>
    7. <hr>
    8. <ShoppingCart></ShoppingCart>
    9. </template>
    10. <script setup >
    11. import ProductionList from './components/ProductionList.vue'
    12. import ShoppingCart from './components/ShoppingCart.vue';
    13. </script>

    启动项目后可以看到基本的页面结构

    准备完毕页面结构后,需要模拟一个接口请求,可以在src目录下新建一个api目录创建一个模拟的数据。

    1. api/product.js
    2. const products = [
    3. {
    4. id: 1,
    5. title: 'iphone 13',
    6. price: 5000,
    7. inventory: 3,
    8. },
    9. {
    10. id: 2,
    11. title: 'xiaomi 12',
    12. price: 3000,
    13. inventory: 20,
    14. },
    15. {
    16. id: 3,
    17. title: 'macbook air',
    18. price: 9900,
    19. inventory: 8,
    20. },
    21. ]
    22. export const getProducts = async () => {
    23. await wait(100)
    24. return products
    25. }
    26. export const buyProducts = async () => {
    27. await wait(100)
    28. return Math.random() > 0.5
    29. }
    30. function wait(delay) {
    31. return new Promise((res) => {
    32. setTimeout(res, delay)
    33. })
    34. }
    4.2 渲染商品列表

    创建商品的store,在actions中定义初始化列表的逻辑。

    1. //store/product.js
    2. import { defineStore } from 'pinia'
    3. import { getProducts } from '../api/product.js'
    4. export const useProductStore = defineStore('product', {
    5. state: () => {
    6. return {
    7. lists: [],
    8. }
    9. },
    10. getters: {},
    11. actions: {
    12. async getProductLists() {
    13. const productLists = await getProducts()
    14. this.lists = productLists
    15. },
    16. },
    17. })

    在列表组件中触发action,然后渲染数据:

    1. <template>
    2. <ul>
    3. <li v-for="item in store.lists" :key="item.id">
    4. {{ item.title }}-{{ item.price }}
    5. <br />
    6. <button>添加到购物车</button>
    7. </li>
    8. </ul>
    9. </template>
    10. <script setup>
    11. import { useProductStore } from '../store/product.js'
    12. const store = useProductStore()
    13. store.getProductLists()
    14. </script>

    在浏览器中确认页面正确渲染

    4.3 购物车添加商品

    购物车组件需要创建专属的store,actions中也需要完成添加商品的逻辑,这里也是编码的重点,可以先把大体框架完成。

    1. store/shopCart.js
    2. import { defineStore } from 'pinia'
    3. defineStore('shopCart', {
    4. state: () => {
    5. return {
    6. lists: [],
    7. }
    8. },
    9. getters: {},
    10. actions: {
    11. addProduct(product) {
    12. //这里书写添加商品的逻辑
    13. },
    14. decrement(prodcut) { //减少库存数量
    15. const res = this.lists.find((item) => item.id === prodcut.id)
    16. res.inventory--
    17. },
    18. },
    19. })

    现在完成添加商品的逻辑,它应该包括这些要求:

    • 添加商品时检查是否有库存 如果没有需要终止函数
    • 添加商品时检查购物车列表中是否有该商品
      • 如果有 商品数量+1
      • 如果没有 添加到购物车列表中
    • 添加完成后库存数量-1

    然后用代码实现:

    1. addProduct(product) {
    2. //这里书写添加商品的逻辑
    3. if (product.inventory < 1) {
    4. return
    5. }
    6. const res = this.lists.find((item) => item.id === product.id)
    7. if (res) {
    8. res.number++
    9. } else {
    10. this.lists.push({
    11. id: product.id,
    12. title: product.title,
    13. price: product.price,
    14. number: 1,
    15. })
    16. }
    17. const pStore = useProductStore()
    18. pStore.decrement(product)
    19. },

    store中的逻辑完成后,在购物车和商品列表组件中渲染视图:

    1. 购物车
    2. <template>
    3. <ul>
    4. <li v-for="item in store1.lists" :key="item.id">
    5. {{ item.title }}-{{ item.price }}
    6. <br />
    7. <!-- 通过库存状态给按钮绑定禁用逻辑 -->
    8. <button @click="store2.addProduct(item)" :disabled="!item.inventory">
    9. </li>
    10. </ul>
    11. </template>
    12. <script setup>
    13. import { useProductStore } from '../store/product.js'
    14. import { useShopCartStore } from '../store/shopCart'
    15. const store1 = useProductStore()
    16. store1.getProductLists()
    17. const store2 = useShopCartStore()
    18. </script>
    4.4 总价和结算

    完成添加商品的核心逻辑后,再来完成总价计算和结算结果的显示。

    总价使用getter计算得来:

    1. store/shopCart.js
    2. getters: {
    3. sum(state) {
    4. return state.lists.reduce((total, item) => {
    5. return total + item.price * item.number
    6. }, 0)
    7. },
    8. },

    点击结算按钮,发送请求来获得结算结果,在购物车store中定义一个action来处理结算结果:

    1. store/shopCart.js
    2. state: () => {
    3. return {
    4. ...
    5. result: '',
    6. }
    7. },
    8. actions: {
    9. ...
    10. async getResult() {
    11. const res = await buyProducts()
    12. this.result = res ? '成功' : '失败'
    13. if (res) { //结算成功后清空购物车列表
    14. this.lists = []
    15. }
    16. },
    17. },

    所有购物车的基本功能都实现了,Pinia帮我们把所有的业务逻辑都提炼到了store中处理,大家有兴趣可以用VueX再来将这个案例完成一次,通过比较你会很快发现Pinia要比VueX更加方便。

  • 相关阅读:
    对HTTP和HTTPS的介绍
    C语言——扫雷游戏最全讲解
    uni app push 发送透传通知 支持安卓和苹果(最终版)
    强化记忆的七种武器
    项目的一些难点
    GEE两行代码下载任意范围影像python API
    matlab绘制动图
    Java [ 进阶 ] JVM双亲委派机制✨
    PHP反序列化
    20.添加HTTP模块
  • 原文地址:https://blog.csdn.net/albedo_102/article/details/132927953