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

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia() // 调用方法,创建pinia实例,将pinia引入项目中
createApp(App).use(pinia).mount('#app')

2.2 快速入门

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

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

// store/index.js  创建store(pinia创建的是小store
import { defineStore } from 'pinia' 
// 接收函数/方法,用use开头
const useMainStore = defineStore('main', { // 起名,配置对象
  state: () => {
    return {
      count: 0,
    }
  },
  getters: {},
  actions: {},
})
export { useMainStore }

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

<template>
  {{ mainStore.count }}
</template>
<script setup>
  import { useMainStore } from './store'
  const mainStore = useMainStore()
</script>

<style></style>

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

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

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

3.Pinia核心概念

3.1 状态修改

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

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

const useMainStore = defineStore('main', {
  state: () => {
    return {
      count: 0,
      name: '张三',
      hobby: ['抽烟', '喝酒', '烫头'],
    }
  },
  getters: {},
  actions: {},
})

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

<template>
  <p>{{ count }}</p>
  <p>{{ name }}</p>
  <p>{{ hobby }}</p>
  <button>修改数据</button>
</template>

<script setup>
  import { storeToRefs } from 'pinia'
  import { useMainStore } from './store'
  const mainStore = useMainStore()
  const { count, name, hobby } = storeToRefs(mainStore)
</script>

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

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

  • 直接修改 但是这样做从性能层面不够好

    <template>

    {{ count }}

    {{ name }}

    {{ hobby }}

    <button @click="changeStore">修改数据</button> </template> <script setup> import { storeToRefs } from 'pinia' import { useMainStore } from './store' const mainStore = useMainStore() const { count, name, hobby } = storeToRefs(mainStore) const changeStore = () => { mainStore.count++ mainStore.name = '李四' mainStore.hobby.push('敲代码') } </script>
  • 通过$patch方法传递一个对象 利用这个方法改变数据可以优化数据更新的性能

    const changeStore = () => {
    mainStore.$patch({
    count: mainStore.count + 1,
    name: '李四',
    hobby: [...mainStore.hobby, '敲代码'],
    })
    }

  • 通过$patch方法传递一个函数 可以优化传递对象的代码

    mainStore.$patch((state) => { //state即是store中的状态
    state.count++
    state.name = '李四'
    state.hobby.push('敲代码')
    })

  • 通过触发action中的方法来修改数据 可以进行参数传递 推荐使用这个方法

    //store
    actions: {
    changeState(n) { //action中可以通过this访问到store实例
    this.$patch((state) => {
    state.count+=n
    state.name = '李四'
    state.hobby.push('敲代码')
    })
    },
    },

    //组件中
    mainStore.changeState(10)

    import { defineStore } from 'pinia'
    const useMainStore = defineStore('main', {
    actions: {
    addCount(number) {
    this.count += number
    }
    },
    });
    export default useMainStore

    <template>
    <button @click="add">{{ mainStore.count }}</button> {{ mainStore.count10 }}
    </template> <script setup> import useMainStore from './store' const mainStore = useMainStore() function add() { mainStore.addCount(10) } </script>

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

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

<template>
  <div>
    <button @click="add">{{ count }}</button>
    {{ count10 }}
    <p>{{ name }}</p>
    <p v-for="(item, index) in friends" :key="index">{{ item }}</p>
  </div>
</template>
<script setup>
import useMainStore from './store'
import { storeToRefs } from 'pinia'
const mainStore = useMainStore()
const { count, name, friends, count10 } = storeToRefs(mainStore)

const useMainStore = defineStore('main', { // 起名,配置对象
  state: () => {
    return {
      count: 1,
      name:'peiqi',
      friends: ['qiaozhi','suxi','peideluo']
    }
  },
  actions: {
    addCount(number) {
      // this.count += number
      // this.name = 'xiaozhu'
      // this.friends.push('lingyangfuren')
      // $patch有两种传参形式,一种传参数,一种传函数
      // this.$patch({
      //     count: this.count+number,
      //     name:'xiaozhu',
      //     friends:[...this.friends,"lingyangfuren"]
      // })
      this.$patch((state) => {
        state.count += number
        state.name = 'xiaozhu'
        state.friends.push("lingyangfuren")
      })
    })
  }
},
});

3.2 getters

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

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

store的setup语法糖写法

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

const useMainStore = defineStore('main', () => {
    let count = ref(1)
    let name = ref('peiqi')
    let friends = ref(['qiaozhi','suxi','peideluo'])
    let count10 = computed(() => {
        return count.value *10
    })
    function addCount() {
        count.value++
        name.value = 'xiaozhu'
        friends.value.push('lingyangfuren')
    }
    function addAsync() {
        setTimeout(() => {
            this.count += 5
        }, 3000);
    }
    return{
        count, name, friends, count10, addCount, addAsync
    }
})
const useLoginStore = defineStore('login', {})
export {useMainStore, useLoginStore}

4.购物车案例

4.1 案例准备

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

商品列表组件
  <template>
    <ul>
      <li>
        商品名称-商品价格
        <br />
        <button>添加到购物车</button>
      </li>
      <li>
        商品名称-商品价格
        <br />
        <button>添加到购物车</button>
      </li>
      <li>
        商品名称-商品价格
        <br />
        <button>添加到购物车</button>
      </li>
    </ul>
  </template>

购物车结算组件
  <template>
    <div class="cart">
      <h2>购物车</h2>
      <p>
        <i>请添加一些商品到购物车</i>
      </p>
      <ul>
        <li>商品名称-商品价格 * 商品数量</li>
        <li>商品名称-商品价格 * 商品数量</li>
        <li>商品名称-商品价格 * 商品数量</li>
      </ul>
      <p>商品总价:XXX</p>
      <p>
        <button>结算</button>
      </p>
      <p>结算成功/失败</p>
    </div>
  </template>

App.vue
<template>
  <h1>Pinia-购物车</h1>
  <hr>
  <h2>商品列表</h2>
  <ProductionList></ProductionList>
  <hr>
  <ShoppingCart></ShoppingCart>
</template>

<script setup >
  import ProductionList from './components/ProductionList.vue'
  import ShoppingCart from './components/ShoppingCart.vue';
</script>

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

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

api/product.js
const products = [
  {
    id: 1,
    title: 'iphone 13',
    price: 5000,
    inventory: 3,
  },
  {
    id: 2,
    title: 'xiaomi 12',
    price: 3000,
    inventory: 20,
  },
  {
    id: 3,
    title: 'macbook air',
    price: 9900,
    inventory: 8,
  },
]
export const getProducts = async () => {
  await wait(100)
  return products
}
export const buyProducts = async () => {
  await wait(100)
  return Math.random() > 0.5
}
function wait(delay) {
  return new Promise((res) => {
    setTimeout(res, delay)
  })
}

4.2 渲染商品列表

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

//store/product.js
import { defineStore } from 'pinia'
import { getProducts } from '../api/product.js'
export const useProductStore = defineStore('product', {
  state: () => {
    return {
      lists: [],
    }
  },
  getters: {},
  actions: {
    async getProductLists() {
      const productLists = await getProducts()
      this.lists = productLists
    },
  },
})

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

<template>
  <ul>
    <li v-for="item in store.lists" :key="item.id">
      {{ item.title }}-{{ item.price }}
      <br />
      <button>添加到购物车</button>
    </li>
  </ul>
</template>

<script setup>
  import { useProductStore } from '../store/product.js'
  const store = useProductStore()
  store.getProductLists()
</script>

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

4.3 购物车添加商品

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

store/shopCart.js
import { defineStore } from 'pinia'

defineStore('shopCart', {
  state: () => {
    return {
      lists: [],
    }
  }, 
  getters: {},
  actions: {
    addProduct(product) {
      //这里书写添加商品的逻辑
    },
    decrement(prodcut) { //减少库存数量
    const res = this.lists.find((item) => item.id === prodcut.id)
    res.inventory--
    },
  },
})

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

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

然后用代码实现:

addProduct(product) {
  //这里书写添加商品的逻辑
  if (product.inventory < 1) {
    return
  }
  const res = this.lists.find((item) => item.id === product.id)
  if (res) {
    res.number++
  } else {
    this.lists.push({
      id: product.id,
      title: product.title,
      price: product.price,
      number: 1,
    })
  }
  const pStore = useProductStore()
  pStore.decrement(product)
},

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

购物车
  <template>
    <ul>
      <li v-for="item in store1.lists" :key="item.id">
        {{ item.title }}-{{ item.price }}
        <br />
        <!-- 通过库存状态给按钮绑定禁用逻辑 -->
        <button @click="store2.addProduct(item)" :disabled="!item.inventory">
        </li>
    </ul>
  </template>

  <script setup>
    import { useProductStore } from '../store/product.js'
    import { useShopCartStore } from '../store/shopCart'
    const store1 = useProductStore()
    store1.getProductLists()
    const store2 = useShopCartStore()
  </script>

4.4 总价和结算

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

总价使用getter计算得来:

store/shopCart.js
getters: {
  sum(state) {
    return state.lists.reduce((total, item) => {
      return total + item.price * item.number
    }, 0)
  },
},

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

store/shopCart.js
state: () => {
  return {
    ...
      result: '',
    }
},
actions: {
  ...
  async getResult() {
    const res = await buyProducts()
    this.result = res ? '成功' : '失败'
    if (res) { //结算成功后清空购物车列表
      this.lists = []
    }
  },
},

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

相关推荐
以对_13 分钟前
uview表单校验不生效问题
前端·uni-app
程序猿小D1 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
奔跑吧邓邓子2 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
前端李易安2 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙2 小时前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
杨荧3 小时前
【JAVA开源】基于Vue和SpringBoot的旅游管理系统
java·vue.js·spring boot·spring cloud·开源·旅游
Envyᥫᩣ3 小时前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.7 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
一 乐8 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
昨天;明天。今天。8 小时前
案例-表白墙简单实现
前端·javascript·css