https://router.vuejs.org/zh-cn/
https://www.cnblogs.com/lijuntao/p/7777581.html
https://blog.csdn.net/weixin_38704338/article/details/79103230
https://www.cnblogs.com/zhanyishu/p/6587571.html
https://react-guide.github.io/react-router-cn/index.html
vue-router路由懒加载(解决vue项目首次加载慢)
导航守卫
(译者:『导航』表示路由正在发生改变。)
正如其名,vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。 组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
mounted: function () { this.showdevIndxNav = true this.initWVBridge() document.getElementById('mainPage').addEventListener('touchstart', this.touchStart, false) document.getElementById('mainPage').addEventListener('touchend', this.touchEnd, false) window.addEventListener('resize', this.handleResize) window.addEventListener('offline', this.updateOnlineStatus) this.initNetwork() }, beforeRouteEnter (to, from, next) { if (from.name === 'questionDetails') { to.meta.isBack = true } next() },
全局守卫
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
全局解析守卫
2.5.0 新增
在 2.5.0+ 你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
路由独享的守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与全局前置守卫的方法参数是一样的。
过渡动效
<router-view>
是基本的动态组件,所以我们可以用 <transition>
组件给它添加一些过渡效果:
<transition>
<router-view></router-view>
</transition>
单个路由的过渡
上面的用法会给所有路由设置一样的过渡效果,如果你想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 <transition>
并设置不同的 name。
const Foo = {
template: `
<transition name="slide">
<div class="foo">...</div>
</transition>
`
}
基于路由的动态过渡
还可以基于当前路由与目标路由的变化关系,动态设置过渡效果:
<!-- 使用动态的 transition name -->
<transition :name="transitionName">
<router-view></router-view>
</transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
mode
类型:
string
默认值:
"hash" (浏览器环境) | "abstract" (Node.js 环境)
可选值:
"hash" | "history" | "abstract"
配置路由模式:
hash
: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。history
: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。abstract
: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
to
类型:
string | Location
required
表示目标路由的链接。当被点击后,内部会立刻把 to
的值传到 router.push()
,所以这个值可以是一个字符串或者是描述目标位置的对象。
<!-- 字符串 -->
<router-link to="home">Home</router-link>
replace
类型:
boolean
默认值:
false
设置 replace
属性的话,当点击时,会调用 router.replace()
而不是 router.push()
,于是导航后不会留下 history 记录。
<router-link :to="{ path: '/abc'}" replace></router-link>
append
类型:
boolean
默认值:
false
设置 append
属性后,则在当前(相对)路径前添加基路径。例如,我们从 /a
导航到一个相对路径 b
,如果没有配置 append
,则路径为 /b
,如果配了,则为 /a/b
<router-link :to="{ path: 'relative/path'}" append></router-link>
tag
类型:
string
默认值:
"a"
有时候想要 <router-link>
渲染成某种标签,例如 <li>
。 于是我们使用 tag
prop 类指定何种标签,同样它还是会监听点击,触发导航。
<router-link to="/foo" tag="li">foo</router-link>
<!-- 渲染结果 -->
<li>foo</li>
active-class
类型:
string
默认值:
"router-link-active"
设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项
linkActiveClass
来全局配置。exact
类型:
boolean
默认值:
false
"是否激活" 默认类名的依据是 inclusive match (全包含匹配)。 举个例子,如果当前的路径是
/a
开头的,那么<router-link to="/a">
也会被设置 CSS 类名。按照这个规则,每个路由都会激活
<router-link to="/">
!想要链接使用 "exact 匹配模式",则使用exact
属性:<!-- 这个链接只会在地址为 / 的时候被激活 --> <router-link to="/" exact>
查看更多关于激活链接类名的例子可运行
event
2.1.0+
类型:
string | Array<string>
默认值:
'click'
声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。
exact-active-class
2.5.0+
类型:
string
默认值:
"router-link-exact-active"
配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项
linkExactActiveClass
进行全局配置的。
将激活 class 应用在外层元素
有时候我们要让激活 class 应用在外层元素,而不是 <a>
标签本身,那么可以用 <router-link>
渲染外层元素,包裹着内层的原生 <a>
标签:
<router-link tag="li" to="/foo">
<a>/foo</a>
</router-link>
在这种情况下,<a>
将作为真实的链接(它会获得正确的 href
的),而 "激活时的CSS类名" 则设置到外层的 <li>
。
方法
- router.beforeEach(guard)
- router.beforeResolve(guard) (2.5.0+): 此时异步组件已经加载完成
router.afterEach(hook)
增加全局的导航守卫。参考导航守卫。
在 2.5.0+ 这三个方法都返回一个移除已注册的守卫/钩子的函数。
router.push(location, onComplete?, onAbort?)
- router.replace(location, onComplete?, onAbort?)
- router.go(n)
- router.back()
router.forward()
动态的导航到一个新 URL。参考编程式导航。
router.getMatchedComponents(location?)
返回目标位置或是当前路由匹配的组件数组(是数组的定义/构造类,不是实例)。通常在服务端渲染的数据预加载时时候。
router.resolve(location, current?, append?)
编程式的导航
router.push(location, onComplete?, onAbort?)
注意:在 Vue 实例内部,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
。
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
router.replace(location, onComplete?, onAbort?)
跟 router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
。
操作 History
你也许注意到 router.push
、 router.replace
和 router.go
跟 window.history.pushState
、 window.history.replaceState
和 window.history.go
好像, 实际上它们确实是效仿 window.history
API 的。
因此,如果你已经熟悉 Browser History APIs,那么在 vue-router 中操作 history 就是超级简单的。
还有值得提及的,vue-router 的导航方法 (push
、 replace
、 go
) 在各类路由模式(history
、 hash
和 abstract
)下表现一致。
<router-link>
<router-link>
比起写死的 <a href="...">
会好一些,理由如下:
无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
在 HTML5 history 模式下,
router-link
会守卫点击事件,让浏览器不再重新加载页面。当你在 HTML5 history 模式下使用
base
选项之后,所有的to
属性都不需要写(基路径)了。
<router-view>
<router-view>
组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view>
渲染的组件还可以内嵌自己的 <router-view>
,根据嵌套路径,渲染嵌套组件。
命名视图
有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置(带上 s):
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
命名视图
- index.js
import Vue from 'vue'
import Router from 'vue-router'
import Goodlists from '@/Goodlists/goods'
import Title from '@/Goodlists/title'
import Img from '@/Goodlists/img'
import Cart from '@/Goodlists/cart'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/goods',
name: 'Goodlists',
components:{
default:Goodlists,
title:Title,
image:Img,
}
}
]
})
- App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view></router-view>
<router-view name="title" class="left"></router-view>
<router-view name="image" class="right"></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
在App.vue组件中,
方案一:直接引用sidebar组件的方式 <v-sidebar></v-sidebar>
<template> <div id="app"> <v-toast v-show="showToast"></v-toast> <v-alert v-show="showAlert"></v-alert> <v-loading v-show="loading"></v-loading> <v-header :title="title" :menu-display="menuDisplay" :back-display="backDisplay" :map-display="mapDisplay"></v-header> <div class="content" :class="{tabar: tabar}"> <transition name="slide-left"> <router-view></router-view> <router-view name="a"></router-view> </transition> </div> <v-tabar></v-tabar> <!--<v-sidebar></v-sidebar>--> <router-view name="a"></router-view> </div> </template> <script> import tabar from '@/components/tabar' import header from '@/components/header' import sidebar from '@/components/sidebar' import toast from '@/components/toast' import alert from '@/components/alert' import loading from '@/components/loading' import { mapGetters, mapActions } from 'vuex' export default { name: 'app', components: { 'v-tabar': tabar, 'v-header': header, 'v-sidebar': sidebar, 'v-toast': toast, 'v-alert': alert, 'v-loading': loading, },
方案二:命名视图的方式引入sidebar <router-view name="a"></router-view>
在router/index.js中设置命名视图的components
import sidebar from '@/components/sidebar'
Vue.use(Router) export default new Router({ linkActiveClass: 'active', routes: [ { path: '/', name: 'home', components: { default: Home, a: sidebar } }, { path: '/home', name: 'home', component: Home }, { path: '/sport', name: 'sport', components: { default: Sport, a: sidebar } }, { path: '/travel', name: 'travel', components: { default: Travel, a: sidebar } },
数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。
导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
导航完成后获取数据
当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created
钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
假设我们有一个 Post
组件,需要基于 $route.params.id
获取文章数据:
<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
在导航完成前获取数据
通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter
守卫中获取数据,当数据获取成功后只调用 next
方法。
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
vue路由懒加载:
https://www.cnblogs.com/lijuntao/p/7777581.html
懒加载:----------------------------------------------------?
也叫延迟加载,即在需要的时候进行加载,随用随载。
为什么需要懒加载?
像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,时间过长,会出啊先长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
简单的说就是:进入首页不用一次加载过多资源造成用时过长!!!
如何实现?
懒加载写法:
非懒加载的路由配置:
一、 什么是懒加载
懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。
二、为什么需要懒加载
在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
三、如何与webpack配合实现组件懒加载
1、在webpack配置文件中的output路径配置chunkFilename属性
1
2
3
4
5
6
|
output: {
path: resolve(__dirname,
'dist'
),
filename: options.dev ?
'[name].js'
:
'[name].js?[chunkhash]'
,
chunkFilename:
'chunk[id].js?[chunkhash]'
,
publicPath: options.dev ?
'/assets/'
: publicPath
},
|
chunkFilename路径将会作为组件懒加载的路径
2、配合webpack支持的异步加载方法
- resolve => require([URL], resolve), 支持性好
- () => system.import(URL) , webpack2官网上已经声明将逐渐废除, 不推荐使用
- () => import(URL), webpack2官网推荐使用, 属于es7范畴, 需要配合babel的syntax-dynamic-import插件使用, 具体使用方法如下
1
npm install --save-dev babel-core babel-loader babel-plugin-syntax-dynamic-
import
babel-preset-es2015
1234567use: [{
loader:
'babel-loader'
,
options: {
presets: [[
'es2015'
, {modules:
false
}]],
plugins: [
'syntax-dynamic-import'
]
}
}]
四、具体实例中实现懒加载
1、路由中配置异步组件
1
2
3
4
5
6
7
8
9
10
|
export
default
new
Router({
routes: [
{
mode:
'history'
,
path:
'/my'
,
name:
'my'
,
component: resolve => require([
'../page/my/my.vue'
], resolve),
//懒加载
},
]
})
|
2、实例中配置异步组件
1
2
3
4
|
components: {
historyTab: resolve => {require([
'../../component/historyTab/historyTab.vue'
], resolve)},
//懒加载
//historyTab: () => import('../../component/historyTab/historyTab.vue')
},
|
3、全局注册异步组件
1
2
3
|
Vue.component(
'mideaHeader'
, () => {
System.
import
(
'./component/header/header.vue'
)
})
|
五、配置异步组件实现懒加载的问题分析
3、如果在两个异步加载的页面中分别同步与异步加载同一个组件时是否会造成资源重用? 如:
//a页面 import historyTab from '../../component/historyTab/historyTab.vue'; export default { components: { historyTab }, } //b页面 export default { components: { historyTab: resolve => {require(['../../component/historyTab/historyTab.vue'], resolve)},//懒加载 }, }
答: 会, 将会造成资源重用, 根据打包后输出的结果来看, a页面中会嵌入historyTab组件的代码, b页面中的historyTab组件还是采用异步加载的方式, 另外打包chunk;
解决方案: 组件开发时, 如果根页面没有导入组件的情况下,而是在其他异步加载页面中同时用到组件, 那么为实现资源的最大利用,在协同开发的时候全部人都使用异步加载组件
4、在异步加载页面中载嵌入异步加载的组件时对页面是否会有渲染延时影响?
答:会, 异步加载的组件将会比页面中其他元素滞后出现, 页面会有瞬间闪跳影响;
解决方案:因为在首次加载组件的时候会有加载时间, 出现页面滞后, 所以需要合理的进行页面结构设计, 避免首次出现跳闪现象;
六、懒加载的最终实现方案
1、路由页面以及路由页面中的组件全都使用懒加载
优点:(1)最大化的实现随用随载
(2)团队开发不会因为沟通问题造成资源的重复浪费
缺点:(1)当一个页面中嵌套多个组件时将发送多次的http请求,可能会造成网页显示过慢且渲染参差不齐的问题
2、路由页面使用懒加载, 而路由页面中的组件按需进行懒加载, 即如果组件不大且使用不太频繁, 直接在路由页面中导入组件, 如果组件使用较为频繁使用懒加载
优点:(1)能够减少页面中的http请求,页面显示效果好
缺点:(2)需要团队事先交流, 在框架中分别建立懒加载组件与非懒加载组件文件夹
3、路由页面使用懒加载,在不特别影响首页显示延迟的情况下,根页面合理导入复用组件,再结合方案2
优点:(1)合理解决首页延迟显示问题
(2)能够最大化的减少http请求, 且做其他他路由界面的显示效果最佳
缺点:(1)还是需要团队交流,建立合理区分各种加载方式的组件文件夹
八、具体代码实现设计
1、路由设计:
import Router from 'vue-router'; import Vue from 'vue'; Vue.use(Router); export default new Router({ routes: [ { mode: 'history', path: '/home', name: 'home', component: resolve => require([URL], resolve),//懒加载 children: [ { mode: 'history', path: '/home/:name', name: 'any', component: resolve => require(['../page/any/any.vue'], resolve),//懒加载 }, ] }, { mode: 'history', path: '/store', name: 'store', component: resolve => require(['../page/store/store.vue'], resolve),//懒加载, children: [ { mode: 'history', path: '/store/:name', name: 'any', component: resolve => require(['../page/any/any.vue'], resolve),//懒加载 }, ] }, { mode: 'history', path: '/my', name: 'my', component: resolve => require(['../page/my/my.vue'], resolve),//懒加载, children: [ { mode: 'history', path: '/my/:name', name: 'any', component: resolve => require(['../page/any/any.vue'], resolve),//懒加载 }, ] }, ] })
(1)首层的路由根组件分别对应的tab页面
(2)根目录后跟着各个子路由页面,子路由采用动态路由配合路由的编程式导航再加上vuex,最优化提高开发效率
直接贴上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/**
* Created by ZHANZF on 2017-3-20.
*/
//vuex配置
import
Vue from
'vue'
;
import
Vuex from
'vuex'
;
Vue.use(Vuex);
export
default
new
Vuex.Store({
state: {
//路由组件存储器
routers: {}
},
getters: {
routers: state => {
return state.data;
}
},
mutations: {
//动态增加路由
addRouter: (state, data) => {
state.routers = Object.assign({}, state.routers, {
[data.name]: data.component
});
}
},
actions: {
acMethods({commit}) {
}
},
})
//根目录中注册路由组件
window.midea = {
registerRouter(name, component) {
Store.commit( 'addRouter' , {
name: name,
component: component
})
}
};<br><br>
//页面使用路由导航
|
openAnyPage() { midea.registerRouter('module', resolve => {require(['../module/module.vue'], resolve)});//懒加载 this.$router.push({path: '/home/module', query: {title: this.title}}); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//页面中使用动态组件 <template> <component :is="currentRouter" :moduleName="title"></component> </template><br><script src="./any.js">
export
default
{
data () {
return
{
routeName:
''
,
currentRouter: '' ,
title:
''
,
}
},
created() {
this
.routeName =
this
.$route.params.name;
this
.title =
this
.$route.query.title;
this .currentRouter = this .$store.state.routers[ this .routeName];
},
methods: {
}
}
</script>
|
二、动态组件的设计
直接用即用即加的方式在实例或路由中直接配置异步组件