解析vue-router

文章内容输出来源:拉勾教育前端高薪训练营

history路由模式配置

  • history需要服务器支持
  • 单页应用中,服务端不存在http://www.testurl.com/login这样的的地址,会返回找不到该页面
  • 在服务端应该除了静态资源外都返回单页应用的index.html

Node.js处理history模式

const path = require('path')
// 导入处理history模式的模块
const history = require('connect-history-api-fallback')
// 导入express
const express = require('express')

const app = express()
// 注册处理history模式中间件
app.use(history())
// 处理静态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))

// 开启服务器
app.listen(3000, () => {
  console.log('服务器开启,localhost:3000')
})

nginx处理history模式

nginx目录不能有中文字符

nginx.conf的location中添加配置

try_files $uri $uri/ /index.html

启动nginx

# 启动
$ start nginx

# 重启
$ nginx -s reload

# 停止
$ nginx -s stop

vue-router

vue-router中Hash模式工作原理

  1. url中#后面的内容作为路径地址
  2. 监听hashchange事件,记录当前路由地址
  3. 根据当前路由地址找到对应组件重新渲染

vue-router中History模式工作原理

  1. 通过history.pushState()方法改变地址栏
  2. 监听popstate事件,记录改变后的地址(调用pushState和replaceState不会触发该事件,点击浏览器前进后退或history的back和forword时触发)
  3. 根据当前路由地址找到对应组件重新渲染

模拟开发一个vue-router类

params参数

  • options:路由规则
  • mode:路由模式
  • data:响应式对象,记录当前路由的信息
  • routeMap:记录路由地址和组件的对应关系

方法

  • Constructor(options): VueRouter
  • _install(Vue): void
  • setRouterData(): Vue.observable 根据路由模式返回对应的,在window.location中找到相应的参数设置current值
  • init(): void 初始化方法
  • initEvent(): void 注册popState事件,监听浏览器历史变化
  • initRouteMap(): void 初始化routeMap
  • initComponents(Vue): void 创建router-link和router-view

install静态方法

  • 判断当前插件是够已经被安装
  • 把Vue构造函数记录到全局变量
  • 把创建Vue实例时候传入的router对象注入到Vue实例上
let _Vue = null

export default class VueRouter {
  static install (Vue) {
    // 判断当前插件是够已经被安装
    if (VueRouter.install.installed) return
    VueRouter.install.installed = true
    // 把Vue构造函数记录到全局变量
    _Vue = Vue
    // 把创建Vue实例时候传入的router对象注入到Vue实例上,注入的时候要用混入,直接等的时候this是指向VueRouter类
    _Vue.mixin({
      beforeCreate () {
        // 判断this.$options.router,组件没有此属性
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      },
    })
  }
}

构造函数

let _Vue = null

export default class VueRouter {
  static install (Vue) {
    // 判断当前插件是够已经被安装
    if (VueRouter.install.installed) return
    VueRouter.install.installed = true
    // 把Vue构造函数记录到全局变量
    _Vue = Vue
    // 把创建Vue实例时候传入的router对象注入到Vue实例上
    _Vue.mixin({
      beforeCreate () {
        // 判断this.$options.router,组件没有此属性
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      },
    })
  }

  constructor (options) {
    this.options = options
    this.mode = options.mode
    // 判断mode的值是否正确
    if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
    this.routeMap = {}
    // 根据mode的值设置对应的current参数
    this.data = this.setRouterData(this.mode)
  }

  setRouterData (mode) {
    const { pathname, hash } = window.location
    return _Vue.observable({
      current: mode === 'history' ? pathname : hash.substr(1)
    })
  }
}

initRouteMap

遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中

let _Vue = null

export default class VueRouter {
 static install (Vue) {
   // 判断当前插件是够已经被安装
   if (VueRouter.install.installed) return
   VueRouter.install.installed = true
   // 把Vue构造函数记录到全局变量
   _Vue = Vue
   // 把创建Vue实例时候传入的router对象注入到Vue实例上
   _Vue.mixin({
     beforeCreate () {
       // 判断this.$options.router,组件没有此属性
       if (this.$options.router) {
         _Vue.prototype.$router = this.$options.router
       }
     },
   })
 }

 constructor (options) {
   this.options = options
   this.mode = options.mode
   // 判断mode的值是否正确
   if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
   this.routeMap = {}
   // 根据mode的值设置对应的current参数
   this.data = this.setRouterData(this.mode)
 }

 setRouterData (mode) {
   const { pathname, hash } = window.location
   return _Vue.observable({
     current: mode === 'history' ? pathname : hash.substr(1)
   })
 }

 initRouteMap () {
   // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
   this.options.routes.forEach(route => {
     this.routeMap[route.path] = route.component
   })
 }
}

initComponents

生成router-link和router-view

先说明下vue的构建版本

  • 运行时版:不支持template模板,需要打包的时候提前编译
  • 完整版:包含运行时和编译器,体积比运行时版大10kb左右,程序运行的时候把模板转换成render函数
let _Vue = null

export default class VueRouter {
 static install (Vue) {
   // 判断当前插件是够已经被安装
   if (VueRouter.install.installed) return
   VueRouter.install.installed = true
   // 把Vue构造函数记录到全局变量
   _Vue = Vue
   // 把创建Vue实例时候传入的router对象注入到Vue实例上
   _Vue.mixin({
     beforeCreate () {
       // 判断this.$options.router,组件没有此属性
       if (this.$options.router) {
         _Vue.prototype.$router = this.$options.router
         this.$options.router.init(_Vue)
       }
     },
   })
 }

 constructor (options) {
   this.options = options
   this.mode = options.mode
   // 判断mode的值是否正确
   if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
   this.routeMap = {}
   // 根据mode的值设置对应的current参数
   this.data = this.setRouterData(this.mode)
 }

 setRouterData (mode) {
   const { pathname, hash } = window.location
   return _Vue.observable({
     current: mode === 'history' ? pathname : hash.substr(1)
   })
 }

 init (Vue) {
   this.initRouteMap()
   this.initComponents()
 }

 initRouteMap () {
   // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
   this.options.routes.forEach(route => {
     this.routeMap[route.path] = route.component
   })
 }

 initComponents (Vue) {
   const self = this
   const { mode } = self

   Vue.component('router-link', {
     props: {
       to: String,
     },
     // 此处用template时,如果Vue时运行时版将会报错,因为运行时版没有编译器,无法编译模板
     // template: '<a :href="to"><slot></slot></a>'
     // 使用render函数代替template
     render (h) {
       return h('a', {
         attrs: {
           href: mode === 'history' ? this.to : `#${this.to}`,
         },
         on: {
           click: this.handleClick,
         },
       }, [this.$slots.default])
     },
     methods: {
       handleClick (e) {
         if (mode === 'history') {
           history.pushState({}, '', this.to)
           e.preventDefault()
         } else {
           window.location.hash = `#${this.to}`
         }
         this.$router.data.current = this.to
       },
     },
   })

   const self = this
   Vue.component('router-view', {
     render (h) {
       const component = self.routeMap[self.data.current]
       return h(component)
     },
   })
 }
}

解决Vue.component时template无法编译

  1. 在vue.config.js中配置runtimeCompiler: true,就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右
  2. 使用render函数代替template
render (h) {
  return h('a', {
    attrs: {
      href: mode === 'history' ? this.to : `#${this.to}`,
    },
    on: {
      click: this.handleClick,
    },
  }, [this.$slots.default])
}

initEvent实现,代码模拟完成

let _Vue = null

export default class VueRouter {
  static install (Vue) {
    // 判断当前插件是够已经被安装
    if (VueRouter.install.installed) return
    VueRouter.install.installed = true
    // 把Vue构造函数记录到全局变量
    _Vue = Vue
    // 把创建Vue实例时候传入的router对象注入到Vue实例上
    _Vue.mixin({
      beforeCreate () {
        // 判断this.$options.router,组件没有此属性
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init(_Vue)
        }
      },
    })
  }

  constructor (options) {
    this.options = options
    this.mode = options.mode
    // 判断mode的值是否正确
    if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
    this.routeMap = {}
    // 根据mode的值设置对应的current参数
    this.data = this.setRouterData(this.mode)
  }

  setRouterData (mode) {
    const { pathname, hash } = window.location
    return _Vue.observable({
      current: mode === 'history' ? pathname : hash.substr(1)
    })
  }

  init (Vue) {
    this.initRouteMap()
    this.initComponents(Vue)
    this.initEvent()
  }

  initRouteMap () {
    // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    })
  }

  initComponents (Vue) {
    const self = this
    const { mode } = self
    
    Vue.component('router-link', {
      props: {
        to: String,
      },
      // 此处用template时,如果Vue时运行时版将会报错,因为运行时版没有编译器,无法编译模板
      // template: '<a :href="to"><slot></slot></a>'
      // 使用render函数代替template
      render (h) {
        return h('a', {
          attrs: {
            href: mode === 'history' ? this.to : `#${this.to}`,
          },
          on: {
            click: this.handleClick,
          },
        }, [this.$slots.default])
      },
      methods: {
        handleClick (e) {
          if (mode === 'history') {
            history.pushState({}, '', this.to)
            e.preventDefault()
          } else {
            window.location.hash = `#${this.to}`
          }
          this.$router.data.current = this.to
        },
      },
    })

    Vue.component('router-view', {
      render (h) {
        const component = self.routeMap[self.data.current]
        return h(component)
      },
    })
  }

  initEvent () {
    const { mode } = this
    const eventName = mode === 'history' ? 'popstate' : 'hashchange'
    window.addEventListener(eventName, () => {
      const { pathname, hash } = window.location
      const currentPath = mode === 'history' ? pathname : hash.substr(1)
      this.data.current = currentPath
    })
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值