前端路由与vue-router原理

前端路由vue-router

前端路由概念

理解:url与ui的映射关系,再改变url的情况下,展示对应内容,但是不会对整个页面进行刷新

(1)监视地址栏变化;(2)查找当前路径对应的页面组件;(3)将找到的页面组件替换router-vieW 的位置

实现前端路由的两个核心:

1》修改url,但是页面不刷新

2》能够检测到url的变化

hash

1、hash特点

hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新

hash 的变化不会导致浏览器向服务端发送请求,所以也就不会刷新页面;hash的实现全部在前端,不需要后端服务器配合,兼容性好,主要是通过监听hashchange事件,处理前端业务逻辑

2、hashchange事件

通过hashchange事件可以监听到hash值的变化

修改hash的几种方式:

  • 通过浏览器的前进、后退(调用history的back,go,forward方法触发该事件)
  • 通过a标签的href属性修改url
  • 通过window.location修改url

history

1、pushState与replaceState

HTML5新出来的 history.pushState() 和 history.replaceState() 能够改变 URL 的 path 部分但是不会引起页面刷新

仅改变网址url,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面

基本语法:

window.history.pushState(state, title, targetURL);
@状态对象:传给目标路由的信息,可为空
@页面标题:目前所有浏览器都不支持,填空字符串即可
@可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data
window.history.replaceState(state, title, targetURL);
@类似于pushState,但是会直接替换掉当前url,而不会在history中留下记录

2、popstate事件

popstate事件会在点击浏览器的前进、后退按钮(调用back、forward、go方法)时触发;

通过pushState、replaceState或标签改变 URL 不会触发 popstate 事件



  
    
    
    history
  
  
    跳转
    
    
  

但是我们可以拦截pushState、replaceState的调用和a标签的点击事件来检测url的变化

如果要触发popstate可以通过js 调用history的back,go,forward方法来触发该事件

原生js实现前端路由

基于hash



  
    
    
    hash
  
  
    修改url
    
基于哈希来实现前端路由

基于history



  
    
    
    history
  
  
    history
    
    

我们通过a标签的href属性来改变URL的path值(当改变path值时,如果不是只修改hash,默认会触发页面的跳转,虽然不触发popstate事件,但是真个页面回刷新,所以需要拦截 标签点击事件默认行为)点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。

我们监听popState事件。一旦事件触发,就改变routerView的内容。

vue-router路由

三种模式:

  1. hash:哈希模式

  2. history:历史模式

  3. abstract:抽象模式

hash模式和history模式都是利用的浏览器api,abstract是在不支持浏览器API的环境下使用

基本使用

import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
  mode: 'hash',
  routes:[],
})
export default router

从它的使用上可以看出,VueRouter是一个类或构造函数,它通过use来注册,那肯定有一个install方法

下面是install的源码:“vue-router”: “^3.6.5”

import View from './components/view'
import Link from './components/link'

export let _Vue

export function install (Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined

  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

注册了RouterView、RouterLink两个全局组件

mixin的作用是将mixin的内容混合到Vue的初始参数options中

为什么是beforeCreate而不是created呢?因为如果是在created操作的话,$options已经初始化好了

如果当前是根实例:那么就会添加_routerRoot和_router属性,this._routerRoot也就是拿到根实例,this._router就是传入的router

        this._routerRoot = this
        this._router = this.$options.router

如果不是根实例:也会添加_routerRoot属性,如果

        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this

这样每个组件中都可以拿到可以获取到根组件和传入的router了,子组件可以通过this.$parent._router.$options也就是this._routerRoot._router来拿到传入的router了

判断了是不是根组件是根据有没有传入router属性,在main.js入口文件中,我们是传入了router属性,但是在App.vue组件中,我们不会传入router

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app')

为什么判断当前组件是子组件,就可以通过父组件拿到_root根组件呢

父beforeCreate-> 父created -> 父beforeMount -> 子beforeCreate ->子create ->子beforeMount ->子 mounted -> 父mounted

在执行子组件的beforeCreate的时候,父组件已经执行完beforeCreate了,那理所当然父组件拿到父组件中的_router,父组件又会向上一级父组件查找,一种递归式的查找

vue-router中hash与history路由模式的区别

他们的区别主要是因为他们的实现原理不同

1》形式上,哈希模式有#号,历史模式没有;

哈希有#号,不美观;如果考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合推广宣传

2》监听的事件不一样

hash模式监听的是hashChange事件,history模式监听的是popState事件

2》pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL
pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中

4》请求不同,历史模式会请求完整的url地址,而哈希模式只会请求#前面的地址,所以需要设置404的代理;而哈希不会,默认会展示之前页面的内容,但是你可以配置对应404页面

5》哈希模式的SEO不好;历史模式SEO相对会更好

6》push 和 replace 方法的实现不一样

使用中常见的一些处理

哈希模式配置404:

  {
    path: '/404',
    component: () => import('../views/404.vue'),
  },
  {
    path: '*',
    redirect: '/404',
  },

history模式处理404

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

当请求的文件或目录不存在时,将请求重定向到index.html。这使得所有的路由请求都指向了Vue应用的入口页面

在开发环境需要设置historyApiFallback

  devServer: {
    historyApiFallback: {
      index: '/index.html',
    },
  },

this.$router.push()如果是跳和当前路由一样的路由,就会报错,下面就捕获了下错误:

const originalPush = VueRouter.prototype.push
const originalReplace = VueRouter.prototype.replace
VueRouter.prototype.push = function (location, onResolve, onReject) {
  if (onResolve || onReject)
    return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch((err) => err)
}
VueRouter.prototype.replace = function (location, onResolve, onReject) {
  if (onResolve || onReject)
    return originalReplace.call(this, location, onResolve, onReject)
  return originalReplace.call(this, location).catch((err) => err)
}

你可能感兴趣的:(vue,前端,vue.js,javascript)