目录
11.2 在项目中,经常会有API文件夹【里面存放的就是axios请求】
14.3 this.$state.dispatch与this.$state.commit的主要区别
21.2 用watch + this.$neckTick(较为完美的解决方案)
24.1 Search模块中子组件SearchSelector.vue的动态开发
37.1 放大镜操作——ImageList组件和Zoom组件之间的小操作
39.2 路由传递参数结合会话存储(成功路由跳转与参数传递)
五十四. 表单验证(插件:vee-validate)【了解即可,看懂就行】
VUE2项目尚品汇笔记
将Typora记录的笔记、以及手写的笔记进行二次整理。
整理完本次笔记,也会继续将之前学习HTML、CSS、JS、Vue的笔记进行二次整理。
方便以后查找,同时有需要改正或补充的,还请大家指教。
一、vue-cli脚手架初始化项目各文件夹
node_modules文件夹:项目依赖文件夹
public文件夹:一般放置一些静态资源(图片等)。注:放在public文件夹中的静态资源,当webpack进行打包时候,会原封不动打包到dist文件夹中。
src文件夹(程序员源代码文件夹):
assets文件夹:一般放置静态资源(一般放置多个组件共用的静态资源)。注:放置在assets文件夹里的静态资源,在webpack打包时,会把它当做一个模块,打包到JS文件里。
components文件夹:一般放置的是非路由组件(全局组件)。
App.vue:唯一的根组件,Vue当中的组件(.vue)。
main.js:程序入口文件,也是整个程序当中最先执行的文件。
babel.config.js:配置文件(babel相关)。
package.json文件:类似于项目的‘身份证’,记录项目叫什么,当中有哪些依赖,如何运行。
package.lock.json文件:缓存性文件。
README.md文件:说明性文件。
二、 项目的其他配置
2.1 项目运行,让浏览器自动打开
在package.json文件中配置
{
......
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
......
2.2 eslint校验功能(各种规范的报错)的关闭
eg:声明了变量,但未进行使用,则eslint校验工具会报错
- 在根目录下创建文件vue.config.js
module.exports = { //关闭eslint lintOnSave: false }
2.3 src文件夹简写方法,配置别名
①.jsconfig.json配置别名,可用@提示
【@代表的是src文件夹,常在js文件中使用。"exclude"表示“除了......”,意为配置的别名不能在"node_modules"和"dist"使用】
{
"compilerOptions":{
"baseUrl":"./",
"paths":{
"@/*":["src/*"]
}
//表示不能在"node_modules"和"dist"中使用这种配置的别名。
"exclude":["node_modules","dist"]
},
②.也可在.css中配置别名@提示 【在@前面加上~这个符号】
<style>
......
background-image: url(~@/assets/images/icons.png);
......
</style>
2.4 样式的引入
在public文件夹下的index.html中引入同级reset.css样式
<link rel="stylesheet" href="/reset.css">
三. 本次项目路由的分析
vue-router 前端的路由:KV键值对。
key:URL(地址栏中的路径) value:相应的路由组件 注:本次项目是上中下结构
- 本次项目的路由组件可分为:
Home首页路由组件、Search搜索路由组件、login登录路由组件、Refister注册路由组件
- 本次项目的非路由组件可分为:
Header组件:【存在于首页、搜索页】 Footer组件:【存在于首页、搜索页,但登录和注册页面没有】
四. 完成非路由组件Header与Footer业务
本次项目主要关注业务、逻辑。
- 开发项目步骤:
- 书写静态页面(HTML+CSS)(本次项目已提前准备好了)
- 拆分组件(静态组件、路由组件等)
- 获取服务器的数据动态展示
- 完成相应的动态业务逻辑(eg:Js动态业务等)
- 注意:
- 创建组件=组件结构 + 组件的样式 + 图片资源。
- 本次采用less样式。
需通过less、less-loader【安装版本五npm install --save less less-loader@5
】进行处理less,将其变为css样式。
若想让组件识别less样式,需在style标签上加上lang=less。<style lang="less" scoped>
五. 路由组件的搭建
经过上面的分析,本次项目的路由组件有四个:
Home首页、Search搜索、Login登录、Register注册。
components文件夹:经常放置非路由组件(共用的全局组件).
pages或views文件夹:经常放置路由组件
5.1 配置路由
项目当中配置的路由一般放置在router文件夹中的index.js里。路由配置好之后在入口文件main.js中引入。
5.2 路由组件与非路由组件的区别?
- 共同点:
注册完路由,不管是路由组件、还是非路由组件,它们身上都会有$route、$router属性。
$route:一般获取路由信息【路径、query、params等】
$router:一般进行编程式导航,进行路由跳转【push或replace(其中一个是有缓存记忆的)】
- 不同点:
路由组件 | 非路由组件(普通组件) |
一般放置在pages或views文件夹中 | 一般放置在components文件夹中。 |
一般需要在router文件夹中进行注册 (使用的名字即为组件的名字,例如router文件中的index.js的写法) 在App.vue中展示时, 用的是<router-view></router-view> |
一般以标签的形式使用 (例如App.vue里的<Header></Header>标签)。 |
5.3 路由的跳转的两种形式
① 声明式导航:router-link
② 编程式导航:push或replace
编程式导航:声明式导航能做的,编程式导航都能做;而且编程式导航不仅可以进行路由跳转,还做一些其他业务逻辑。
5.4 重定向
redirect重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
在router的index.js里书写重定向代码:
//redirect重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
//配置路由,路由的单词都是小写的
export default [
......
{
path: '*',
redirect: './home'
}
......
]
六. Footer组件显示与隐藏
6.1 v-if和v-show的区别
显示or隐藏,可用v-if或者v-show。两者都是动态显示DOM元素。
v-if初始化较快,但代价较高;v-show初始化较慢,但切换成本低。
v-if | v-show |
---|---|
动态向Dom内添加或删除DOM元素 | 通过设置DOM元素的display样式属性控制显示或隐藏 |
切换有一个局部编译/卸载的过程,过程中可能会销毁和重建内部的事件监听和子组件。 | 单纯地基于css切换 |
惰性的; 初始值为假时,不作任何操作;只有在条件第一次为真时才开始局部编译。被缓存后,再次切换时进行局部卸载。 |
无论首次条件是否为真,都可被编译;被缓存且DOM元素保留。 |
有更高的切换消耗 | 有更高的初始化渲染消耗 |
适合运营条件不大可能改变的场景 | 适合频繁切换的场景 |
6.2 Footer组件编写
本次项目在Home首页、Search搜索显示Footer组件,但是在Login登录、Register注册时隐藏Footer组件。
可根据组件身上的$route获取当前路由的信息,通过路由路径判断Home和Footer这俩非路由组件的显示与隐藏。
<!-- 写法一(不推荐): -->
<Footer v-show="$route.path=='/home'||$route.path=='/search'"></Footer>
配置路由时,可给路由添加路由元信息【meta】,注意路由配置对象的key不能乱写。
在APP.vue文件中:
<!-- 写法二: -->
<Footer v-show="$route.meta.show"></Footer>
在router文件的index.js中配置【meta】:
//配置路由
export default new VueRouter({
routes: [{
path: "/home",
component: Home,
meta: { show: true }
},{
path: "/search",
component: Search,
meta: { show: true }
},{
path: "/login",
component: Login,
meta: { show: false }
},{
path: "/register",
component: Register,
meta: { show: false }
},
//重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
{
path: '*',
redirect: './home'
}
]
})
七. 路由传参
了解URL语法格式
https://blog.csdn.net/Thaley/article/details/122286201
格式:protocol :// hostname[:port] / path / [;parameters][?query]#fragment
[ ]中的内容可有可无
即:协议 :// 主机名[:端口号] / 路径 / [;参数][?查询]#信息片断
7.1 路由跳转的两种形式
eg:A -> B
①声明式导航:router-link(务必要有to属性),<router-link to="......"></router-link>可实现路由跳转。
②编程式导航:利用组件实例的$router.push或replace方法,可实现路由的跳转,也可书写一些自己的业务逻辑。
7.2 路由传参(params、query)
①params参数:属于路径的一部分,需注意,在配置路由的时候,需要占位。
如路径的path:"/search/:keyword",/:keyword即为params参数的占位符。
②query参数:不属于路径的一部分,类似于ajax中的queryString /home?k=v&kv=, 不需占位。
params参数 | query参数 |
属于路径的一部分,需要占位 | 不属于路径的一部分, 不需占位 |
用name来引入路由 | 用path来引入路由 |
取值用法:this.$route.params.name | 取值用法this.$route.query.name |
有些类似于post,在浏览器地址栏中不显示参数,所以params传值相对安全一些。 | 类似于我们ajax中get传参,在浏览器地址栏中显示参数。 |
传值一刷新就没。 | 传值刷新还存在。 |
7.3 路由传参相关问题
问1. 路由传参(对象的写法)path是否可以结合params参数一起使用?
不能。路由跳转传参时,对象写法可为name、path形式,但是path这种写法不能与params参数一起使用。而且,路径参数缺失则无法匹配path里的占位符。
//错误写法【错误写法!】
this.$router.push({
path:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
//应改写成:
this.$router.push({
name:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
问2. 如何指定params参数可传可不传?
若路由要求传params参数,但未传,则URL会产生问题。
- 未传递params参数时,得到的地址情况:
......
methods:{
this.$router.push(
{name:"search",
query:{
k:this.keyword.toUpperCase()//由其他程序可得,调用toUpperCase()返回ABC
}
}
)
},
......
// 上面的代码,得到的地址为:http://localhost:8080/#/?k=ABC 此地址缺少了/search。
- 传递了params参数时,得到的地址情况:
......
methods:{
this.$router.push(
{name:"search",
params:{
keyword:this.keyword},
query:{
k:this.keyword.toUpperCase()//由其他程序可得,调用toUpperCase()返回ABC
}
}
)
},
......
// 则得到的地址为:http://localhost:8080/#/search/abc?k=ABC 此地址有/search
如何指定params参数可传或可不传?
在配置路由(router文件中的index.js时),改变path,在占位的后面加上一个问号?
即:path: "/search/:keyword?"
【这里的?表示params可传递或可不传递(和正则表达式雷同,?代表出现次数为0次或1次,即可有可无)】
//配置路由
export default new VueRouter({
//配置路由
routes: [
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search"
},
]
})
问3. params参数可传递也可不传递,但若传递空字符串,如何解决?
params传递空字符串也会产生URL问题。可通过在params传递的空字符串后面加上||undefined
进行解决。
......
methods:{
this.$router.push(
{name:"search",
params:{
keyword:''||undefined},
query:{
k:this.keyword.toUpperCase()
}
}
)
},
......
问4. 路由组件能否传递props数据?若有,有几种?
可以。有三种写法:
this.$router.push({
path:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
- 方式一:props用布尔值,但只能传递params
props: true
- 方式二:对象写法。额外给路由组件传递一些props
props: { a: 1, b: 2 }
- 方式三:函数写法。可通过props传递给路由组件params参数、query参数。
props: ($route) => {
return { keyword: $route.params.keyword, k: $route.query.k }
}
点击当前页的某个按钮跳转到另外一个页面去,并将某个值带过去
<div class="examine" @click="insurance(2)">查看详情</div>
- 第一种方法:页面刷新数据不会丢失。直接调用$router.push 实现携带参数的跳转。
methods:{ insurance(id) { //直接调用$router.push 实现携带参数的跳转 this.$router.push({ path: `/particulars/${id}`, }) }
需要对应路由配置如下:
{ path: '/particulars/:id', name: 'particulars', component: particulars }
可以看出需要在path中添加/:id来对应 $router.push 中path携带的参数。在子组件中可以使用来获取传递的参数值
另外页面获取参数如下this.$route.params.id
- 第二种方法:页面刷新数据会丢失。通过路由属性中的name来匹配路由,通过params来传递参数。
methods:{ insurance(id) { this.$router.push({ name: 'particulars', params: { id: id } }) }
对应路由配置: 注意这里不能使用:/id来传递参数了,因为组件中,已经使用params来携带参数了。
{ path: '/particulars', name: 'particulars', component: particulars }
子组件中: 这样来获取参数
this.$route.params.id
- 第三种方法:使用path来匹配路由,然后通过query来传递参数。
这种情况下 query传递的参数会显示在url后面?id=?
methods:{ insurance(id) { this.$router.push({ path: '/particulars', query: { id: id } }) }
对应路由配置:
{ path: '/particulars', name: 'particulars', component: particulars }
对应子组件: 这样来获取参数
this.$route.query.id
八. 重写push和replace
--路由跳转的两种形式:①.声明式导航、②.编程式导航
为什么编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?
--声明式导航没有这类问题,因为vue-router底层已经处理好了。
8.1 为什么编程式导航进行路由跳转时,会有这种警告错误?
因为最新版本的vue-router引入了promise,promise需要传递成功和失败两个参数。
1.而push的返回是一个promise,所以通过给push方法传递相应的成功、失败的回调函数,可捕获到当前错误,可以解决。
- 方法一(治标不治本,在将来的组件当中push或replace,编程式导航还是会有类似警告错误):
this.$router.push({
name:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
},()=>{},()=>{})
// ()=>{},()=>{}表示执行成功和执行失败的回调函数
2.通过底部的代码,也可实现解决错误。
- 方法二(在router文件夹中的index.js中):
//若有成功和失败的回调,有则返回这俩值,无则自己手写() => {}
//先把VueRouter原型对象的push先保存一份
let originPush = VueRouter.prototype.push;
//先把VueRouter原型对象的replace先保存一份
let originReplace = VueRouter.prototype.replace;
//重写push|replace
//第一个参数location:告诉原来的push方法,你往哪里跳转(传递哪些参数)
//第二个参数resolve:成功回调。
//第三个参数reject:失败回调。
//call|apply区别
//相同点:都可调用函数一次,都可篡改函数的上下文一次。
//不同点:call与apply传递参数:call传递参数用逗号隔开,apply方法执行,传递数组。
VueRouter.prototype.push = function(location, resolve, reject) {
if (resolve && reject) {
originPush.call(this, location, resolve, reject);
} else {
originPush.call(this, location, () => {}, () => {})
}
}
VueRouter.prototype.replace = function(location, resolve, reject) {
if (resolve && reject) {
originReplace.call(this, location, resolve, reject);
} else {
originReplace.call(this, location, () => {}, () => {})
}
}
九. Home首页组件拆分业务分析
本次项目的Home首页被拆分为了七个部分。
<template>
<div>
<!-- 三级联动全局组件 -->
<!-- 不需要再用import引入三级联动了,因为已经是全局组件了,可以直接使用 -->
<TypeNav />
<!-- 轮播图列表 -->
<ListContainer />
<!-- 今日推荐 -->
<TodayRecommend />
<!-- 商品排行 -->
<Rank />
<!-- 猜你喜欢 -->
<Like />
<!-- 楼层 -->
<Floor />
<!-- 商标 -->
<Brand />
</div>
</template>
<script>
//引入其余的组件
import ListContainer from "@/pages/Home/ListContainer";
import TodayRecommend from "@/pages/Home/TodayRecommend";
import Rank from "@/pages/Home/Rank";
import Like from "@/pages/Home/Like";
import Floor from "@/pages/Home/Floor";
import Brand from "@/pages/Home/Brand";
export default {
name: "Home",
components: {
ListContainer,
TodayRecommend,
Rank,
Like,
Floor,
Brand,
},
};
</script>
完成步骤:
-- 先把静态(css、html)页面完成。
-- 拆分出静态组件。
-- 获取服务器的数据进行展示。
-- 若有js的动态业务,则将其完成即可。
9.1. TypeNav的三级联动全局组件
——由于三级联动,在Home、Search、Detai都有使用,故把三级联动注册为全局组件。优点:只需注册一次,即可在项目任意地方使用。
——本次讲解的组件是属于Home模块下的组件,所以暂时在pages的Home文件夹下新建个文件夹(这里取名为TypeNav)
9.2. 完成其余静态组件
需要耐心、需要注意:
HTML + CSS + 图片静态资源 —————信息的【结构、样式、图片资源】,
其文件的名字、文件引用路径需要一一对应
十. POSTMAN测试接口
利用postman工具测试接口是否正常
--通过postman工具测试,接口无问题。
--若服务器返回的数据code字段为200(此项目设置的),代表服务器返回数据成功。
--整个项目,接口前缀会有/api字样
十一. axios二次封装
npm官网中的axios文档:axios - npm
axios文档网:axios中文文档|axios中文网 | axios
封装的方法有:XMLHttpRequest、fetch、JQ、axios
本次项目在api文件夹下的request.js文件进行axios的二次封装。
11.1 为什么需要进行二次封装axios?
为了请求拦截器、响应拦截器。
**请求拦截器**:可以在发请求之前,可处理一些业务。
**响应拦截器**:当服务器数据返回后,可处理一些事情。
安装axios : npm install axios
11.2 在项目中,经常会有API文件夹【里面存放的就是axios请求】
使接口中,路径都带有/api:*baseURL:"/api"
//利用axios对axios进行二次封装
//故先引入axios
import axios from 'axios'
//1.利用axios对象的方法create,创建一个axios实例
//2.request就是axios,但需要稍微进行配置。
const requests = axios.create({
//配置对象
//baseURL基础路径,发请求时,路径当中会出现api
baseURL: "/api",
//timeout代表请求超时的时间
timeout: 5000,
});
//请求拦截器:可以在发请求之前,可处理一些业务。
requests.interceptors.request.use((config) => {
//config:配置对象,对象中有一个属性很重要(即:headers请求头)
return config;
})
//响应拦截器
requests.interceptors.request.use((res) => {
//响应成功的回调函数:服务器响应数据回来之后,响应拦截器可检测到,可做一些事情
return res.data;
}, (error) => {
//响应失败的回调函数
return Promise.reject(new Error('faile'));
});
//对外暴露
export default requests;
十二. API接口统一管理
当项目很小时:完全可以在组件的生命周期函数中发请求。
当项目很大时:axios.get('xxx')
接口一般写在API文件夹的index.js文件中。
//当前模块的功能:API接口统一管理
//用到了二次封装的requests,需要将其引入
import requests from "./request";
//三级联动接口
//WORD接口文档中已经说明三级联动的接口为
// /api/product/getBaseCategoryList methods为get 无参数
//发请求:axios发请求返回的结果是Promise对象
export const reqCategoryList = () => {
//二次封装时,已经写了/api,所以这里不用再写/api了
return requests({
url: '/product/getBaseCategoryList',
method: 'get' })
}
//上面代码=>箭头函数的简写形式
/* export const reqCategoryList = () => requests({
url: '/product/getBaseCategoryList', method: 'get'
}) */
export const reqCategoryList = () => requests.get('/product/getBaseCategoryList')
写好接口,即可再书写仓库中的内容
12.1 跨域问题
(服务器与服务器之间是没有跨域问题的,只有浏览器与浏览器之间才有)
问1:什么是跨域?
协议、域名、端口号不同请求,称之为跨域。
`webpack.config.js`文件实质就是`vue.config.js`
`target`是要获取的那台服务器的IP地址
问2:跨域的解决方案是什么?
JSONP方法、CROS方法、代理(Proxy)方法【较为常用】
十三. nprogress进度条的使用
安装nprogress:npm install --save nprogress
13.1 打开页面时,页面上方显示的进度条。
start:进度条开始
done:进度条结束
在API文件夹里的request.js文件中写:
//利用axios对axios进行二次封装
//故先引入axios
import axios from 'axios'
//①.引入进度条
import nprogress from 'nprogress';
// console.log(nprogress),输出的内容其中start:表示进度条开始;done:表示进度条结束
//②.引入进度条样式
import "nprogress/nprogress.css";
//1.利用axios对象的方法create,创建一个axios实例
//2.request就是axios,但需要稍微进行配置。
const requests = axios.create({
baseURL: "/api",
timeout: 5000,
});
requests.interceptors.request.use((config) => {
nprogress.start() //③.进度条开始动
return config;
})
//响应拦截器
requests.interceptors.response.use((res) => {
nprogress.done() //③.进度条结束
return res.data;
}, (error) => {
return Promise.reject(new Error('faile'));
});
//对外暴露
export default requests;
进度条的颜色可修改,在nprogress.css中修改即可。
十四. Vuex状态管理库
14.1 vuex是什么?
是官方提供的一个插件,状态管理库,集中式管理项目中组件共用的数据。
但并不是全部项目都需要Vuex,若项目很小,完全不需要;若项目很大、组件很多、数据很多,数据维护很费劲,则需要使用Vuex。Vuex可以模块化开发。
安装Vuex:npm install --save vuex
- 配置仓库Vuex:在src文件夹下新建一个store的文件夹,再里面再建一个index.js文件。
- 在入口文件main.js中引入建好的仓库
- 在使用到该仓库的组件中,引入{mapState} from 'vuex'映射到组件身上,成为组件的数组
- 三连环:在actions里面提交mutations,让mutations进来修改state。
14.2 Vuex的四个大核心概念
- state:仓库存储数据的地方。
const state = {};
- mutation:修改state的唯一手段。
const mutations = {};
- action:处理action,可书写自己的的业务逻辑,也可处理异步。
const actions = {};
- getters:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便。
const getters = {};
//写完接口,接着就写小仓库
import { reqCategoryList } from '@/api';
const state = {};
const mutations = {};
const actions = {};
const getters = {};
//默认暴露
export default {
state,
mutations,
actions,
getters
}
14.3 this.$state.dispatch与this.$state.commit的主要区别
dispatch:含有异步操作,数据提交至actions,可用于向后台提交数据。
this.$store.dispatch('action', payload);
commit:同步操作,数据提交至 mutations ,可用于读取用户信息写到缓存里。
this.$store.commit('add',1);
14.4 vuex实现模块式开发的大致概念
项目很大、组件很多、数据很多,数据维护很费劲,则需要使用Vuex,Vuex可以模块化开发(把大仓库变成小仓库,按模块式进行存储)。
1.可以先给每一个组件模块来一个小仓库。
2.再将每个小仓库引入到store文件夹里的index.js(大仓库)中,用modules对外暴露,实现Vuex仓库模块式开发存储数据。
import Vue from 'vue';
import Vuex from 'vuex'
//需要使用插件一次
Vue.use(Vuex);
//引入小仓库
import home from '@/store/home';
//对外暴露Store类的一个实例
export default new Vuex.Store({
//实现Vuex仓库模块式开发存储数据
modules: {
home,
}
})
十五. TypeNav三级联动展示数据业务
前提已经将axios二次封装好了,vuex也准备好了,vuex中的模块化也准备好了。
注:项目中所有的全局组件的文件最好都放在components文件夹中
多练练一眼识别几层分类的思想。
state 相当于 data(){return{}} 区域定义属性;
mutations 相当于 created() 或 mounted() 调用方法;
actions 相当于 methods 定义方法;
getters 相当于 computed 是为了简化数据而生的。
十六. 完成三级联动动态背景颜色
- 方法一:写鼠标事件的css样式
.item:hover{
background:skyblue;
}
- 方法二:写js
<template>
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
......
<h3 @mouseenter="changeIndex(index)">
......
</div>
</template>
<script>
......
methods: {
//鼠标进入修改响应式数据currentIndex属性
changeIndex(index) {
//index:鼠标移上某一个一级分类的元素的索引值
this.currentIndex = index;
},
//一级分类鼠标移除的事件回调
leaveIndex(){
//鼠标移除currentIndex,变为-1
this.currentIndex = -1;
}
},
......
</script>
- 通过js动态去控制二、三级分类的隐藏和显示业务
二、三级分类应有一个动态的样式,动态地显示和隐藏。
判断条件:只要谁有类名,谁就应该显示;currentIndex是否等于当前的索引值index。
:style="{display:currentIndex==index?'block':'none'}"
代码如下:
<template>
......
<!-- 事件委派|事件代理 -->
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
<!-- 三级联动 -->
<div class="sort">
......
<!-- 二级、三级分类 -->
<div class="item-list clearfix"
:style="{display:currentIndex==index?'block':'none'}">
......
</template>
十七. 函数的防抖与节流【lodash插件】
17.1 卡顿现象引入函数的防抖与节流
● 卡顿:由于用户的行为过快,导致浏览器反应不过来。若当前回调函数中有一些大量业务,则可能出现卡顿现象。
○ 正常情况下(用户慢慢操作):鼠标一进入每一个一级分类的h3,触发鼠标进入事件,可正常执行。
○ 非正常情况下(用户操作过快):本身全部的一级分类都应该触发鼠标进入事件,但经测试,只有部分h3触发了。
● 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,即若连续快速地触发,也只会执行最后一次。
● 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个事件间隔才会触发回调,把频繁触发变为少量触发,使浏览器有充分时间解析代码 。
17.2 函数防抖的理解
● 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,即若连续快速地触发,也只会执行最后一次。
安装lodash.js:npm install --save lodash
防抖事例代码_.debounce:
input.oninput = _.debounce(function() {
console.log('ajax发请求')
}, 1000)//放个延时器
防抖事例进行使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
<!-- 引入lodash:lodash全部的功能引入 -->
<script src="js/lodash.js"></script>
</head>
<body>
<p>请输入搜索内容:<input type="text"></p>
</body>
</html>
<script>
//防抖:前面的所有触发都被取消,最后一次执行在规定时间之后才会触发,即若连续快速的触发,只会执行一次。
let input = document.querySelector('input');
//文本发生变化立即执行
/* input.oninput = function() {
console.log('ajax发请求')
} */
input.oninput = _.debounce(function() {
console.log('ajax发请求')
}, 1000)
//lodash插件:里面封装函数的防抖与节流的业务【闭包+延迟器】
//1.lodash函数库对外暴露的是_函数
</script>
17.3 函数节流的理解
● 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个事件间隔才会触发回调,把频繁触发变为少量触发,使浏览器有充分时间解析代码 。
安装lodash.js:npm install --save lodash
节流事例用代码_.throttle:
button.onclick = _.throttle(function() {
//节流:目前这个回调函数5s执行一次
//加入这里有很多的业务代码,是可以给浏览器充裕的时间进行解析的。
count++;
span.innerHTML = count;
console.log('执行');
}, 5000);
节流事例进行使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
<!-- 引入lodash:lodash全部的功能引入 -->
<script src="js/lodash.js"></script>
</head>
<body>
<div>
<h1>我是计数器<span>0</span></h1>
<button>点我加一</button>
</div>
</body>
</html>
<script>
//节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个事件间隔才会触发回调,把频繁触发变为少量触发。
//获取节点
let span = document.querySelector('span');
let button = document.querySelector('button');
let count = 0;
//计数器:在一秒以内,数字只能加上1
button.onclick = _.throttle(function() {
//节流:目前这个回调函数5s执行一次
//加入这里有很多的业务代码,是可以给浏览器充裕的时间进行解析的。
count++;
span.innerHTML = count;
console.log('执行');
}, 5000);
</script>
十八. 三级联动
18.1 三级联动中的节流
throttle回调函数别用箭头函数,否则可能出现上下文this的问题。
......
methods: {
..........
changeIndex:throttle(function(index) {
//index:鼠标移上某一个一级分类的元素的索引值
this.currentIndex = index;
},50),
.......
}
......
18.2 三级联动中的路由跳转分析
三级联动用户的分类:一级分类、二级分类、三级分类
Home模块跳转到Search模块,一级会把用户选中的产品(产品名字、产品ID)在路由跳转的时候进行传递。
路由跳转的两种方式:
①.声明式导航:router-link
在本次项目中,router-link容易出现卡顿问题。
②.编程式导航:push|replace
本次较好的解决方案:编程式导航 + 事件委派
18.3 用编程式导航 + 事件的委派
解决方案:用编程式导航 + 事件委派 完成三级联动的路由跳转与传递参数的业务
利用事件委派存在一些问题以及解决方法:
①.点击的不一定都是a标签
利用自定义属性data-categoryname解决,区分是否为a标签,带有data-categoryname这样的节点【一定是a标签】。
②.如何获取子节点参数【产品名、产品id】
节点有一个属性dataset属性,可获取节点的自定义属性与属性值。
通过categoryname区分是否为a标签,通过category1id、category2id、category3id区分是否为一级、二级、三级标签。再通过函数中传入的event参数,获取当前点击事件,通过event.target属性获取当前节点,通过dataset属性获取节点属性信息。
<template>
......
<div class="all-sort-list2" @click="goSearch" @mouseleave="leaveIndex">
......
</div>
......
</template>
<script>
......
methods{
......
//进行路由跳转的方法
goSearch(event){
//最好的解决方案:编程式导航 + 事件委派
//利用事件委派存在一些问题:①.点击的不一定都是a标签 ②.如何获取参数【产品名、产品id】
console.log(event.target)
}
......
}
</script>
通过categoryname区分是否为a标签,通过category1id、category2id、category3id区分是否为一级、二级、三级标签。再通过函数中传入的event参数,获取当前点击事件,通过event.target属性获取当前节点,通过dataset属性获取节点属性信息。
<template>
........
<h3 @mouseenter="changeIndex(index)">
<a
:data-categoryName="c1.categoryName"
:data-category1Id="c1.categoryId">
{
{ c1.categoryName }}--{
{ index }}
</a>
</h3>
<!-- 二级、三级分类 -->
<div
class="item-list clearfix"
:style="{display:currentIndex==index?'block':'none'}">
<!-- 因为:Key里已经有categoryId了,所以不用写index -->
<div
class="subitem"
v-for="(c2, index) in c1.categoryChild"
:key="c2.categoryId">
<dl class="fore">
<dt>
<a
:data-categoryName="c2.categoryName"
:data-category2Id="c2.categoryId">
{
{ c2.categoryName }}--{
{ index }}
</a>
</dt>
<dd>
<em v-for="c3 in c2.categoryChild" :key="c3.categoryId">
<a
:data-categoryName="c3.categoryName"
:data-category3Id="c3.categoryId">
{
{ c3.categoryName }}
</a>
</em>
</dd>
</dl>
</div>
</div>
........
</template>
<script>
........
goSearch(event){
//最好的解决方案:编程式导航 + 事件委派
//利用事件委派存在一些问题:①.点击的不一定都是a标签 ②.如何获取参数【产品名、产品id】
// this.$router.push('/search')
let element = event.target;
console.log(element)
//获取到当前触发这个事件的节点【h3、a、dt、dl】,需带有data-categoryname这样的节点【一定是a标签】
//节点有一个属性dataset属性,可获取节点的自定义属性与属性值。
// 通过categoryname区分是否为a标签,通过category1id,category2id,category3id区分是否为一级、二级、三级标签
let {categoryname,category1id,category2id,category3id} = element.dataset;
//如果标签身上拥有categoryname,则一定是a标签
if(categoryname){
//整理路由跳转的参数
let location = {name:"search"}
let query = {categoryname:categoryname}
//一级分类、二级分类、三级分类
if(category1id){
query.category1Id =category1id;
}else if(category2id){
query.category2Id =category2id;
}else if(category3id){
query.category3Id =category3id;
}
//整理完参数
// console.log(location,query)
location.query=query;
//将其加入路由跳转
this.$router.push(location);
}
}
........
</script>
十九. Search模块
19.1 Search模块商品模块分类与过渡动画
全局组件可直接使用,不需引用
Search模块中 typeNav三级联动商品分类菜单 的过渡动画效果
过渡动画:前提组件、元素务必要有v-if或v-show指令才可以进行过渡动画。
1).判断是否为某个路由组件(可用路径判断)
<templayte>
......
<div @mouseleave="leaveShow" @mouseenter="enterShow">
......
</div>
......
</templayte>
......
<script>
......
//当鼠标移入时,让商品分类列表进行展示
enterShow(){
if(this.$route.path !="/home"){
this.show = true
}
},
//当鼠标离开时,让商品分类列表进行隐藏
leaveShow(){
this.currentIndex = -1
if(this.$route.path !="/home"){
this.show = false
}
}
......
</script>