目录
Vue-cli脚手架文件组成
自带文件夹:
后期设置的文件夹:
项目其他配置:
eslint校验关闭:
src文件夹配置别名:
路由分析
全局组件:
非路由组件的业务:
路由组件业务:
vue-router的使用
路由重定向
路由跳转
路由传参
重写push和replace(项目中遇到的问题)
接口相关
postman测试接口
axios二次封装
接口统一管理
跨域问题
nprogress进度条的使用
Vuex
使用
Vuex模块开发
数据展示
添加动态背景
二三级分类显示与隐藏
给三级联动加上防抖与节流
三级联动路由跳转
三级联动的显示与隐藏
三级分类的过渡动画
防抖与节流
效果
原理
防抖的应用场景
实现:
方法一、使用lodash插件,其中包含防抖与节流业务
方法二、自己写防抖节流函数
node_modules:放置项目的依赖
public:一般放置静态资源,放在public中的静态资源在打包的时候会原封不动的打包到dist文件中
src:程序员源代码文件夹
assests:放置静态资源(一般放置多个文件共用的静态资源)webpack打包的时候会把这 里的静态资源当成一个模块打包到JS文件里面
components:放置非路由组件和全局组件
App.vue:唯一的根组件
main.js:入口组件,也是整个组件最先执行的文件
babel.config.js:配置文件(babel相关)
package.json文件:记录项目叫做什么,项目中有哪些依赖以及相应的版本
package-lock.json:缓存性文件
README.md:说明文件(怎么安装依赖,项目怎么运行,怎么打包)
vue.config.js:配置了eslint校验关闭
jsconfig.js配置了src文件别名
pages:放置路由组件
router:路由文件夹
api:放置api相关
store:Vuex相关
mock:mock数据
utils:存放一些功能函数例如获取uuid
在根目录下创建一个vue.config.js然后配置
module.exports = defineConfig({
lintOnSave: false,//关闭eslint
})
为什么要关闭?声明变量但是没有使用时,eslint检验工具会报错
创建jsconfig.json然后配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}
因为项目大的时候src(源代码文件夹):里面目录会很多,找文件不方便,设置src文件夹的别名的好处,找文件会方便一些
分为上中下结构,上下结构不会变,设置为非路由组件,中间部分会改变设置为非路由组件
分析路由组件以及非路由组件
路由组件:home,search,login,register
非路由组件:footer,header
全局组件:在多个组件中都出现的组件注册为全局组件,放在components中例如三级联动以及分页器
在main.js中import引入组件
注册全局组件:Vue.compoent(组件名,组件)
步骤:引入组件、注册组件、引入样式
v-if或v-show这里用v-show,因为切换得比较频繁
方法一:通过$route获取路径,判断路径,不建议,会很麻烦
方法二:利用路由元信息meta,通过footerShow来决定是否显示footer,通过$router.meta.来获取字段
{
path: '/register',
//路由懒加载
component: () =>
import ('@/pages/Register'),
meta: {
footerShow: false,
}
}
安装vue-router,新建router文件夹,import引入vue-router,Vue.use(VueRouter)使用
在router中引入路由组件并配置路由组件的路径
最后在main.js中引入router并注册
路由组件引用的时候用router-view,非路由组件直接使用标签
路由组件组件一般放在pages中非路由组件放在compoents中
$route:一般用来获取路由信息(路由身上的一些属性吧)例如path\query\params
$router:一般进行编程式导航进行路由跳转(使用路由身上的一些方法)例如push\replace
注册完路由之后,不论是路由组件还是非路由组件身上都会有这两种属性
将路由定义到自己想定义的页面
//一进入页面就到home组件里面
{
path: '*',
redirect: '/home'
},
声明式导航:router-link,一定要有to属性设置路径
我的订单
编程式导航:$router.push,$router.replace 声明式导航能做的,编程式导航都能做,编程式导航还能够处理一些业务逻辑
a标签中的target='_blank'代表打开一个新网页进行跳转
params参数 :属于路径中的一部分,在配置路由的时候需要事先占位,(占位方式:在设置路由的时候,路径后面加/:KEY),用对象写法的时候不能用path否则会崩溃
query参数:不属于路由的一部分,/home?k=v&a=b,不需要占位
路由传参方式
1、字符串形式:直接在跳转的路径后面加
this.$router.push("/search/"+this.params+"?k="+this.query)
2、模板字符串
this.$router.push(`/search/${this.params}?k=+${this.query}`)
3、对象写法
this.$router.push({name:'home',params:{keyword:this.keyword},query:{k:this.k})
路由传参相关面试题:
1、路由传递参数(对象写法)path是否可以结合params参数一起使用?
不可以:不能这样书写,程序会崩掉,要用name
2:如何指定params参数可传可不传?
如果占位了但是没有传递params那么url会出现问题(会导致原来要跳转的路径消失)
可以在占位时在后面加上一个问号代表这个参数可传可不传
3、params参数可以传递也可以不传递,但是如果传递是空串,如何解决?
如果传递的是空串,url同样会出现问题
用undefined解决 params:{k:''||undefined}
this.$router.push({name:'home',params:{keyword:''||undefined},query:{k:this.k})
4、 路由组件能不能传递props数据?能,有三种写法,布尔值,函数,对象
this.$router.push({ name:'home', params:{keyword:''||undefined}, query:{k:this.k}, //1、布尔值形式可以让params参数成为组件的参数 //接收组件直接用props就可以接收 props:true, //2、对象写法:额外给路由组件传递参数 props:{a:1,b:2}, //3、函数写法:可以将params参数和query参数成为组件的参数 props:{($route)=>{return {keyword:$route.params.keyword,k:$route.query.k} }} })
编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误
原因:vue-router3.1.0之后, 引入了promise的语法,$router.push和$router.replace如果没有通过参数指定成功或者失败回调函数就返回一个promise来指定成功/失败的回调,且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
解决方法:
1、在每次调用的时候都传入成功或失败回调,或者catch捕获错误
this.$router.push({
name: "search",
params: { keyword: this.keyword || undefined},
query: { keyword1: this.keyword.toUpperCase() },
}).catch(() => {});
但是每次使用的时候都要写,治标不治本
2、直接修正Vue原型上的push和replace方法(推荐)
在router文件中,首先保存原来的方法,然后修改VueRouter原型上的push和replace方法,传递三个参数location:原先的参数(路径和参数) ;resolved:成功回调;rejected:失败回调。如果有成功和失败回调传入,就直接传入原先的push和replace,如果没有就手动传入两个回调()=>{}。
注意,在调用原方法的时候要用call改变上下文,因为要让组件实例来调用这个方法,如果不用call的话就这个函数的上下文就变成window了
在组件中调用this.$router.push的时候this指的是组件实例,$router和$route都是在注册路由的时候添加在组件上的属性
push和replace是VueRouter原型上的方法
//将原有的push方法地址,保存起来,后期还能拿到原来的
const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace
//可以大胆的去修改原型的push,让原型的push指向另外一个函数
VueRouter.prototype.push = function(location,onResolved,onRejected){
//location就是我们调用 this.$router.push,传递过来的对象
if(onResolved === undefined && onRejected === undefined){
//证明调用的时候只传递了个匹配路由对象,没有传递成功或者失败的回调
return originPush.call(this,location).catch(() => {})
}else{
//证明调用的时候传递了成功或者失败的回调,或者都有
return originPush.call(this,location,onResolved,onRejected)
}
}
VueRouter.prototype.replace = function(location,onResolved,onRejected){
//location就是我们调用 this.$router.replace,传递过来的对象
if(onResolved === undefined && onRejected === undefined){
//证明调用的时候只传递了个匹配路由对象,没有传递成功或者失败的回调
return originReplace.call(this,location).catch(() => {})
}else{
//证明调用的时候传递了成功或者失败的回调,或者都有
return originReplace.call(this,location,onResolved,onRejected)
}
}
postman可以测试接口能不能正常使用,选择请求方式并输入接口地址就能测试接口能否正常使用,如果返回的code字段是200则表示成功
向服务器发请求的方式:XMLHttpRequest,fetch,JQ,axios
项目当中常用api文件夹放置api相关的东西,创建request.js重新配置axios
//这个request其实就是axios
const request=axios.create({
//配置基础路径
baseURL:'/api',
//配置请求超时的时间
timeout:5000,
})
为什么要二次封装?为了实现请求拦截器和响应拦截器
请求拦截器:在发送请求之前可以做一些处理
//请求拦截器可以在请求发出之前检测到,并进行一些处理
requests.interceptors.request.use((config) => {
//config是配置对象,身上有个属性很重要,headers请求头
return config;
})
响应拦截器:数据返回之后进行一些处理
//响应拦截器
requests.interceptors.response.use((res) => {
//成功的回调函数
return res.data;
}, (error) => {
//失败的回调函数
return Promise.reject(new Error('faile'));
});
记得对外暴露
项目小的时候:可以在组件的生命周期函数中发送请求
项目大的时候:在api文件夹下创建index.js对项目接口进行统一管理
export const reqSubmitOrder = ((tradeNo, data) => requests({ url: `/order/auth/submitOrder?tradeNo=${tradeNo}`, method: 'post', data }))
处理方式:配置代理服务器
在vue.config.js文件中配置
//代理跨域
devServer: {
proxy: {
//只要路径中有api就去找target的服务器要东西
'/api': {
target: 'http://gmall-h5-api.atguigu.cn',
}
}
}
原理:
当浏览器向服务器发起请求时,即使存在跨域问题,服务器依旧是会响应请求,并返回数据给浏览器,但当浏览器拿到数据后发现存在跨域问题了,这时候浏览器就不会将数据给页面,相当于把数据给扣留了。而代理服务器与前端页面同源,由他返回的数据就不存在跨域的问题,如图。 (备注:代理服务器与服务器之间是非同源,但不存在跨域问题,是因为服务器之间采用的是http请求,而不是ajax技术)
nginx (相对繁琐,且需要一定的后端知识)
proxy:(相对简单,熟悉一定的webpack配置即可) ,可以配置单个或多个代理
安装nprogress,在api文件夹下的request.js中import引入,引入进度条样式
//引入进度条
import nprogress from "nprogress";
//引入进度条样式,可以在这个文件中改变样式
import 'nprogress/nprogress.css'
在请求拦截器和响应拦截器中使用,start进度条开始,done进度条开始
requests.interceptors.request.use((config) => {
//进度条开始动
nprogress.start();
//config配置对象,包含header请求头
return config;
})
//响应拦截器
requests.interceptors.response.use((res) => {
//成功的回调函数
//进度条结束
nprogress.done();
return res.data;
}, (error) => {
//失败的回调函数
return Promise.reject(new Error('faile'));
});
什么是Vuex:官方提供的一个插件,状态管理库,集中管理项目公共的数据 ,
项目比较大的时候才用,项目小完全不需要
安装vuex(记得@3.6.2),新建store文件夹,引入Vue(依赖于Vue所以要引入Vue)和Vuex,使用Vuex插件,在main.js中引入store,引入后组件身上会多一个$store属性
获取store中的数据,直接$store获取或者用map(数组、对象【value是函数的形式】)
将大仓库拆分成小module ,记得打开命名空间
import trade from './trade'
export default new Vuex.Store({
modules: {
trade
}
})
就是三连环:在mounted里面dispatch,在actions中拿调用api文件中设置的接口函数,向服务器发送请求得到返回的数据,并进行处理,返回成功则提交mutation修改state中的数据(state中的数据默认初始值应该根据返回数据的类型来设置),在组件中获取数据,最后展示数据,展示数据的时候常用v-for循环
方法一::hover
方法二、data中设置一个值x,鼠标没有在任何一个一级标签的时候设置为-1,鼠标在哪个一级标签上就赋值为哪个标签的index,添加动态类名,当x==index的时候就加上这个类,当鼠标移开的时候x=-1
要求:当鼠标离开‘全部商品分类’的时候背景颜色才消失
使用事件的委派【在三级联动路由跳转的时候也有用到】,在一级列表和全部商品分类外面再套一个盒子,然后把鼠标移除事件放在外面的父盒子里面
事件委派的定义就是,把原来加给子元素身上的事件绑定在父元素身上,就是把事件委派给父元素。换句话说,就是把一个本来让下属干的事情交给上司做了。这样做的好处就是不在进行查询,减少了DOM操作,极大地改善了代码性能,它既可以为动态添加的元素附上效果,也可以改善代码性能
方法一、:hover,鼠标移上就display:block否则display:none
方法二、js动态添加样式
给三级联动加上防抖与节流
防止出现卡顿,使用lodash,Vue2框架自带lodash,直接引入就行。lodash对外暴露的是一个下划线_,直接import _ from 'lodash'是全部引入,最好按需引入import throttle from 'lodash/throttle'
//节流
changBackground: throttle(function(index){
this.currentIndex=index;
},50)
三级联动路由跳转
在路由跳转的时候会把选择产品的信息进行传递
因为router-link是一个组件,当服务器数据返回之后循环出很多的router-link组件,很耗内存,会出现卡顿,最好用编程式导航 但是如果直接在a标签上面用编程式导航也会导致大量绑定,所以可以用事件委派将事件绑定在父盒子上面。
事件委派存在的问题:如何识别a标签?如何获取参数?使用自定义属性,在所以的a标签中都设置一个:data-categoryName="c1.categoryName",一级标签为c1二级c2三级c3,判断元素身上是否有categoryname【注意小写】属性就可以判断是否为a标签,再添加一个自定义标签categoryId来分辨一级二级三级标签
{{c1.categoryName}}
自定义属性设置 :在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放。这里的data-前缀就被称为data属性 ;我们可以通过dataset来获取data属性,在dataset中属性名会自动变为小写
三级路由跳转使用编程式导航+事件委派+自定义标签的形式实现
三级联动的显示与隐藏
在home页面会一直显示,但是search中不会,所以在typenav挂载完时判断是在哪个路由下,如果在search那就隐藏typenav,并且鼠标移到商品时才显示,鼠标移出的时候又隐藏
三级分类的过渡动画
动画效果:在要用动画效果的一个元素外面套上transition标签,并且元素上一定要有v-show或者v-if标签
V-enter 进入的起点动画效果
V-enter-to 进入的终点动画效果
V-leave 离开的起点动画效果
V-leave-to 离开的终点动画效果
V-enter-active 进入时激活的样式
V-leave-active 离开时激活的样式
当有多个动画效果时可以给元素取name,然后把v换成name
当要包含多个元素的时候要用transition-group标签
还可以使用animate库
.sort-enter,.sort-leave-to{
height: 0px;
}
.sort-leave,.sort-enter-to{
height: 461px;
}
.sort-enter-active{
transition: 0.1s linear;
}
.sort-leave-active{
transition: 0.1s linear;
}
}
防抖与节流
用户行为过快会导致浏览器反应不过来,如果当前回调函数中有一些大量的业务,有可能会出现卡顿现象,因此需要防抖和节流
效果
防抖:前面所有的触发事件都被取消,最后一次执行在规定时间之后才会触发,也就是说快速连续触发时只会执行一次
节流:在规定的时间间隔范围内不会重复触发回调,只要大于这个时间间隔才会触发回调,把频繁触发转变为少量触发
原理
防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 然后重新设置超时调用,即重新计时。这样一来,只有最后一次操作能被触发。
节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
防抖的应用场景
( 1 ) 用户在输入框中连续输入一串字符,只会在输入完成后取最终输入的内容。然后发送ajax请求,这样可以有效地减少请求次数,节约请求资源。
( 2 ) window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖函数让其只触发一次。
节流的应用场景
( 1 ) 鼠标连续不断地触发某事件(如点击),在单位时间内只触发一次。
( 2 ) 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。
( 3 ) 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
实现:
方法一、使用lodash插件,其中包含防抖与节流业务
防抖:_.debounce(function(){},1000);其中function(){}为要防抖的函数,1s后执行,用户操作频繁但是只执行一次
节流:_.throttle(function(){},1000) 用户操作频繁,把频繁操作变为少量操作
Vue2框架自带lodash直接引入就可以
方法二、自己写防抖节流函数
防抖:分为立即执行版本和非立即执行版本
防抖函数中获得argument并使用apply改变this是为了让 debounce 函数最终返回的函数 this 指向不变(否则this会指向window)以及依旧能接受到 event 参数(这里接收的arguments其实就是event参数)。
非立即执行:触发事件后不会立即执行而是在n秒后执行
let input = document.querySelector('input')
let a = 'a';
input.oninput = debounce(function(a) {
console.log(1)
}, 1000)
function debounce(fn, delay) {
let time = null;
function _debounce() {
let args = [...arguments];
clearTimeout(time);
time = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
return _debounce
}
立即执行,触发后会先立即执行一次
let input = document.querySelector('input')
let a = 'a';
input.oninput = debounce(function(a) {
console.log(1)
}, 1000, true)
function debounce(fn, delay, isImmediate = true) {
let time = null;
let isFirst = true
function _debounce() {
let args = [...arguments]
if (time) {
clearTimeout(time)
}
if (isFirst && isImmediate) {
fn.apply(this, args);
isFirst = false;
}
time = setTimeout(() => {
fn.apply(this, args)
isFirst = true;
}, delay)
}
return _debounce
}
节流:有两个版本,时间戳和定时器
时间戳版本
function throttle(fn, wait) {
let lastTime = 0;
function _throttle() {
let nowTime = Date.now();
let args = [...arguments]
if (nowTime - lastTime > wait) {
fn.apply(this, args);
lastTime = nowTime;
}
}
return _throttle
}
定时器
function throttle(fn, wait) {
let lastTime = 0;
function _throttle() {
let nowTime = Date.now();
let args = [...arguments]
if (nowTime - lastTime > wait) {
fn.apply(this, args);
lastTime = nowTime;
}
}
return _throttle
}
时间戳版本是在触发时立马就执行,而定时器版本是在触发一段时间后再执行
参考内容