vue-router 使用装饰器进行按需加载与页面加载动画
1. 在 app.vue 中添加 div
这里假设 div 为 .page-loading-loader
。使用相应的 css3 动画进行显示,这里 css 代码略。
//- app.vue
#app
.page-loading-loader
.my-css-animation-element
.page-header
.page-main
router-view
.page-footer
我们用 js 来动态控制 .page-loading-loader
的显示与隐藏。
为了方便,示例中使用 jQuery 来处理。
2. 原有的组件加载方式
webpack 中有两种加载方式,一种是直接加载,一种是按需加载:
# router.coffee
[
{
path: '/load'
name: 'load'
# 直接加载:
component: require('./views/load.vue')
},
{
path: '/lazy-load'
name: 'lazy_load'
# 按需加载:
component: (resolve) ->
require(['../views/lazy-load.vue'], resolve)
},
...
]
3. 页面加载动画
直接加载模式,由于已经加载到浏览器中了,无需使用加载动画。
故,只有按需加载时需要使用页面加载动画。
3.1 处理动画
# router.coffee
[
{
path: '/lazy-load'
name: 'lazy_load'
# 按需加载:
component: (resolve) ->
# 加载前显示动画
$('.page-loading-loader').show()
require(['../views/lazy-load.vue'], (component) ->
# 加载完成后渲染并隐藏动画
resolve(component)
$('.page-loading-loader').hide()
)
},
...
]
但是这样会有一个问题,比如有两个 url,对应两个按需加载的组件。
假设组件 1 加载需要 10 秒,组件 2 加载需要 2 秒。
用户点击组件 1 的 router-link,然后在 10 秒内(即组件 1 还没有加载完成时)点击组件 2 的 router-link,此时加载组件 2。
由于组件 2 加载速度快,先返回,先 resolve,resolve 后又 hide() 了动画 div。
之后已经没有动画了。
但是组件 1 还在加载,当几秒过后加载完成,再次 resolve,此时页面变成了组件 1。
这样用户的意愿是要查看组件 1,后来又变更了想法,要访问组件 2。但是我们的程序先展示组件 2,然后隐藏动画,过一会又展示了组件 1。导致逻辑混乱。
3.2 解决加载顺序混乱
问题的关键在于 resolve 时没有检查当前的路由是否与要 resolve 的路由一致。故,每次加载时保存到一个全局变量。每次 resolve 时,进行对比。
# router.coffee
# 值仅用于每次获取时作为唯一标记,值没有任何用途。
# 也可以用 Math.random() 或者 new Date().getTime() 等代替
uuid = 1
unique = ->
uuid += 1
return uuid
[
{
path: '/lazy-load'
name: 'lazy_load'
# 按需加载:
component: (resolve) ->
# 加载前显示动画
$('.page-loading-loader').show()
_uuid = unique()
require(['../views/lazy-load.vue'], (component) ->
# 如果加载完成的组件就是当前用户最后点击的那个组件,那么才 resolve
if _uuid == uuid
# 加载完成后渲染并隐藏动画
resolve(component)
$('.page-loading-loader').hide()
)
},
...
]
3.3 封装
3.3.1 封装 require
webpack 中的 require 不能使用变量的形式来加载组件,这就导致不能使用传递变量的方法来封装。
即,如下代码是行不通的:
# router.coffee
# 值仅用于每次获取时作为唯一标记,值没有任何用途。
# 也可以用 Math.random() 或者 new Date().getTime() 等代替
uuid = 1
unique = ->
uuid += 1
return uuid
fetchComponent = (path, resolve) ->
# 加载前显示动画
$('.page-loading-loader').show()
_uuid = unique()
# 这是的 require 会报错的!!!
require([path], (component) ->
# 如果加载完成的组件就是当前用户最后点击的那个组件,那么才 resolve
if _uuid == uuid
# 加载完成后渲染并隐藏动画
resolve(component)
$('.page-loading-loader').hide()
)
router: [
{
path: '/lazy-load'
name: 'lazy_load'
# 按需加载:
component: (resolve) ->
fetchComponent('../views/lazy-load.vue', resolve)
},
...
]
3.3.2 使用装饰器
我们不过是想在 require 前后做一些初始化及收尾的工作,用 装饰器 来解决再好不过。
# 值仅用于每次获取时作为唯一标记,值没有任何用途。
# 也可以用 Math.random() 或者 new Date().getTime() 等
uuid = 1
unique = ->
uuid += 1
return uuid
setLoadingVisibility = (func) ->
$('.page-loading-loader')[func]()
# require 之前及之后,做一些初始化及收尾工作。装饰器。
decorator = (cb) ->
(resolve) ->
setLoadingVisibility('show')
current_component = unique()
# hideLoadingResolve 相当于 webpack 的 component: (resolve) -> 中的 resolve,
# 只是多做一些收尾工作(隐藏 loading 动画),最终还是返回了 resolve
hideLoadingResolve = (fetched_component) ->
(component) ->
# 如果获取完成的不是当前正在获取的组件,那么什么也不做。
# 如,用户连续点击两个导航菜单:
# 第一个还没有获取成功,第二个就被点击了。
# 此时此 resolve 最新的那个
if current_component == fetched_component
setLoadingVisibility('hide')
return resolve(component)
cb(hideLoadingResolve(current_component))
router: [
{
path: '/'
name: 'dashboard'
component: decorator (resolve) ->
require(['./list.vue'], resolve)
},
{
path: '/group'
name: 'group'
component: decorator (resolve) ->
require(['./group.vue'], resolve)
},
...
]
如上所示加载组件就方便了,用 decorator 装饰一下就好了:
之前为:
component: (resolve) ->
require(['./list.vue'], resolve)
现在加一个单词 decorator 即可:
component: decorator (resolve) ->
require(['./list.vue'], resolve)
以上。