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>
  <p>{{ count }}</p>
  <p>{{ name }}</p>
  <p>{{ hobby }}</p>
  <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>
  <div>
    <button @click="add">{{ mainStore.count }}</button>
    {{ mainStore.count10 }}
  </div>
</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更加方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值