1、三级联动组件的路由跳转与传递参数
三级联动用户可以点击的有:一级分类、二级分类、三级分类,当点击的时候,Home模块跳转到Search模块,一级会把用户选中的产品(产品的名字、ID)在路由跳转时进行传递。
路由跳转的两种方式:
声明式导航
编程式导航
当使用声明式导航router-link 直接添加给a标签时,虽然可以实现路由的跳转与传参,但是会出现卡顿现象,原因:
router-link是一个组件,当服务器的数据返回之后,会循环出很多的router-link组件(创建组件实例,并将虚拟dom转换成真实dom),在创建组件实例的时候,一瞬间会很耗内存的。
最终解决思路:
1、给类为all-sort-list2 的div添加路由跳转事件,利用事件委派,将事件传给子节点;
2、给子节点里的a标签添加自定义属性data-categoryName 和data-categoryXid ,X为1、2、3,1级菜单的a标签为data-category1id,以此类推。这样的做法是为了让程序确认点击的是a标签,并通过路由,将对应的参数传递给search。因为all-sort-list2 的div子节点有很多种:h3 a dl dd em 都是它的子节点,通过给a绑定独有的属性,然后判断属性data-categoryName 和data-categoryXid是否存在,即可确定点击的是不是a标签、以及是哪一级的a标签。
下为goSearch事件的代码:
goSearch(event){
// this.$router.push('/search');
// 最好的解决方案:编程式导航+事件委派
// 利用事件委派存在的一些问题:1、如何确定点击的一定是a标签;2、如何获取参数(1、2、3级分类的产品名字和id)
// 把子节点当中a标签加上自定义属性data-categoryName,其余的子节点没有
let element = event.target;
//console.log(element);
// 获取到当前触发这个事件的节点(可能是h3 a dt dl),需要带有data-categoryName这样的节点(一定是a标签)
// 节点有一个属性dataset 可以获取节点的自定义属性与属性值
//console.log(element.dataset);
let {categoryname,category1id,category2id,category3id} = element.dataset;
// console.log(categoryname);
// 如果标签身上拥有categoryname,那么一定是a标签
if(categoryname){
// 区分是一级分类、二级分类、还是三级分类的a标签
// 整理路由跳转的参数
let location ={name:'search'};
let query={categoryName:categoryname};
if(category1id){
// 说明是一级分类a标签
query.category1Id = category1id;
}else if(category2id){
// 说明是二级分类a标签
query.category2Id = category2id;
}else{
// 说明是三级分类a标签
query.category3Id = category3id;
}
location.query=query;
this.$router.push(location);
}
}
总结:这里用到了编程式导航和事件委派,并通过自定义属性来优化性能。
2、Search模块中商品分类与过渡动画
这里使用了全局组件TypeNav,当页面跳转到search时,需要添加TypeNav的1级菜单的显示与隐藏及动画效果:左边为search 右边为home
可以使用this.$route.path来判断页面跳转的路径,并给对应的div添加一个v-show=‘show’,通过路由来设置该值,从而控制1级菜单的显示与隐藏:
mounted() {
// 通知Vuex发请求,获取数据,存储于仓库中
this.$store.dispatch("categoryList");
// console.log("我是TypeNav,挂载完毕");
// 当组件挂载完毕,让show的属性变为false
if(this.$route.path==='/home'){
this.show=true;
}else{
this.show=false;
}
},
这里的动画效果,老师是使用vue的过渡动画,需要给过渡动画对应的节点添加一个包裹<transition></transition> ,并添加属性name。对应的动画分为加载前、加载后,下面为代码:
<transition name="sort">
<!-- 三级联动 -->
<div class="sort" v-show="show">
...
</div>
</transition>
// 过渡动画的样式
.sort-enter{
height: 0;
}
// 过渡动画结束状态
.sort-enter-to{
height: 475px;
}
.sort-enter-active{
transition: all .5s linear;
}
3、TypeNav商品分类列表的优化
之前将获取TypeNav组件的服务器数据请求放在TypeNav组件中,可以将该请求放在App.vue里。
App.vue里面的代码:
mounted(){
// 通知Vuex发请求,获取数据,存储于仓库中,放在这里比放在TypeNav组件中要好,因为当页面发生变化时,TypeNav组件会重新生成,这样会导致用户在切换页面时,反复给后台服务器发送请求,获取数据;而放在App里,则只会当用户刷新整个页面时,才会重新发送请求,相当于优化了代码
this.$store.dispatch("categoryList");
}
4、合并参数
对params和query参数的合并:
先需要理解一下用户的操作:
在首页,用户的搜索分为2中情况:
1、点击3级菜单(传入query参数)——跳转到search页面——输入关键字——点击搜索(传入params参数);
2、在home页面搜索栏直接输入关键字——点击搜索(传入params参数);
目前的情况是——
点击3级菜单,query参数有,params参数为空;
点击搜索按钮,params参数有,query参数为空。
上面的情况出现的原因是:目前的两个组件都没有对params参数和query参数做合并,两边在传参的时候,由于都声明了一个单独的location,所以导致这种结果。
解决办法:只需要在传参的时候,判断当前的this.$route中是否存在query参数或params参数即可:
TypeNav组件代码如下:
// 路由跳转到search
goSearch(event){
let element = event.target;
let {categoryname,category1id,category2id,category3id} = element.dataset;
if(categoryname){
let location ={name:'search'};
let query={categoryName:categoryname};
if(category1id){
// 说明是一级分类a标签
query.category1Id = category1id;
}else if(category2id){
// 说明是二级分类a标签
query.category2Id = category2id;
}else{
// 说明是三级分类a标签
query.category3Id = category3id;
}
// 判断:如果路由跳转的时候,带有params参数,捎带脚传递过去
if(this.$route.params){
location.query=query;
location.params=this.$route.params; //点击3级菜单时,如果存在params参数,就给location添加上对应的params参数
this.$router.push(location);
}
}
Header组件代码:
goSearch(){
if(this.$route.query){ // 由于$route的query和params默认为空对象,所以if的条件判断必定为true,不用担心因为不点击3级菜单而导致this.$router.query为undefined
let location = {
name:'search',
params:{keyword:this.keyword ||undefined}
}
location.query = this.$route.query; //给location传入之前点击3级菜单后,$route里存放的query参数
this.$router.push(location);
}
}
5、mockjs模拟数据(P32)
开发Home首页当中的ListContainer组件与Floor组件
但是这里需要知道一件事情:服务器返回的数据(接口)只有商品分类菜单分类数据,对于ListContainer组件与Floor组件数据服务器没有提供的。
mock数据(模拟):如果你想mock数据,需要用到一个插件mockjs
此节跳过
6、swiper使用
安装Swiper插件 视频同步选择Swiper5
npm install --save swiper@5
6.1 通过watch+nextTick解决问题
API — Vue.js|vue.$nextTick
watch:{
// 监听bannerList数据的变化——由空数组变为数组里面有4个元素
bannerList:{
handler(newVal,oldVal){
// 现在咱们通过watch监听bannerList属性的属性值的变化
// 如果执行handler方法,代表组件实例身上这个属性的属性值已经有了(4个元素的数组)
// 当前这个函数执行:只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了
// v-for执行完毕,才有结构,你现在在watch中没办法保证的
// nextTick:在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的dom
// 即:bannerList从空数组变成4个元素的数组后(bannerLinst的数据发生了修改),且v-for循环完成后,会执行nextTick里面的回调函数,确保了mySwiper实例是在页面存在结构后才生成的,保证了swiper的功能正常使用。
this.$nextTick(function(){
var mySwiper = new Swiper ('.swiper-container', {
// direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
// 点击小球也可以进行切换
clickable:true
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
});
})
}
}
}
$nextTick可以保证页面中的结构一定是存在的,经常和很多的插件一起使用【很多插件都需要DOM存在才能正常工作】
7、获取floor组件mock数据
关键点:
1、根据floor组件的mock数据可知,获取数据需要在Home路由组件当中获取;
2、v-for也可以在自定义标签中使用;
组件间通信方式有哪些?
props:用于父子组件通信(面试频率极高)
自定义事件:$on $emit 可以实现子给父通信
全局事件总线:$bus 全能
pubsub-js :vue中几乎不用 react用的比较多
插槽:3种
vuex
另外需要注意:Floor组件的Swiper实例可以直接在mounted中引入,原因如下:
//第一次书写Swiper的时候:在mounted当中书写是不可以的,但是为什么现在这里可以啦!
//第一次书写轮播图的时候,是在当前组件内部发请求、动态渲染解构【前台至少服务器数据需要回来】,因此当年的写法在这里不行//现在的这种写法为什么可以:因为请求是父组件发的,父组件通过props传递过来的,而且结构都已经有了的情况下执行mounted