vue3从零开始(终
简介: 本文将基于 原来的代码进行添加到购物车的操作, 这里会使用到 vue-router 进行路由跳转,以及使用pinia 进行购物车数据,和登录 状态管理 本来我写的标题是快速上手vue3 但是仔细想来写的有点繁琐了于是更名成从零开始 跳转路由 /cart
<button class="cart-button button-bottom" @click="">添加至购物车</button>
import { useRouter } from 'vue-router'
const router = useRouter()
//跳转购物车
const routerToCart = () => {
if(window.confirm("添加成功,是否前往购物车查看")){
router.push({name: "cart"})
}
}
这里使用router.push 方法进行路由跳转 也可以使用router.go(-1) 返回上一页
六、pinia
使用 pinia 进行公共数据管理 购物车的数据一般会在多个页面用到,所以我们要将其作为共享数据进行管理
1、pinia安装
yarn add pinia
//main.js
import { createPinia } from 'pinia'
app.use(pinia)
定义store 使用 defineStore
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => { //两个参数,一个id (name) 和一个回调函数
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
使用state
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
访问state
const store = useStore()
store.counter++
重置状态
const store = useStore()
store.$reset()
使用Getters
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
//使用this 访问整个store
doublePlusOne(): number {
return this.counter * 2 + 1
},
})
store可以直接访问getters’
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
<script>
export default {
setup() {
const store = useStore()
return { store }
},
}
</script>
通过this 访问其他getters
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 类型是自动推断的,因为我们没有使用 `this`
doubleCount: (state) => state.counter * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
})
传递参数给getters
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
并在组件中使用
<script>
export default {
setup() {
const store = useStore()
return { getUserById: store.getUserById }
},
}
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
访问其他store 的getters
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
使用actions
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
与getters 不同 actions 可以异步
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// 让表单组件显示错误
return error
}
},
},
})
可以通过store 直接调用
export default {
setup() {
const store = useStore()
store.randomizeCounter()
},
}
订阅 Actions
可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数。 以类似的方式,onError 允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用,类似于 Vue 文档中的这个提示。
const unsubscribe = someStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
实践: 同过pinia管理购物车数据 在商品页ProductDetail 添加添加购物车方法
import { cartStore } from '../store/cart'
const store = cartStore()
//跳转购物车
const addToCart = () => {
if(!state.count && state.count*1 < 1 ){
alert('请输入购买数量')
return;
}
let item = Object.assign(state.product,{number: state.count,coutnPrice: countPrice._value})
//添加购物车
store.addToCart(item)
console.log(store.fullCarts)
if(window.confirm("添加成功,是否前往购物车查看")){
router.push({name: "cart"})
}
}
购物车页面
<template>
<div class="cart-page">
<div class="cart-head flex-start">
<div class="btn-back-box" @click="goBack()">
<
</div>
<div class="head-title">
购物车
</div>
</div>
<div class="cart-main">
<div class="cart-list" v-if="store.fullCarts.length>0">
<div class="cart-list-item flex-between" v-for="(item,index) in store.fullCarts">
<div class="cart-item-center flex-start">
<div class="item-center-img-box">
<img class="item-center-img" :src="item.imgUrl" alt="图片">
</div>
<div class="item-center-info">
<div class="item-info-head">
{{item.name}}
<br>
{{item.price}} 元/斤
</div>
<div class="item-info-foot">
数量:<input type="text" v-model="item.number">
</div>
</div>
</div>
<div class="cart-item-right">
<button @click="deleteCartItem(index)">删除</button>
</div>
</div>
</div>
<div v-else>
<h1 style="margin-left: 40px;margin-top: 40px;">购物车暂无商品</h1>
</div>
</div>
<div class="cart-footer flex-end">
<div class="cart-footer-left">
总共:{{totalPrice}} 元
</div>
<div class="cart-footer-right">
<button>去结算</button>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.cart-page {
width: 600px;
.cart-head {
height: 60px;
.btn-back-box {
width: 60px;
height: 60px;
line-height: 60px;
font-size: 20px;
cursor: pointer;
text-align: center;
}
.head-title {
margin-left: 200px;
font-size: 20px;
}
}
.cart-main {
.cart-list-item {
width: 600px;
height: 100px;
border: 1px solid #ccc;
.cart-item-center {
margin-left: 20px;
.item-center-img-box{
border: 1px solid #ccc;
margin-right: 10px;
.item-center-img {
width: 80px;
height: 80px;
}
}
.item-center-info{
input{
width: 40px;
}
}
}
.cart-item-right {
button {
cursor: pointer;
margin-top: 50px;
margin-right: 20px;
border: 1px solid #f60000;
border-radius: 2px;
}
}
}
}
.cart-footer {
width: 600px;
height: 60px;
position: fixed;
z-index: 99;
bottom: 0px;
left: 0px;
background-color: #f5f5f5;
.cart-footer-right {
margin-left: 20px;
button {
margin-right: 20px;
color: #fff;
background-color: #ff6700;
border: 1px solid #ff6700;
cursor: pointer;
}
}
}
}
</style>
<script>
import { computed } from '@vue/reactivity'
import { onMounted } from 'vue'
import { useCounterStore } from '../stores/counter'
import { reactive, toRefs } from 'vue'
import Data from '../assets/data'
import { cartStore } from '../stores/cart'
import router from '../router-self'
import { useRouter } from 'vue-router'
export default {
setup() {
const state = reactive({
fruits: Data.fruits
})
const store = cartStore()
const router = useRouter()
const totalPrice = computed(()=>{
let totalPrice = 0
store.fullCarts.forEach((item)=>{
totalPrice += item.number * item.price
})
return totalPrice
})
const deleteCartItem = (index) =>{
store.fullCarts.splice(index,1)
}
const goBack = () => {
router.go(-1)
}
return {
...toRefs(state),
store,
totalPrice,
deleteCartItem,
goBack
}
}
}
</script>
cart.js
import { defineStore } from 'pinia'
export const cartStore = defineStore('cart',{
state: () => {
return {
fullCarts: [],//购物车列表
}
},
getters: {
getCarts(){
return this.fullCarts
}
},
actions: {
addToCart(item){
this.fullCarts.push(item)
},
clearCart(){
console.log('清除购物车')
}
}
})
总结: 使用pinia 确实比 vuex 方便许多
|