卑微小前端开始工作了。开始记录一下自己在工作中遇到的问题,有些是粗心出的错,有些是基础不扎实遇到的问题。有些则是真的不知道有这种操作才出现的问题。总之就记录自己的在公司项目中遇到的各式各样的问题。也算是记录一下自己的成长。也方便自己之后遇到相同问题的时候。可以直接来自己博客找。大部分内容都源自别人写的博客,算是个缝合怪。。。。嘛。。起码我这东西还是自己试过可行的。
target 事件属性可返回事件的目标节点(触发该事件的节点),如生成事件的元素、文档或窗口。
在标准浏览器下我们一般使用event.target就能解决,然而低版本IE浏览器总是会出些幺蛾子,这时候就得使用event.srcElement。
这时候就得有一个兼容性的写法了。
1、使用三元运算符
var Target = event.target ? event.target : event.srcElement
2、使用if语句来判断
if(event.target){
// IE10及以下版本浏览器不能识别`` ``
// do something...``
}else if(event.srcElement){
// IE10及以下版本会执行该代码`` ``
// do something...``
}
getAttribute()是HTML DOM的一个方法,用以获取HTML元素的属性(如id,name,type以及其他自定义属性)。同理,setAttribute()、removeAttribute()都是对HTML元素的属性进行操作。
style是HTML DOM的一个关于样式的对象,style对象的属性(如background,color,border等等)用以设置元素的样式。
举例说明:
<input type="text" id="btn" style="background-color: red">
获取type属性可以用: document.getElementById(“btn”).getAttribute(“text”); //注意要获取的属性有双引号
获取元素样式可以用: document.getElementById(“btn”).style.backgroundColor;
注: 如果属性是内联样式,也可直接用getAttribute()来获取其属性,但只能获取style,无法设置style里面具体的background-color里面具体的值。
利用.getAttribute(“style”)得到的结果是 background-color: red
vue运行为v-on在监听键盘事件时,添加了特殊的键盘修饰符:
<input v-on:keyup.13="submit">
vue还非常贴心地给出了常用按键的别名,这样就不必去记keyCode ~ ~
上面代码,还可以在这样写:
<input v-on:keyup.enter="submit">
<input @keyup.enter="submit">
全部的键盘别名:
.enter
.tab
.delete (捕获 “删除” 和 “退格” 键)
.esc
.space
.up
.down
.left
.right
还有一些组合按键:
.ctrl
.alt
.shift
.meta(window系统下是window键,mac下是command键)
Alt + C :
Ctrl + Click :
点我注意!!!如果用了封装组件的话,比如element,这个时候使用按键修饰符需要加上.native
比如:
<el-input v-model="account" placeholder="请输入账号" @keyup.enter.native="search()"></el-input>
定义和用法
完整的 keypress 过程分为两个部分:1. 按键被按下;2. 按键被松开。
当按钮被按下时,发生 keydown 事件。
keydown() 方法触发 keydown 事件,或规定当发生 keydown 事件时运行的函数。
<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("input").keydown(function(){
$("input").css("background-color","#FFFFCC");
});
$("input").keyup(function(){
$("input").css("background-color","#D6D6FF");
});
});
</script>
</head>
<body>
Enter your name: <input type="text" />
<p>当发生 keydown 和 keyup 事件时,输入域会改变颜色。请试着在其中输入内容。</p>
</body>
</html>
keyup 是在用户将按键抬起的时候才会触发的,属于整个按键过程中的最后阶段,所以有其特定的用处,就是在左侧输入,右侧同步显示的过程中很有用处。典型的例子就是邮件编辑预览的应用。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>无标题页</title>
<script src="JS/jquery-1.4.2.js" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
$('#t1').live('keyup', function() {
$('#v1').text($(this).val());
});
$('#t2').live('keydown', function() {
$('#v2').text($(this).val());
});
$('#t3').live('keypress', function() {
$('#v3').text($(this).val());
});
});
</script>
</head>
<body>
<textarea id="t1"></textarea>
<div id="v1">
</div>
<textarea id="t2"></textarea>
<div id="v2">
</div>
<textarea id="t3"></textarea>
<div id="v3">
</div>
</body>
</html>
这里分别用 keydown 、keyup、keypress做了实验,其中只有 keyup可以完整的将输入内容同步获取到,keydown和keypress总是无法获取到最后一个字符,这说明了三者之间的区别:
keydown 总是在摁键摁下的当时触发,不能得到最后的输入结果,keypress也是一样
这是因为keydown操作后,事件触发了,但值还未显示在文本框中,所以这类操作要用 keyup 一个完整的按键动作后,才可以获取文本框的值。
keydown与keypress更适用于通过键盘控制页面类功能的实现。
获取键盘点击的键位:
<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("input").keydown(function(event){
$("div").html("Key: " + event.which);
});
});
</script>
</head>
<body>
请随意键入一些字符:<input type="text" />
<p>当您在上面的框中键入文本时,下面的 div 会显示键位序号。</p>
<div />
</body>
</html>
windows 窗体通过引发键盘事件来处理键盘入以响应 windows 消息,大多数 windows 窗体应用程序都通过处理键盘事件以独占方式处理键盘输入。
按键的类型
windows 窗体将键盘输入标识为:由按位 keys 枚举表示的虚拟键代码。
使用 keys 枚举,可以综合一系列按键以生成单个值,这些值与 WM_KEYDOWN 和 WM_SYSKEYDOWNWindows 消息所伴随的值相对应。通过处理 keydown 或 keyup 事件可以检测大多数的恶物理按键操作。字符键是 keys 枚举的子集,它们与 WM_KEYDOWN 和 WM_SYSKEYDOWNWindows 消息所伴随的值相对应,如果通过组合按键得到一个字符,则可以处理 keypress 事件来检测该字符。
键盘事件的顺序
3.键的预处理
像其他消息一样,键盘消息是在窗体或控件的 WindProc 方法中处理的,窗体或控件在处理键盘消息之前 PreProcessMessage 方法会调用一个或多个方法,这些方法可以被重写以处理特殊的字符键和物理按键。
1
keydown 触发后不一定触发 keyup 当 keydown按下后拖动鼠标,那么将不会触发 keyup事件
4 用法区别:
系统由 keydown 返回键盘的代码 后再由 translateMessage 函数翻译成字符,然后在由 keypress 返回键盘的字符值(Ascall 字符)。
所以,在使用时应根据你的目的,选择性的使用:
如果你只想读取字符用 keypress;如果想读取各键盘的状态用 keydown
5.系统组合键的判定
在使用键盘的时候通常会用到 Ctrl+shift+alt 类似的组合键功能,通过 keyup 事件能够处理(说明一下这里不用keydown事件:因为在判定keydown 的时候,ctrl、shift、和Alt属于一直按下状态然后再加另外一个键是不能准确捕获组合键,所以使用keydown 是不能准确判断的,要通过keyup事件来判定)
6.捕获PreScrn按键事件
通过一种钩子的方式可以判定PrScrn 按键事件,钩子可以获取任何键盘事件
在调用时需要写为:
<router-link to='/baseData/dictionary'></router-link>
很多人在这里会想到用懒加载来加载组件,代码如下:
{
path:'/medicalSvc',
name:'医疗服务',
component:resolve => require(['../components/medicalData/medicalServe'], resolve),
},
但是如果你构建的是系统类项目,有上百个路由,这样的话 当你用webpack hot进行代码热更新的时候,速度就会超级慢,此时解决方法就是让他在开发环境下不要懒加载,在生产环境下再进行懒加载。我们首先要定义一个变量
const _import = require('./_import_' + process.env.NODE_ENV);
process.env是读取系统环境变量的,比如你在启动服务的时候,设置环境变量为production或者development,那么在程序里面就可以通过process.env.NODE_ENV获取。此时你就需要再写两个文件(注意文件的名称命名)分别导出不同环境下的文件目录如:
*_production.js: module.exports = file => () => import('@/pages/' + file + '.vue')
*_develope.js: module.exports = file => import('@/pages/' + file + '.vue')
然后将组件路径定义为:
{path:'/app/customerSource',component:_import('customerSource')},
那么又是怎么设置环境变量为production或者development值的呢,是在配置文件index.js下
另外注意:404页面一定要最后加载,如果放在constantRouterMap一同声明了404,它后面的所有页面都会被拦截到404,详细的问题见addRoutes when you’ve got a wildcard route for 404s does not work
const _import = require('./_import_' + process.env.NODE_ENV) //获取组件的方法
//远程运维智能管理
{
path: '/operationmanagement/statisticalReport',
name: 'statisticalReport',
component: _import('operationmanagement/statisticalReport'),
}
process对象是全局变量,它提供当前node.js的有关进程。因为是全局变量,它对于node应用程序是始终可用的,无需require()
env是process的属性,这个属性包含用户环境信息对象,这个属性返回包含用户环境信息的对象
NODE_ENV不是process.env对象上原有的属性,他是我们添加的环境变量,用来确定当前所处的开发阶段。一般生产阶段设为production,开发阶段设为develop,然后在脚本中读取process.env.NODE_ENV。运行脚本时,可以这样改变环境变量,在package.json文件的script里面添加命令。set NODE_ENV=production node build.js
cross-env可以跨平台的设置和使用环境变量
npm install --save-dev cross-env
在vue项目中,如果前一个页面有滚动条的滚动,当路由跳转后发现滚动条的位置还保持在原来的位置,这个就带来了困扰。
查询了资料,发现可以在路由的导航守卫afterEach里面添加:window.scrollTo(0,0);
这种方法可以在每次路由跳转后手动使滚动条回到头部位置。
如图:
其实在vue官网中介绍了scrollBehavior方法,同样可以实现路由跳转之后滚动条滚到顶部。
https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html#异步滚动
具体代码如下:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
vue
组件滚动行为之前,先简单说说(毕竟深入了我也很模糊o(╯□╰)o)浏览器是如何对用户访问过的页面的保持,浏览器历史记录是对用户所访问的页面按时间顺序进行的记录和保存,以上是MDN对浏览器就如何跟踪用户访问过网页的解释性说明。history
可以满足一些需求。vue
路由跳转就是通过对 history.pushState()
和 history.replaceState()
方法的模拟来实现,会往 history
栈中存放一条记录,这也是为什么 vue
的 router.push
方法只能在支持 history.pushState()
方法的浏览器中使用,当调用 router.go()
或者 router.back()
方法的时候就和 history.go()
、history.back()
效果一样,都是对 history
栈中的记录进行访问,上述行为与通过浏览器的回退和前进效果也是一样。/home
、/list
、/about
,即对应三个不同的组件, <ul class="tab">
<li>
<router-link to="/" >首页router-link>
li>
<li>
<router-link to="/list" >列表router-link>
li>
<li>
<router-link to="/about" >关于router-link>
li>
<li>
<a href="#" @click='() => { this.$router.back() }'>点击回退a>
li>
ul>
<router-view>router-view>
ul>li
的结构
<ul class="list_content home">
<li v-for='i in 10'>{{ i }}li>
ul>
长这样:
请注意,现在开始滚动首页位置至第 5 屏的位置,当切换到列表以及关于页面的时候,会发现这两个页面的滚动行为和首页滚动行为一致。
既不涉及组件的缓存,也不涉及组件的复用,我们不禁会疑惑为什么首页的滚动会影响到其他两个页面,如果我们有当切换组件的时候,需要让当前组件的内容是从 scrollTop = 0
的时候开始浏览,那这样的结果将会是一个绊脚石。
原因如下,因为基于SPA模式开发,所以页面仅有一个,实现页面切换是利用哈希与组件的映射关系,vue-router
是通过哈希来模拟完整的 url
,但是对于页面来说仍是一个 url
,所以在任何一个组件滚动页面,切换到其他组件的时候,页面仍保持滚动之前的状态,这就是出现上述现象的原因。
router.beforeEach()
导航守卫会是一个不错的选择: router.beforeEach((to, from, next) => {
// 让页面回到顶部
document.documentElement.scrollTop = 0
// 一定不要忘记调用 next()
next()
})
但这不是我们的主题,要借助 vue-router
提供的 scrollBehavior
,来管理组件滚动行为。
scrollBehavior
,这里贴出官网对概念的介绍 传送门,当然,借助 scrollBehavior
,你也能让页面在组件切换的时候回到顶部: const scrollBehavior = function (to, from, savedPosition) {
// savedPosition 会在你使用浏览器前进或后退按钮时候生效
// 这个跟你使用 router.go() 或 router.back() 效果一致
// 这也是为什么我在 tab 栏结构中放入了一个 点击回退 的按钮
if (savedPosition) {
return savedPosition
} else {
// 如果不是通过上述行为切换组件,就会让页面回到顶部
return {x: 0, y: 0}
}
}
home
页面到底部的时候,跳转到 list
页面浏览,当浏览到中间的时候,跳转到 about
页面浏览,当用户每次回退的时候,都希望保持离开之前页面的状态,即:从 about
回退到 list
页面的时候,页面仍是在中间,回退到 home
页面的时候,仍是在底部,这就需要我们个性化定制。这里用到路由的元信息 meta
更细颗粒度控制滚动行为,这里以 home
组件为例说明:
const Home = {
template: `
- {{ i }}
`,
data () {
return {
timerId: ''
}
},
mounted () {
// 通过 addEventListener 方法注册事件的时候需要格外小心
// 如果在 destroyed 钩子函数中没有销毁 scroll 事件
// 在激活 home 组件的时候会再次绑定 scroll 事件
// window.addEventListener('scroll', this.justifyPos)
// 通过 on 方式绑定事件能够有效避免上述情况
window.onscroll = this.justifyPos
},
methods: {
justifyPos () {
// 节流;
if (this.timerId) clearTimeout(this.timerId)
this.timerId = setTimeout(() => {
// 获取页面滚动距离之后设置给当前路由的 元信息
this.$route.meta.y = window.pageYOffset
}, 300)
}
},
destroyed () {
// 当组件销毁的时候,移除滚动行为监听, 清空定时器;
// 该方法是绑定到 window 身上,即使跳转到其他组件,仍然会监听页面的滚动行为
// window.removeEventListener('scroll', this.justifyPos)
// clearTimeout(this.timerId)
}
}
const List = {
template: `
- {{ i }}
`
}
const About = {
template: `
- {{ i }}
`
}
const routes = [
// 设置 meta,细颗粒控制组件滚动
{path: '/', component: Home, meta: {x: 0, y: 0}},
{path: '/list', component: List, meta: {x: 0, y: 0}},
{path: '/about', component: About, meta: {x: 0, y: 0}}
]
const scrollBehavior = function (to, from, savedPosition) {
return to.meta
}
const router = new VueRouter({
routes,
scrollBehavior,
linkExactActiveClass: 'current'
})
上述会在 home
组件滚动停止的时候记录当前组件的滚动位置信息,并且存储到对应 home
组件的路由 meta
这个对象中,当切换到 list
或者 about
页面之后在回到 home
组件,仍会保留着离开之前的位置,而不是简单地让页面回到顶部。
但是,你会发现你只是针对 home
组件的滚动行为进行控制,list
和 about
组件的滚动行为也能够实现个性化定制,即也会将当前组件的滚动行为记录在对应的路由 meta
中。
这会让人疑惑,因为在 list
和 about
组件中并没有设置 justifyPos
方法,并且 window.onscroll = this.justifyPos
将 this
绑定到当前的上下文中。
vue
官网对于组件销毁介绍,会解绑所有的指令以及事件监听,但是对于方法的引用处理没有提到,个人觉得在这里应该抛出警告或者错误的,但是 vue
却没有提示,这也是令我困惑的一点。但是,这却为滚动行为监听提供了更好的处理方法,那就是绑定到 vue
根实例上,而不是某一个单一组件上,因为 this
会自动绑定到当前上下文:
new Vue({
router,
data: {
timerId: ''
},
mounted () {
window.addEventListener('scroll', this.justifyPos)
},
methods: {
justifyPos () {
if (this.timerId) clearTimeout(this.timerId)
this.timerId = setTimeout(() => {
this.$route.meta.y = window.pageYOffset
}, 300)
}
}
}).$mount('#app')
bs
,如果在项目中用到该插件,那么页面滚动行为跟组件滚动行为是完全不一样的,这是因为 bs
特殊的结构要求,父容器需要有个固定的高度,所有的滚动行为是由子元素来产生的,在移动端应用 bs
,通常会将父容器的高度设置为屏幕的高度,你的所有应用都应该放到这个父容器内。bs
在移动端性能很出色,但是这却为组件个性化定制 scrollBehavior
带来了一些小麻烦。bs
插件的组件,一般会设置高度和屏幕高度一致,这样即使通过 meta
来设置滚动记录,在 vue-router
的 scrollBehavior
中返回 meta
也没有用处,因为高度是定死了,就不存在滚动,你所看到的滚动式是 bs
插件所处理的。bs
提供的一些事件和方法了,仍以 home 组件为例说明,看代码: const Home = {
template: `
- {{ i }}
`,
mounted () {
this.$nextTick(() => {
// 初始化 BS
this._initScroll()
// 滚动监听
this.scroll.on('scrollEnd', (pos) => {
// 将滚动信息设置给当前路由元信息
this.$route.meta.y = pos.y
})
// 当前组件激活的时候,滚动到离开前位置
// 如果你想要滚动动画效果,可以在 scrollTo 方法中自定义
this.scroll.scrollTo(0, this.$route.meta.y, 0)
})
},
methods: {
_initScroll () {
if (!this.$refs.wrapper) return
this.scroll = new BScroll(this.$refs.wrapper, {
mouseWheel: {
speed: 20,
invert: false,
easeTime: 300
},
// 派发 click 事件;
click: true
})
},
// 跳转到列表页;
goList () {
this.$router.push({name: 'list'})
}
}
}
const List = {
template: `
- {{ i }}
`,
methods: {
// 回跳到首页
goHome () {
this.$router.push({name: 'home'})
}
}
}
const routes = [
// 设置 meta
{path: '/', name: 'home', component: Home, meta: {x: 0, y: 0}},
{path: '/list', name: 'list', component: List, meta: {x: 0, y: 0}},
{path: '/about', component: About, meta: {x: 0, y: 0}}
]
// scrollBehavior 其实这里已经没有什么作用了,因为当前组件的高度被定死和整个屏幕一样高
// const scrollBehavior = function (to, from, savedPosition) {
// return to.meta
// }
// 设置路由
const router = new VueRouter({
routes,
scrollBehavior,
linkExactActiveClass: 'current'
})
// 挂载
new Vue({router}).$mount('#app')
bs
提供的事件以及方法再结合路由的 meta
,也能够实现细颗粒度控制滚动,如果对组件使用了 keep-alive
,你应该在每次切换到该组件的时候在 activated
钩子函数中初始化 bs
、scrollEnd
事件以及 scrollTo
方法;如果你页面有分页的功能,你可能需要在分页边界花费一些心思如何让滚动行为跨越分页,这里建议是使用组件缓存,关于组件如何清除缓存,可以参考另一篇文章 组件去缓存,当然如果你有更好的处理方式,也可以留言。在这里我们将会实现一个vue动态路由的案列,当用户登陆成功后,根据用户的角色,拿到他对应的菜单信息,并将它动态的载入到我们的路由中。
我们的通用的后台管理系统中,我们会根据权限的粗细不同,会对每个角色每个权限每个资源进行控制。同样的我们也需要实现一个这样的功能。 这篇文章我将主要讲vue端的实现,关于后台接口我就不会涉及,当我接触的时候我们的后台接口是springcloud实现。
在vue-router对象中首先初始化公共路由,比如(404,login)等,然后在用户登陆成功,根据用户的角色信息,获取对应权限菜单信息menuList,并将后台返回的menuList转换成我们需要的router数据结构,然后通过vue-router2.2新添的router.addRouter(routes)方法,同时我们可以将转后的路由信息保存于vuex,这样我们可以在我们的SideBar组件中获取我们的全部路由信息,并且渲染我们的左侧菜单栏,让动态路由实现。
1、公共路由定义
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '../views/layout/Layout'
export const constantRouterMap = [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'Dashboard',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
]
export default new Router({
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
2、获取菜单信息
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
if (getToken()) { // determine if there has token
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取user_info
const roles = res.roles
store.dispatch("GetMenu").then(data => {
initMenu(router, data);
});
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next('/login') // 否则全部重定向到登录页
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
}
}
})
router.afterEach(() => {
NProgress.done() // finish progress bar
})
在这里 我们通过在router的beforeEach钩子函数 判断用户是否已经获得角色信息,如果没有,则请求后台获取对应的角色信息,然后通过角色信息再次请求获取对应的菜单列表,获取到菜单列表,然后去格式化菜单列表,使其转换成router数组的结构。
3、动态加载路由
import store from '../store'
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
// 最后添加
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound)
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
export const formatRoutes = (aMenu) => {
const aRouter = []
aMenu.forEach(oMenu => {
const {
path,
component,
name,
icon,
childrens
} = oMenu
if (!validatenull(component)) {
let filePath;
const oRouter = {
path: path,
component(resolve) {
let componentPath = ''
if (component === 'Layout') {
require(['../views/layout/Layout'], resolve)
return
} else {
componentPath = component
}
require([`../${componentPath}.vue`], resolve)
},
name: name,
icon: icon,
children: validatenull(childrens) ? [] : formatRoutes(childrens)
}
aRouter.push(oRouter)
}
})
return aRouter
}
在这里我们把menList转换成routerList因为我们后台返回的数据不是规范的router结构,所以这里需要我们处理一下,如果你们后台返回规范的就不需要处理,然后通过router.addRoutes把后台返回的加入到我们的路由中,并且将其保存在我们的vuex中,需要主要的 如果404组件一定要放在动态路由在后载入。
4、渲染菜单
其实这里已经不属于我们的所讲的重点,在这里只需要取出上一步存在vuex中的路由信息,并且将其渲染成我们的左侧菜单栏就可以。在这里我们使用了element-ui。
<template>
<el-scrollbar wrapClass="scrollbar-wrapper">
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
</el-menu>
</el-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import { validatenull } from "@/utils/validate";
import { initMenu } from "@/utils/util";
export default {
components: { SidebarItem },
created() {
},
computed: {
...mapGetters([
'permission_routers',
'sidebar',
'addRouters'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
就这样我们动态加载路由就是实现了,是不是很简单,关键点就是router.addRoute方法。下面我就说一下防踩坑点。
1、关于加载菜单信息的时机
在此之前我将第二步获取菜单信息放在我的SideBar组件的create函数中,当时我发现也没有什么问题。登录跳转到home界面 左侧菜单也成功渲染,点击菜单进入我们动态加载的路由界面,也没问题。但是当我点击刷新的时候问题来。页面空白 控制台也不报错。当时我就蒙蔽了,什么情况,不是好好的嘛?如果大家也遇到这种这时候大家不要着急,冷静的分析整个流程,就会发现问题的所在。
1、登陆成功跳转home界面,home组件是公共路由,存在的没问题。
2、这时候 sidebar组件create钩子触发,成功获取菜单列表
3、菜单列表转成路由数组,并且加载到router实例中和vuex中
4、sidebar从vuex获取到路由数组渲染菜单 进入我们动态加载页面中,显示正常,这一切看起来没什么问题
5、点击浏览器的刷新按钮、或者F5,页面空白。
原因: 第五步中我们我们浏览器刷新,在spa应用整个vue实例会重新加载,也是说我的vue-router会重新初始化,那么我们之前的动态addRoute就不存在了,但是我们此时访问一个不存在的页面,所以我们的sidebar组件也就不会被访问,那么也无法获取菜单信息,就导致页面空白。所以我们需要把加载菜单信息这一步放在router的全局守卫beforeEach中就可以了。
2、关于404组件的位置
大家可以看到
export const initMenu = (router, menu) =>
{
if (menu.length === 0)
{ return }
let menus = formatRoutes(menu);// 最后添加
let unfound = {
path: '*',
redirect: '/404',
hidden: true
}
menus.push(unfound)
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
我强调了 404组件一定要放在动态路由组件的最后,不然你刷新动态加载的页面,会跳转到404页面的。
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中;
先来第一个例子看一看
<template>
<section>
<div ref="hello">
<h1>Hello World ~</h1>
</div>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
methods: {
get() {
}
},
mounted() {
console.log(333);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(444);
console.log(this.$refs['hello']);
});
},
created() {
console.log(111);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(222);
console.log(this.$refs['hello']);
});
}
}
</script>
可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象
然后来看第二个例子
<template>
<section>
<h1 ref="hello">{{ value }}</h1>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
data() {
return {
value: 'Hello World ~'
};
},
methods: {
get() {
this.value = '你好啊';
console.log(this.$refs['hello'].innerText);
this.$nextTick(() => {
console.log(this.$refs['hello'].innerText);
});
}
},
mounted() {
},
created() {
}
}
</script>
根据上面的例子可以看出,在方法里直接打印的话, 由于dom元素还没有更新, 因此打印出来的还是未改变之前的值,而通过this.$nextTick()获取到的值为dom更新之后的值
this.$nextTick()在页面交互,尤其是从后台获取数据后重新生成dom对象之后的操作有很大的优势,这里只是简单的例子,实际应用中更为好用~
JavaScript 中有两个特殊数据类型:undefined 和 null,下节介绍了 null 的判断,下面谈谈 undefined 的判断。
var exp = undefined;
if (exp == undefined)
{
alert("undefined");
}
exp 为 null 时,也会得到与 undefined 相同的结果,虽然 null 和 undefined 不一样。注意:要同时判断 undefined 和 null 时可使用本法。
var exp = undefined;
if (typeof(exp) == undefined)
{
alert("undefined");
}
var exp = undefined;
if (typeof(exp) == "undefined")
{
alert("undefined");
}
var exp = null;
if (exp == null)
{
alert(“is null”);
}
exp 为 undefined 时,也会得到与 null 相同的结果,虽然 null 和 undefined 不一样。注意:要同时判断 null 和 undefined 时可使用本法。
var exp = null;
if (!exp)
{
alert(“is null”);
}
如果 exp 为 undefined 或者数字零,也会得到与 null 相同的结果,虽然 null 和二者不一样。注意:要同时判断 null、undefined 和数字零时可使用本法。
var exp = null;
if (typeof(exp) == “null”)
{
alert(“is null”);
}
为了向下兼容,exp 为 null 时,typeof 总返回 object。
var exp = null;
if (isNull(exp))
{
alert(“is null”);
}
JavaScript 中没有 isNull 这个函数。
var exp = null;
if (!exp && typeof(exp)!=”undefined” && exp!=0)
{
alert(“is null”);
}
尽管如此,我们在 DOM 应用中,一般只需要用 (!exp) 来判断就可以了,因为 DOM 应用中,可能返回 null,可能返回 undefined,如果具体判断 null 还是 undefined 会使程序过于复杂。