vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡

文章目录

  • vue2项目实战尚品汇前台02
    • 向服务器请求数据
      • apifox 测试接口
      • axios 二次封装 √
        • API接口统一管理
      • nprogress进度条
    • vuex状态管理器
      • vuex的模块化开发 √
      • TypeNav模块完成
        • 动态三级联动数据
        • 动态添加背景颜色
          • 事件委派
        • 通过JS控制二三级商品分类的显示与隐藏
        • 卡顿现象 ?
          • 防抖与节流 js性能优化 闭包
          • 按需引入lodash工具库封装的节流操作
        • 实现三级联动路由跳转与传参 (编程式导航+事件委派) √
          • 如何判断给标签添加了自定义属性
        • search中的TypeName组件动画过渡效果
        • 只发送一次数据请求 性能优化 √

vue2项目实战尚品汇前台02

√ 面试时可以提到的点
?项目中遇见的问题

向服务器请求数据

apifox 测试接口

如测试http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList
vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第1张图片
1.如果服务器返回的数据code字段200,代表服务器返回数据成功
2.整个项目,接口的前缀都有/api字样

axios 二次封装 √

为什么需要进行二次封装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;

API接口统一管理

对外暴露一个函数,只要外部调用这个函数,就向服务器发送ajax请求。当前函数执行后需要把服务器返回结果返回。
axios发送请求返回结果是Promise对象

import requestAxios from "./request";

//三级联动接口请求,实际请求的是/api/product/getBaseCategoryList
export const reqgetCategoryList = () =>requestAxios.get(`/product/getBaseCategoryList`);

请求时报错了!
vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第2张图片
解决办法
在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' //这里写的服务器的地址
      }
    }
  }
})

nprogress进度条

当客户端发送请求后,进度条开始显示。
当服务器返回完数据后,进度条消失。

1.插件安装

cnpm i --save nprogress

2.插件使用的位置

在发送请求时,进度条开始显示,当服务器返回数据后进度条消失。
适用于所有的请求,所以可以在拦截器中使用。

引入的nprogress是一个对象

  • start方法表示进度条开始
  • done方法表示进度条结束。

注意这里还需要引用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状态管理器

vuex是官方提供一个插件,状态管理库,集中式管理项目中组件共用的数据。

# 安装vuex3版本
cnpm i --save vuex@3

vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第3张图片

  • state:仓库存储数据的地方
  • mutations: 直接修改state
  • action:用于响应组件中的dispatch动作,通过commit触发mutations的方法
  • getters:用于对state中的数据进行加工,帮助我们映射getters中的数据为计算属性,让组件获取仓库数据更加方便

vuex的模块化开发 √

思路:根据不同的业务,将该业务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
    }
})

vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第4张图片

TypeNav模块完成

动态三级联动数据

当组件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链
})

vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第5张图片
所以发送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']);//展开就是上面的写法
  }

动态添加背景颜色

当鼠标移动到对应的选项时,对应选项背景颜色边变蓝vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第6张图片
方法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}">
事件委派

需求
vue2项目实战尚品汇前台02 axios二次封装-事件委派-动态添加属性-自定义属性-组件的动画过渡_第7张图片

鼠标移除时取消样式,也就是将currentIndex置为-1。
首先选项和红色背景的结构关系是兄弟关系,所以我们可以利用事件委派来实现。把鼠标移除的监听绑定在他们共同的父类上

事件委派
将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上。好处 ①减少内存占用(事件监听的回调变少) ②动态添加的内部元素也能响应

通过JS控制二三级商品分类的显示与隐藏

原始做法
通过CSS样式display:block | none 显示与隐藏二三级商品分类

通过JS控制
给二三级分类添加动态样式:style="{display:currentIndex==index?'block':'none'}"
或者直接使用v-show=”currentIndex==index“

卡顿现象 ?

引出防抖和节流,可以说通过下列现象想到的xxx场景。

现象描述:给一级菜单绑定了鼠标的事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。

防抖与节流 js性能优化 闭包
  • 节流(throttle):控制事件执行的时间间隔,在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
    • 适合多次事件按时间做平均分配触发:窗口调整(resize)+ 页面滚动(scroll)等
  • 防抖(debounce):在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
    • 适合多次事件一次响应的情况:输入框实时搜索联想(keyup/input)

防抖(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;		
		}
	}
}
按需引入lodash工具库封装的节流操作
//按需使用
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);
        }
      }
    }

search中的TypeName组件动画过渡效果

过渡动画: 前提 组件|元素 必须要有 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组件也会重新创建实例、组件会重新挂载,所以会再次发送数据请求。

对于导航组件来说,一般都是不变的,所以我们希望只发送一次数据请求。所以可以把数据请求放在根组件,根组件只会实例化一次。

你可能感兴趣的:(Vue,前端,javascript,vue.js)