// 其中的 Reflet 是ES6语法,具体语义用法参考: https://www.bookstack.cn/read/es6-3rd/docs-reflect.md
// reactive.js中
const reactive = target => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log("数据收集")
return res
},
set(target, key, receiver) {
const res = Reflect.set(target, key, receiver)
console.log("数据更新")
return res
}
})
}
const obj = reactive({
a: 10, b:20})
obj.a = 150
console.log(obj.a)
// 通过控制台执行 node reactive.js
// 控制台能依次打印出
// 数据更新
// 数据收集
// 150
// import ComponentB from "./components/ComponentB.vue"
const ComponentB = defineAsyncComponent(() => {
import("./components/ComponentB.vue")
})
// composables / useCountDown.js中
// 封装倒计时逻辑函数
import {
computed, onUnmounted, ref } from 'vue'
import dayjs from 'dayjs'
export const useCountDown = () => {
// 1. 响应式的数据
let timer = null
const time = ref(0)
// 格式化时间 为 xx分xx秒
const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
// 2. 开启倒计时的函数
const start = (currentTime) => {
// 开始倒计时的逻辑
// 核心逻辑的编写:每隔1s就减1
time.value = currentTime
timer = setInterval(() => {
time.value--
}, 1000)
}
// 组件销毁时清除定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
return {
formatTime,
start,
}
}
// 组件中使用
import {
useCountDown } from '@/composables/useCountDown'
const {
formatTime, start } = useCountDown()
onMounted(() => {
// 初始化倒计时秒数
start(600) // 传一个600秒
})
<p>
支付还剩 <span>{
{
formatTime }}</span>, 超时后将取消订单.
</p>
import axios from 'axios'
import {
ElMessage } from 'element-plus'
import {
useUserStore } from '@/store/userStore'
import router from '@/router'
// axios.create 创建基础实例
const httpInstance = axios.create({
baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net', // 基地址
timeout: 30000, // 超时时间,30秒
})
// 请求拦截器
httpInstance.interceptors.request.use(
(config) => {
// 1. 从pinia获取token数据
const userStore = useUserStore()
// 2. 按照后端的要求拼接token数据
const token = userStore.userInfo.token
if (token) {
config.headers.Authorization = `Bearer ${
token}`
}
return config
},
(err) => {
Promise.reject(err)
}
)
// 响应拦截器
httpInstance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
const userStore = useUserStore()
// 统一错误提示
ElMessage({
type: 'warning',
message: e.response.data.message,
})
// 401 token 失效处理: (1)清除本地用户数据. (2)跳转到登录页
if (e.response.data.status == 401) {
userStore.clearUserInfo()
router.push('/login')
}
return Promise.reject(err)
}
)
export default httpInstance
需要做持久化的原因: 用户数据中有一个关键的数据叫做Token (用来标识当前用户是否登录),而Token持续一段时间才会过期,
Pinia的存储是基于内存的,刷新就丢失,为了保持登录状态就要做到刷新不丢失,需要配合持久化进行存储.
目的:保持token不丢失,保持登录状态
最终效果:操作state时会自动把用户数据在本地的localStorage也存一份,刷新的时候会从localStorage中先取
实现步骤:
(1) npm install pinia-plugin-persistedstate
(2) pinia 注册插件
// store/index.js中
import {
createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
// 注册持久化插件
pinia.use(piniaPluginPersistedstate)
export default pinia
(3) 在需要持久化的store中进行配置
// 管理用户数据相关
import {
defineStore } from 'pinia'
export const useUserStore = defineStore(
'user',
() => {
// ...
},
{
persist: true,
}
)
// main.js中
// 引入全局组件插件
import {
componentPlugin } from '@/components'
app.use(componentPlugin)
// components/index.js中
import Sku from './XtxSku/index.vue' // 不同组件单独放进各自的文件夹下
export const componentPlugin = {
install (app) {
// app.component('组件名字',组件配置对象)
app.component('XtxSku', Sku)
}
}
import {
createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [ ],
// 路由滚动行为定制
scrollBehavior() {
return {
top: 0,
}
},
})
export default router
核心实现逻辑:使用elementPlus提供的 v-infinite-scroll 指令监听是否满足触底条件,
满足加载条件时让页数参数加一获取下一页数据,做新老数据拼接渲染
实现步骤:
(1) 配置 v-infinite-scroll
(2) 页数加一获取下一页数据
(3) 老数据和新数据拼接
(4) 加载完毕结束监听
参考 element-plus 官网:
https://element-plus.gitee.io/zh-CN/component/infinite-scroll.html
使用逻辑函数拆分业务,方便代码维护
实现步骤:
(1) 按照业务声明以 use 打头的逻辑函数
(2) 把独立的业务逻辑封装到各个函数内部
(3) 函数内部把组件中需要用到的数据或者方法return出去
(4) 在组件中调用函数把数据或者方法组合回来使用
解决路由缓存问题
原因: 使用带有参数的路由时需要注意的是, 当用户从 /users/johnny 导航到 /users/jolyne 时, 相同的组件实例将被重复调用.
因为两个路由都渲染同个组件, 比起销毁再创建,复用则显得更加高效.
不过,这也意味着组件的生命周期钩子不会被调用(这时就得用到导航钩子了).
解决思路:
方案一: 给 router-view 添加 key
<!-- 添加key 破坏复用机制 强制销毁重建 -->
<RouterView :key="$route.fullPath" /