Vue Router

后端路由与前端路由

后端路由:根据不同的URL地址分发不同的资源

前端路由:根据不同的用户事件,显示不同的页面内容(负责事件监听,触发事件后,通过事件函数渲染不同内容)

多页面应用模式MPA(Multi Page Application)单页面应用模式SPA(Single Page Application)
应用构成由多个完整页面构成一个外壳页面和多个页面片段构成
跳转方式页面之间的跳转是从一个页面到另一个页面一个页面片段删除或隐藏,加载另一个页面片段并显示。片段间的模拟跳转,没有开壳页面
跳转后公共资源是否重新加载
URL模式http://xxx/page1.htmlhttp://xxx/page2.htmlhttp://xxx/shell.html#page1http://xxx/shell.html#page2
用户体验页面间切换加载慢,不流畅,用户体验差,尤其在移动端页面片段间切换快,用户体验好,包括移动设备
能否实现转场动画容易实现(手机APP动效)
页面间传递数据依赖URLcookie或者localstorage,实现麻烦页面传递数据容易(Vue中的父子组件通讯props对象或Vuex
搜索引擎优化(SEO)可以直接做需要单独方案(SSR)
特别适用的范围需要对搜索引擎友好的网站对体验要求高,特别是移动应用
开发难度较低,框架选择容易较高,需要专门的框架来降低这种模式的开发难度

前端路由与SPA

传统的后端路由,根据客户端请求的不同网址,返回不同的网页内容。这样会造成服务器压力增加以及每次都重新请求,响应慢,用户体验下降。于是SPA应运而生,在url地址改变的过程中,通过js来实现不同的UI之间的切换,而不再向服务器重新请求页面,只通过ajax向服务器请求数据,对用户来说这种无刷新的、即时响应有更好的体验。其中根据url地址的变化而展示不同的UI,就是通过前端路由来实现的

前端路由的实现方式

基于location.hash实现(location.hash+hashchange事件)

location.hash的值是url中#后面的内容,如http://www.163.com#netease,location.hash为'#netease'

hash满足一下几个特性,才使得其可以实现前端路由:

  • url中hash值的变化并不会重新加载页面,因为hash是用来指导浏览器行为的,对服务端是无用的,所以不会包括在http请求中

  • hash值的改变,都会在浏览器的访问历史中增加一个记录,能通过浏览器的回退、前进控制hash的切换

  • 我们可以通过onhashchange事件,监听到hash值的变化,从而响应不用路径的逻辑处理。我们就可以在onhashchange事件,根据hash转换来更新对应的视图,但不会去重新请求页面

    window.addEventListener('hashchange', function() {}, false)
    

触发hash值的变化有2种方法:

一种是通过a标签,设置href属性,当a标签点击之后,地址栏会改变同时触发onhashchange事件

<a href="#kaola">to kaola</a>

另一种是通过js直接赋值给location.hash,也会改变url同时触发onhashchange事件

location.hash="#kaola"

具体实现方式:

<div id="app">
    <a href="#/zhuye">主页</a>
    <a href="#/keji">科技</a>
    <a href="#/caijing">财经</a>
    <a href="#/yule">娱乐</a>
    <!-- component标签当做是组件的占位符 -->
    <component :is="comName"></component>
</div>
<script>
    const zhuye = {
        template: "<h1>主页信息</h1>"
    }
    const keji = {
        template: "<h1>科技信息</h1>"
    }
    const caijing = {
        template: "<h1>财经信息</h1>"
    }
    const yule = {
        template: "<h1>娱乐信息</h1>"
    }
    const vm = new Vue({
        el: "#app",
        data: {
            comName: zhuye
        },
        // 注册私有组件
        components: {
            zhuye,
            keji,
            caijing,
            yule
        }
    })
    window.onhashchange = function () {
        switch (location.hash.slice(1)) {
            case "/zhuye":
                vm.comName = zhuye
                break
            case "/keji":
                vm.comName = keji
                break
            case "/caijing":
                vm.comName = caijing
                break
            case "/yule":
                vm.comName = yule
                break
        }
    }
</script>

基于history新API实现(history.pushState()+popState事件)

history.pushState():会增加一条新的历史记录

传入的新url不一定是绝对地址。如果是相对地址,它将以当前url为基准。传入的新url与当前url应该是同源否则pushState()会抛出异常

const state = { 'page_id': 1, 'user_id': 5 } //一个与指定网址相关的状态对象
const title = null //新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
const url = 'hello-world.html' //新的网址,必须与当前url同源
window.history.pushState(state, title, url)

history.replaceState():会替换当前的历史记录

两个API相同之处:都可以修改url地址,操作浏览器的历史记录同时state会改变,但不会引起页面的刷新

通过onpopstate监听history.state的变化,来指引js做加载以及渲染等任务

window.addEventListener('popstate',function(){
    //获取到最新的state值
	var state = history.state
})
hash模式history模式
url显示有#,很Low无#,好看
回车刷新可以加载到hash值对应页面一般就是404掉了
支持版本支持低版本浏览器和IE浏览器HTML5新推出的API

history模式存在的问题:

export default new Router({
  mode: 'history'
}

在路由跳转时,路径改变了,其实并没有加载页面,当我们刷新的时候就会报404错误,因为Vue Router设置的路径不是真实存在的路径

解决history模式下刷新报404的弊端,这就需要服务器端做点手脚,配置一下 apache 或 nginx 的url重定向,重定向到你的首页路由上就ok了(将不存在的路径请求重定向到入口文件index.html)

配置nginx的方式:

location / {
  try_files $uri $uri/ /index.html
}

Vue Router插件

Vue Router是Vue.js官方的路由管理器,可以非常方便的用于SPA应用程序的开发,支持hash模式和HTML5 history模式

添加路由链接

router-link标签默认被渲染为a标签

to属性默认被渲染为href属性

to属性值默认被渲染为#开头的hash地址

<router-link to="/user">User</router-link>

添加路由填充位(路由占位符)

将来通过路由规则匹配到的组件,将会被渲染到router-view所在位置

<router-view></router-view>

router.js

每一个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性:

  • path:表示当前路由规则匹配的hash地址
  • component:表示当前路由规则对应要展示的组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from './components/Login.vue'
import Admin from './components/Admin.vue'
import Customer from './components/Customer.vue'
import AdminHome from './components/Admin/AdminHome.vue'
import CustomerHome from './components/Customer/CustomerHome.vue'
import UserList from './components/Admin/UserList.vue'
import MyOrder from './components/Customer/MyOrder.vue'
import HomeCooking from './components/Customer/HomeCooking.vue'
Vue.use(VueRouter)
// 创建路由实例对象router
const router = new VueRouter({
  // routes是路由规则数组
  routes: [
      // redirect路由重定向,强制跳转
      {path:'/', redirect:'/login'},
      {path:'/login', component:Login},
      {path:'/admin', component:Admin, redirect:'/adminHome', children:[
        {path:'/adminHome', component:AdminHome},
        {path:'/userList', component:UserList}
      ]},
      {path:'/customer', component:Customer, redirect:'/customerHome', children:[
        {path:'/customerHome', component:CustomerHome},
        {path:'/myOrder', component:MyOrder},
        {path:'/homeCooking', component:HomeCooking}
      ]}
  ]
})
// 挂载路由导航守卫
router.beforeEach((to, from, next) => {
    // to将要访问的路径
    // from代表从哪个路径跳转而来
    // next是一个函数,表示放行
    // next('/login')强制跳转
    if(to.path === '/login') return next()
    // 获取token
    const token = window.sessionStorage.getItem('token')
    if(!token) return next('/login')
    next()
})
export default router

Vue Router动态匹配路由

通过动态路由参数的模式进行路由匹配:

<div id="app">
    <router-link to="/user/1">User1</router-link>
    <router-link to="/user/2">User2</router-link>
    <router-link to="/user/3">User3</router-link>
    <router-link to="/register">Register</router-link>
    <router-view></router-view>
</div>
<script>
    const User = {
        // 路由组件中通过$route.params获取动态路由参数
        template: "<h1>User组件--用户id为{{$route.params.id}}</h1>"
    }
    const Register = {
        template: "<h1>Register组件</h1>"
    }
    const router = new VueRouter({
        routes: [{
                path: "/",
                redirect: "/user"
            }, {
                // 动态路由参数以冒号开头(:id)
                path: "/user/:id",
                component: User
            },
            {
                path: "/register",
                component: Register
            }
        ]
    })
    const vm = new Vue({
        el: "#app",
        router: router
    })
</script>

通过路由组件传递参数:

$route与对应路由形成高度耦合且不够灵活,所以可以使用 props 将组件和路由解耦

props的值为布尔类型:

<div id="app">
    <router-link to="/user/1">User1</router-link>
    <router-link to="/user/2">User2</router-link>
    <router-link to="/user/3">User3</router-link>
    <router-link to="/register">Register</router-link>
    <router-view></router-view>
</div>
<script>
    const User = {
        // 使用props接收动态路由参数
        props: ["id"],
        template: "<h1>User组件--用户id为{{id}}</h1>"
    }
    const Register = {
        template: "<h1>Register组件</h1>"
    }
    const router = new VueRouter({
        routes: [{
                path: "/",
                redirect: "/user"
            }, {
                path: "/user/:id",
                component: User,
                // props设置为true,route.params将会被设置为组件属性
                props: true
            },
            {
                path: "/register",
                component: Register
            }
        ]
    })
    const vm = new Vue({
        el: "#app",
        router: router
    })
</script>

props的值为对象类型:

<div id="app">
    <router-link to="/user/1">User1</router-link>
    <router-link to="/user/2">User2</router-link>
    <router-link to="/user/3">User3</router-link>
    <router-link to="/register">Register</router-link>
    <router-view></router-view>
</div>
<script>
const User = {
    props: ["name", "age"],
    template: "<h1>User组件--用户id为{{$route.params.id}}姓名为{{name}}年龄为{{age}}</h1>"
}
const Register = {
    template: "<h1>Register组件</h1>"
}
const router = new VueRouter({
    routes: [{
            path: "/",
            redirect: "/user"
        }, {
            path: "/user/:id",
            component: User,
            props: {
                name: "张三",
                age: 20
            }
        },
        {
            path: "/register",
            component: Register
        }
    ]
})
const vm = new Vue({
    el: "#app",
    router: router
})

props的值为函数类型:

<div id="app">
    <router-link to="/user/1">User1</router-link>
    <router-link to="/user/2">User2</router-link>
    <router-link to="/user/3">User3</router-link>
    <router-link to="/register">Register</router-link>
    <router-view></router-view>
</div>
<script>
    const User = {
        props: ["id", "name", "age"],
        template: "<h1>User组件--用户id为{{id}}姓名为{{name}}年龄为{{age}}</h1>"
    }
    const Register = {
        template: "<h1>Register组件</h1>"
    }
    const router = new VueRouter({
        routes: [{
                path: "/",
                redirect: "/user"
            }, {
                path: "/user/:id",
                component: User,
                props: route => ({
                    name: "张三",
                    age: 20,
                    id: route.params.id
                })
            },
            {
                path: "/register",
                component: Register
            }
        ]
    })
    const vm = new Vue({
        el: "#app",
        router: router
    })
</script>

Vue Router命名路由

<div id="app">
    <router-link to="/user/1">User1</router-link>
    <router-link to="/user/2">User2</router-link>
    <router-link :to="{name: 'user', params: {id:3}}">User3</router-link>
    <router-link to="/register">Register</router-link>
    <router-view></router-view>
</div>
<script>
    const User = {
        props: ["id", "name", "age"],
        template: "<h1>User组件--用户id为{{id}}姓名为{{name}}年龄为{{age}}</h1>"
    }
    const Register = {
        template: "<h1>Register组件</h1>"
    }
    const router = new VueRouter({
        routes: [{
                path: "/",
                redirect: "/user"
            }, {
                // 命名路由
                name: "user",
                path: "/user/:id",
                component: User,
                props: route => ({
                    name: "张三",
                    age: 20,
                    id: route.params.id
                })
            },
            {
                path: "/register",
                component: Register
            }
        ]
    })
    const vm = new Vue({
        el: "#app",
        router: router
    })
</script>

Vue Router编程式导航

页面导航的两种方式:

  • 声明式导航:通过点击链接实现导航的方式,如网页中的 <a></a> 链接或者Vue中的 <router-link></router-link>
  • 编程式导航:通过调用JS形式的API实现导航的方式,如:网页中的 window.location.href

常用的编程式导航API:

  • this.$router.push():跳转到指定url,向history栈添加一个新的记录,点击后退会返回到上个页面
  • this.$router.replace():跳转到指定url,替换history栈中最后一个记录,点击后退会返回到上上个页面
  • this.$router.go(n):向前或向后跳转n个页面,n可为正数也可为负数

this.$router.push()的参数规则:

  • 字符串(路径名称):this.$router.push('/home')

    const User = {
        template: '<div><button @click="goRegister"></button></div>',
        methods: {
            goRegister: function(){
                this.$router.push('/register')
            }
        }
    }
    
  • 对象:this.$router.push( {path:'/home'} )

  • 命名路由(传递参数):this.$router.push( {name:'/user', params:{userId:123}} )

  • 带查询参数:this.$router.push( {path:'/register', query:{name:'lisi'}} ) 变成(/register?name=lisi

this.$router.go(n)的参数规则:

  • this.$router.go(-1):后退一步记录
  • this.$router.go(1):前进一步记录
  • this.$router.go(3):前进三步记录

新闻动态跳转新闻动态详情页具体实现

http://www.zjyushi.com/news

http://www.zjyushi.com/newsDetail/1281104872833888258

在new.vue中点击跳转至对应的newsDetail

handleDetails (val) {
    this.$router.push({
        name: 'newsDetail',
        // 匹配newsDetail命名路由并传递id参数
        params: {
            id: val.newsId
        }
    })
}

router.js

{
    // 接收id参数
	path: '/newsDetail/:id',
	name: 'newsDetail',
	component: resolve => require(['@/views/newsDetail'], resolve)
}

newsDetail.vue

created () {
    // 路由组件中通过$route.params获取动态路由参数
    this.newsId = this.$route.params.id
    this.getNewsDetail()
},
methods: {
    getNewsDetail () {
        // axios携带id参数发起请求,查询出id对应的新闻动态
        this.$apis.getNewsDetail({ newsId: this.newsId }).then(res => {
    }).catch(() => {})
}

导航守卫

全局前置守卫

router.beforeEach((to, from, next) => {
    // to将要访问的路径
    // from代表从哪个路径跳转而来
    // next是一个函数,表示放行
    // next('/login')强制跳转
    if(to.path === '/login') return next()
    // 获取token
    const token = window.sessionStorage.getItem('token')
    if(!token) return next('/login')
    next()
})

全局解析守卫

全局后置钩子

router.afterEach((to, from) => {
  // ...
})

路由独享的守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内的守卫

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

路由元信息(meta字段)

直接在路由配置的时候,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。用它来做登录校验再合适不过了

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此我们需要遍历 $route.matched 来检查路由记录中的 meta 字段

只需要判断item下面的meta对象中的login_require是不是true,就可以做一些限制了

router.beforeEach((to, from, next) => {
  if (to.matched.some(function (item) {
    return item.meta.login_require
  })) {
    next('/login')
  } else 
    next()
})

过渡效果

单个路由的过渡(每个路由组件有各自的过渡效果,可以在各路由组件内使用 <transition> 并设置不同的 name)

const Foo = {
  template: `
    <transition name="slide">
      <div class="foo">...</div>
    </transition>
  `
}
const Bar = {
  template: `
    <transition name="fade">
      <div class="bar">...</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'
  }
}

数据获取

有时候进入某个路由后,需要从服务器获取数据。如:在渲染用户信息时,需要从服务器获取用户的数据。可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航

滚动行为

使用keep-alive标签后部分安卓机返回缓存页位置不精确问题

解决方案:

meta:{
    keepAlive:true //需要被缓存的组件
}
<div id="app">
  <keep-alive>
   <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
  <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
const router = new Router({
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition && to.meta.keepAlive) {
            return savedPosition 
        }
        return { x: 0, y: 0 }
    }
})

不能返回到原有浏览位置

当我们从A页面跳转到B页面,然后在B页面浏览一篇很长的文章,但是这时候我们讲页面滚动到某个位置的时候,我们不小心按了一下浏览器的后退键,这时候我们再按浏览器的前进键的时候会发现,该文章又要从头开始阅读了

在route后面加上scrollBehavior这个函数。该scrollBehavior函数接收to和from路由对象,第三个参数savedPosition仅在这是popstate导航(由浏览器的后退/前进按钮触发)时才可用该函数可以返回滚动位置对象。如果返回假值或空对象,则不会进行滚动

const router = new Router({
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition && to.meta.keepAlive) {
            return savedPosition 
        }
        return { x: 0, y: 0 }
    }
})

路由懒加载

当路由被访问的时候才加载对应组件,这样就更加高效

  • 安装 @babel/plugin-syntax-dynamic-import

  • 在 babel.config.js 配置文件中声明该插件

    const prodPlugins = []
    if(process.env.NODE_ENV === 'production'){
        prodPlugins.push('transform-remove-console')
    }
    module.exports = {
        plugins: [
            //发布阶段需要用到的插件数组
            ...prodPlugins,
            '@babel/plugin-syntax-dynamic-import'
        ]
    }
    
  • 将路由改为按需加载的形式

    import Login from './components/Login.vue'
    import Admin from './components/Admin.vue'
    import Customer from './components/Customer.vue'
    import AdminHome from './components/Admin/AdminHome.vue'
    import CustomerHome from './components/Customer/CustomerHome.vue'
    // 改为按需加载的形式
    const Login = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Login.vue')
    const Admin = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Admin.vue')
    const Customer = () => import(/* webpackChunkName: "login_admin_customer" */ './components/Customer.vue')
    
    const AdminHome = () => import(/* webpackChunkName: "adminhome_customerhome" */ './components/Admin/AdminHome.vue')
    const CustomerHome = () => import(/* webpackChunkName: "adminhome_customerhome" */ './components/Customer/CustomerHome.vue')
    

参考文章

Vue Router

终于搞明白了路由元信息是个啥了

Vue / keep-alive

Vue scrollBehavior 滚动行为

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值