√ 面试时可以提到的点
?项目中遇见的问题
如测试http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList
1.如果服务器返回的数据code字段200,代表服务器返回数据成功
2.整个项目,接口的前缀都有/api字样
为什么需要进行二次封装axios?
主要需要使用axios的请求拦截器与响应拦截器
概述
安装axios
cnpm install --save axios
在项目src中经常有一个API文件夹,该文件下存储axios请求。
二次封装axios,代码在api/request.js中
//引入axios
import axios from 'axios';
/*
1.利用axios对象的方法create,去创建一个axios实例
2.参数是配置对象
*/
const requestAxios = axios.create({
/*
接口当中,路径都带/api
在 http://xxx.xxx:8080后添加api http://xxx.xxx:8080/api
发送请求的时候,路径当中会出现api
*/
baseURL:"/api",
timeout:5000, //请求超时的时间5s
})
//请求拦截器
requestAxios.interceptors.request.use((config)=>{
//config:配置对象,对象里面有一个属性很重要,header请求头
return config;
})
//响应拦截器
//参数1成功的回调,参数2失败的回调
request.interceptors.response.use((res)=>{
return res.data;
},(error)=>{
return Promise.reject(new Error('fail')) //终止promise链
})
export default requestAxios;
对外暴露一个函数,只要外部调用这个函数,就向服务器发送ajax请求。当前函数执行后需要把服务器返回结果返回。
axios发送请求返回结果是Promise对象
import requestAxios from "./request";
//三级联动接口请求,实际请求的是/api/product/getBaseCategoryList
export const reqgetCategoryList = () =>requestAxios.get(`/product/getBaseCategoryList`);
请求时报错了!
解决办法
在vue.config.js里配置代理服务器,配置文件需要重新运行才能生效
代理服务器和我们同源,这样我们可以去代理服务器取数据。
服务器与服务器之间不用ajax请求,没有跨域问题。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,
devServer:{
proxy:{
//遇见api前缀的请求,走代理去服务器端获取
'/api':{
target:'http://gmall-h5-api.atguigu.cn' //这里写的服务器的地址
}
}
}
})
当客户端发送请求后,进度条开始显示。
当服务器返回完数据后,进度条消失。
1.插件安装
cnpm i --save nprogress
2.插件使用的位置
在发送请求时,进度条开始显示,当服务器返回数据后进度条消失。
适用于所有的请求,所以可以在拦截器中使用。
引入的nprogress是一个对象
注意这里还需要引用nprogress的样式
//引入进度条
import nprogress from 'nprogress';
//引入进度条的样式
import 'nprogress/nprogress.css'
//请求拦截器
requestAxios.interceptors.request.use((config)=>{
//config:配置对象,对象里面有一个属性很重要,header请求头
nprogress.start();//进度条开始
return config;
})
//响应拦截器
requestAxios.interceptors.response.use((res)=>{
nprogress.done();//进度条结束
return res.data;
},(error)=>{
return Promise.reject(new Error('fail')) //终止promise链
})
vuex是官方提供一个插件,状态管理库,集中式管理项目中组件共用的数据。
# 安装vuex3版本
cnpm i --save vuex@3
思路:根据不同的业务,将该业务state、mutations、action、getters 的封装在一个js文件中,让多种数据分类更加明确,代码更好维护。 --> 将store大仓库分为一个一个业务(功能) 的小仓库
实现:利用Vuex.store的modules配置挂载在store上
home模块的小仓库
//home模块的小仓库
const state={};
const mutations ={};
const actions = {};
const getter = {};
export default{
namespaced:true,
state,
mutations,
actions,
getter
}
在store大仓库的modules配置下挂载小仓库
import home from './home';
import search from './search';
//Vuex是对象,Store是构造函数,初始化Vuex仓库
export default new Vuex.Store({
modules:{
home,
search
}
})
当组件mount挂载完毕就可以向服务器发送数据请求
如果没有开启命名空间,dispatch触发时的写法
this.$store.dispatch('reqCategoryList')
如果开启命名空间,dispatch触发时要写清楚来自哪个小仓库的写法
this.$store.dispatch('home/reqCategoryList')
这里关于请求的到底是什么数据进行整理
首先添加了响应拦截器,返回的数据是res.date
requestAxios.interceptors.response.use((res)=>{
nprogress.done();//进度条结束
return res.data;//返回服务器返回的数据
},(error)=>{
return Promise.reject(new Error('fail')) //终止promise链
})
所以发送axios请求之后得到的返回值是上图的数据,res.data
返回的就是Array(17)的真实数据了
async reqCategoryList(){
/*reqgetCategoryList().then(res =>{
console.log(res);
});*/
//await 相当于then语法,直接得到结果,也就是上面注释的代码
let res= await getCategoryList();
}
这里整理整个请求的流程
1.TypeNav.vue组件里dispath
action里面的reqCategoryList
方法
export default {
name: 'TypeNav',
mounted() {
//通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('home/reqCategoryList')
},
}
2.action里的reqCategoryList
方法,发送axios请求,获取到数据commit给mutations的REQCATEGORYLIST
方法。
mutations的方法名一般为对应action方法名的大写
action有两个参数,第一个参数context是minstore,包含了commit方法,第二个参数为dispath的传参
const actions = {
//通过API里面的接口函数调用发请求,获取服务器的数据,commit给mutations
async reqCategoryList({commit}){
/*reqgetCategoryList().then(res =>{
console.log(res);
});*/
//相当于then语法,直接得到结果,也就是上面注释的代码
let result = await getCategoryList();
if(result.code==200){
commit("REQCATEGORYLIST",result.data);
}
}
};
3.mutations里的REQCATEGORYLIST
方法修改state数据
mutations有两个参数,第一个参数是state,第二个参数是commit传的参数
const state={
categoryList:[]
};
const mutations ={
REQCATEGORYLIST(state,val){
state.categoryList = val;
}
};
将动态数据渲染到页面
mapState 帮助我们映射state中的数据为计算属性
mapGetters 帮助我们映射getters中的数据为计算属性
学习笔记:https://blog.csdn.net/qq_41370833/article/details/124244138
computed:{
// categoryList(){
// return this.$store.state.home.categoryList
//}
...mapState('home',['categoryList']);//展开就是上面的写法
}
当鼠标移动到对应的选项时,对应选项背景颜色边变蓝
方法1:style样式完成 :hover
item:hover{
backgrounp:skyblue
}
方式2:鼠标移到谁,给谁添加动态类
鼠标移动到谁时,通过该索引值获取原数组的下标位置,动态添加class类时,比较当前index是否是移入的currentIndex
1.先增加一个需要动态添加的类
.cur{
background: skyblue;
}
2.获去鼠标移动到的索引值,修改currentIndex
<h3 @mouseenter="changeIndex(index)">
h3>
<script>
export default {
name: 'TypeNav',
data(){
return{
currentIndex:-1,//存储用户鼠标移动上哪一个分类的索引值,默认是-1
}
},
methods:{
changeIndex(index){ //数据进入修改currentIndex属性为鼠标进入的索引值
this.currentIndex = index;
}
},
script>
3.添加动态类
<div class="item" :class="{cur:currentIndex==index}">
鼠标移除时取消样式,也就是将currentIndex置为-1。
首先选项和红色背景的结构关系是兄弟关系,所以我们可以利用事件委派来实现。把鼠标移除的监听绑定在他们共同的父类上
事件委派
将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上。好处 ①减少内存占用(事件监听的回调变少) ②动态添加的内部元素也能响应
原始做法
通过CSS样式display:block | none 显示与隐藏二三级商品分类
通过JS控制
给二三级分类添加动态样式:style="{display:currentIndex==index?'block':'none'}"
或者直接使用v-show=”currentIndex==index“
引出防抖和节流,可以说通过下列现象想到的xxx场景。
现象描述:给一级菜单绑定了鼠标的事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。
防抖(debounce)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的规定时间
思路
1.返回一个函数,在函数中设置定时器,在定时器中执行回调函数,注意this指向的改变
2.当频繁点击的时候,如果此时已经开启定时器了说明之前触发了回调,我们需要删除定时器
3. 注意定时器的timeId不能在返回函数中定义,如果在返回函数中定义,那么每次触发回调的时候,都会重新定义。而我们的需求是对于当前触发的回调,timeId需要记录之前的结果,通过timeId来判断之前是不是已经开启了定时器。所以这里需要利用闭包实现。
//使用形式:debounce立即执行,返回了函数,返回的函数中的this指向绑定事件的DOM
window.addEventListener('scroll',debounce(()=>{},500));
//防抖函数
function debounce(callback,time){
let timeId = null; //返回函数使用了闭包,闭包会永远在内存中保存所以这个timeId 都是记录的上一次的结果
return function(event){
if(timeId!== null){//说明已经开启了一个定时器,所以需要清空上一次的
clearTimeout(timeId);
}
//console.log(this); 这里指向了dom
timeId= setTimeout(()=>{
//setTimeout 宏队列中调用callback的是window,所以这里要改变指针方向,让函数的指向指向绑定事件的DOM
callback.call(this,event)
}
,time)
}
}
节流(throttle)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的时间间隔
注意点:
1.返回函数使用了闭包,闭包会永远在内存中保存所以这个pre都是记录的上一次的结果
2.修改this的目的是让函数的指向指向绑定事件的DOM
//使用形式 这里的throttle在绑定时也直接执行了
window.addEventListener('scroll',throttle(()=>{},500))
//实现
function throttle(callback,wait){
let pre = 0; //同理利用闭包
return function(event){
const current = Date.now();//当前时间
if(current - pre > wait){
callback.call(this,event);
pre = current;
}
}
}
//按需使用
import throttle from "lodash/throttle"; //默认暴露
export default {
name: 'TypeNav',
data(){
return{
currentIndex:-1,//存储用户鼠标移动上哪一个分类的索引值,默认是-1
}
},
methods:{
changeIndex:throttle(function (value) {//throttle的回调函数不要用箭头函数,可能出现this指向问题
this.currentIndex = index;
},50)
}
}
点击分类,跳转到search路由,使用query参数categoryName = xxx & category(1|2|3)Id=xxx
。
search路由根据参数请求数据,展示在页面
路由跳转的方式
1.声明式导航:router-link
2.编程式导航:$router.push|replace
如果使用声明式导航,可以实现路由的跳转与传参
问题:出现卡顿现象
原因:如果分类是router-link是一个组件当服务器数据返回之后,会循环出很多的 router-link 组件,router-link实现组件的创建与销毁(想象keep-alive案例的两个组件的切换)
这里选择编程时导航
为了减少事件的绑定次数,将事件监听绑定在分类的最近公共元素上,利用事件委派
利用事件委派存在的问题
1.只有点击a标签时才跳转如何判断是否是a标签?是一级分类的a标签还是二级分类的a标签…还是普通a标签?
2.如何获取要传递给search路由的参数?
1.给分类的a标签添加自定义属性,在添加一个自定义属性区分是一级分类还是二级分离
<a :data-categoryName="child.categoryName" :data-category2Id="child.categoryId">{{child.categoryName}}a>
<a :data-categoryName="item.categoryName" :data-category2Id="item.categoryId">{{item.categoryName}}a>
<a :data-categoryName="em.categoryName" :data-category2Id="em.categoryId">{{em.categoryName}}a>
允许自定义属性,以data开头,data-自定义属性,以data开头的自定义属性才能被dataset函数获取到
2.利用事件源对象 event.target.dataset
获取到标签身上的属性
goSeach(event){
let node = event.target;
let {
categoryname,
category1id,
category2id,
category3id,
} = node.dataset;
if (categoryname) {
let loction = { name: "search" };
let query = { categoryName: categoryname };
//一定是a标签:一级目录
if (category1id) {
query.category1Id = category1id;
//一定是a标签:二级目录
} else if (category2id) {
query.category2Id = category2id;
//一定是a标签:三级目录
} else {
query.category3Id = category3id;
}
//判断:如果路由跳转的时候,带有params参数,捎带脚传递过去
if (this.$route.params) {
loction.params = this.$route.params;
//动态给location配置对象添加query属性
loction.query = query;
//路由跳转
this.$router.push(loction);
}
}
}
过渡动画: 前提 组件|元素 必须要有 v-if | v-show 指定才可以进行过渡动画
<transition name="sort">
<div class="sort" :v-show="show">
</div>
</transition >
<style>
//过渡动画的样式
//过渡动画开始状态(进入)
.sort-enter {
height: 0px;
}
// 过渡动画结束状态(进入)
.sort-enter-to {
height: 461px;
}
// 定义动画时间、速率
.sort-enter-active {
transition: all 0.5s linear;
}
</style>
当从home组件到search组件的时候,组件之间切换,会销毁旧组件,创建新组件。
他们的子组件TypeName组件也会重新创建实例、组件会重新挂载,所以会再次发送数据请求。
对于导航组件来说,一般都是不变的,所以我们希望只发送一次数据请求。所以可以把数据请求放在根组件,根组件只会实例化一次。