模拟 vue-router hash 模式的基础功能实现

模拟 vue-router 的实现

以下内容基于 vue-router hash 模式来实现

实现 vue-router 的核心步骤

  • 构建 vue-router 类
  • 构建响应式数据属性 current,动态渲染 dom
  • 添加静态方法 install,完成 Vue 模块的注册
  • 初始化 router-link,router-view 组件,负责路由跳转和组件渲染
  • 通过 match 函数匹配路由,找到需要渲染的组件
  • 监听 hash 的变化,动态改变 current 属性

构建类

class MiniRouter {
  constructor(options) {
    // 存储参数
    this.options = options
    // 存储路由
    this.routes = options.routes

    // 初始化函数
    this.init()
  }

  init() {
    // 所有初始化操作放在这里
  }
}

构建响应式数据属性 current

为了可以在路由发生变化的时候去更新视图,我们可以实例化一个响应式对象 data,并给定 响应式属性 current 来记录当前的 hash,当 current 发生变化的时候,vue 就会动态更新视图

class MiniRouter {
  constructor(options) {
    ...
    // 创建一个响应式的属性 current,当 current 改变的时候就会重新渲染 dom
    this.data = $Vue?.observable({
      // 给定初始 hash
      current: window.location.hash.replace(/#/, '')
    })
  }
}

install方法

当使用 Vue.use(MiniRouter) 的时候,他要求传入的对象,必须存在一个 install 方法,它会自动帮我们执行这个方法,并将 Vue 构造函数传入该方法。所以我们可以在 install 方法中编写一些初始化代码。

而且因为 MiniRouter 是一个类,所以 install 方法应该是一个静态方法

let $Vue = null
let installed = false
class MiniRouter {
  // install 方法,是用于 vue 来注册该组件的必备方法
  static install(Vue) {
    // 判断当前插件是否被安装
    if (installed) {
      return
    }
    // 这里我使用局部变量来缓存模块安装判断
    installed = true
    // 局部保存 Vue 构造函数,将来会用到
    $Vue = Vue
    // console.log('route installed')
    // 当 vue 实例化之后,将 router 实例注入到 vue 实例中
    $Vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          // 将 router 实例保存到 Vue 的 prototype 上
          $Vue.prototype.$router = this.$options.router
        }
      }
    })
  }
  ...
}

初始化 router-link,router-view 组件

router-link 组件是用于改变当前url hash 值和改变属性 current 值的组件

router-view 组件则是负责在 hash 发生变化后,将匹配到的路由组件渲染到页面上

class MiniRouter {
  ...
  initComponents() {
    // 初始化一个 route-link 组件
    $Vue.component('router-link', {
      // 接收 to 参数,指定跳转连接
      props: {
        to: String
      },
      // 渲染 dom
      render(h) {
        return h('a', {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickHandler
          }
        }, [this.$slots.default])
      },
      methods: {
        // 指定组件的点击事件
        clickHandler(e) {
          // 通过 push State 改变 hash
          history.pushState({}, '', `#${this.to}`)
          // 改变 current 值,动态渲染视图
          this.$router.data.current = this.to
          // 阻止默认事件跳转
          e.preventDefault()
        }
      }
    })

    const self = this
    // 初始化一个 router-view 组件,进行组件渲染
    $Vue.component('router-view', {
      render(h) {
        // 匹配路由对象,找到需要渲染的组件
        let cm = self.match(self.data.current)?.component
        return h(cm)
      }
    })
  }
}

initComponents 方法需要在 init 中执行

match 匹配路由

match 函数可以说是这里最核心的方法,它可以根据传入的 path,找到与之匹配的路由对象和部分参数。

class MiniRouter {
  ...
  match(path) {
    // 存储匹配到的路由及部分参数
    let matchRoute = null
    // 遍历所有的路由,知道匹配到对应的路由
    this.routes.every(route => {
      // 通过 getRouteType 判断当前 route 的类型
      let routeType = this.getRouteType(route.path)
      // 找到 route 匹配的规则,用于判断当前路由和传入的 path 是否匹配
      let routeMethod = this[`match${routeType}Route`]
      // 如果规则不存在,则跳过
      if (!routeMethod) {
        return true
      }
      // 判断路由和 path 是否匹配,并返回对应的数据参数
      let matchResult = routeMethod(path, route)
      if (matchResult) {
        // 匹配成功,储存返回的参数,并跳出循环
        matchRoute = matchResult
        return false
      }
      // 进行下一次循环
      return true
    })
    // 返回匹配结果,若没有匹配成功则返回 null
    return matchRoute
  }
}

match 函数内部还有其他的方法,这里不一一阐述,有兴趣可以看源码。核心就是通过 getRouteType 找到路由的类型(静态路由,动态路由,统配符路由),然后找到对应类型下的路由匹配规则this[match${routeType}Route],进行匹配和返回。

监听 hash 改变

最后,我们需要通过监听 popState 来监听 hash 的改变,从而动态改变 视图,这一步主要是监听浏览器的前进,后退,或者用户直接改变hash的操作

class MiniRouter {
  ...
  initEvent() {
    document.addEventListener('popstate', () => {
      this.data.current = window.location.hash.replace(/#/, '')
    })
  }
}

initEvent 方法需要在 init 中执行


以上内容仅供学习参考,如需完整代码,可以私聊我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值