0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

手写vue-router

修罗 2020-10-26 1485 0条评论 vue

首页 / 正文

手写vue-router

1、编写路由类 my-router.js

let Vue;
class MyRouter{
    
  constructor($options){
    // $options为 { routes = [ { path: '/', '路由': Index, name: '首页' }] }
    this.$options = $options

    // 定义响应式的属性current
    const initial = window.location.hash.slice(1) || '/'
    Vue.util.defineReactive(this, 'current', initial)

    // 监听hashchange事件
    window.addEventListener('hashchange', this.onHashChange.bind(this))

    // 路由表:缓存path和route映射关系
    this.routeMap = {}
    // 每个路由地址都对应自己的route
    this.$options.routes.forEach(route => {
      // '/': { path: '/', '路由': Index, name: '首页' }
      this.routeMap[route.path] = route
    });
  }
  onHashChange() {
    // 当前hash去掉#
    this.current = window.location.hash.slice(1)
  }
    
}

// 定义插件
MyRouter.install = function(vue){
  // 保存vue供上面响应式设置
  Vue = vue
  vue.mixin({
    // 每个实例都会调用beforeCreate
    beforeCreate(){
      // 判断,拿到main.js中new vue()传递的router对象
      if(this.$options.router){
        vue.prototype.$router = this.$options.router
      }
    }
  })

  // 全局组件注册
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        required: true
      }
    },
    // runtime-only不带编译器,预编译,打包的时候编译。这里不能用template
    // render函数只要用到响应式数据就会被依赖收集起来,等待重新调用
    render(h){
      return h(
        'a', 
        { attrs: { href: `#${this.to}` }, style: { display:'inline-block',padding:'0 10px' } }, 
        [this.$slots.default]
      )
    }
  })

  // current发生改变render函数重新渲染,实现了组件切换
  Vue.component('router-view', {
    render(h) {
      // 从router对象中拿到当前路由和路由表
      // 如果当前路由有children,即路由有router-view,
      // router-view会从current匹配当前路由,当前路由又是原来那个路由,无限循环
      // 比如:/about有router-view,about要渲染router-view,后面router-view又要渲染about
      const { routeMap, current } = this.$router
      // 拿到当前路由对应的组件
      const component = routeMap[current].component;
      return h(component);
    }      
  })
}

export default MyRouter

2、router/index.js引入路由,传递路由数组

import MyRouter from './my-router'
import vue from 'vue'
import About from '@/components/about'
import Index from '@/components/index'

vue.use(MyRouter)

const routes = [
  {
    path: '/',
    component : Index,
    name: '首页'
  },
  {
    path: '/about',
    component: About,
    name: '关于'
  }
]

const router = new MyRouter({ routes })

export default router

main.js挂载路由实例

1603710385574.png

使用自定义路由

1、编写组件app.vue

1603709933784.png

2、index.vue

<template>
  <div>index</div>
</template>

<script>
</script>
<style>
</style>

3、about.vue

<template>
  <div>
    <div>about</div> 
  </div>
</template>

<script>
</script>
<style>
</style>

效果

1603710498231.png

about路由加入children

  • 正如上面所提到的如果当前路由有children,即路由有router-view,router-view会从current匹配当前路由,当前路由又是原来那个路由,无限循环
  • 比如:/about有router-view,about要渲染router-view,后面router-view

info组件和上面about组件一样简单用div显示:info

1603710638798.png

更新my-router.js

let Vue;
class MyRouter{

  constructor($options){
    // $options为 { routes = [ { path: '/', '路由': Index, name: '首页' }] }
    this.$options = $options
    // 初始化当前页面hash
    this.current = window.location.hash.slice(1) || '/'
    // 定义响应式的属性matched,所有使用到matched的页面都会收集依赖,matched更新,页面更新
    Vue.util.defineReactive(this, 'matched', [])
    this.match()
    // 监听hashchange事件
    window.addEventListener('hashchange', this.onHashChange.bind(this))
  }

  onHashChange() {
    // 当前hash去掉#
    this.current = window.location.hash.slice(1)
    // 页面跳转重新获取匹配路由数组
    this.matched = []
    this.match()
  }

  match(routes){
    routes = routes || this.$options.routes
    // 递归遍历
    for(const route of routes){
      if(route.path === '/' && this.current === '/'){
        this.matched.push(route)
        return
      }
      // /about/info
      if(route.path !== '/' && this.current.indexOf(route.path) !== -1){
        this.matched.push(route)
        if(route.children){
          this.match(route.children)
        }
        return
      }
    }
  }
    
}

// 定义插件
MyRouter.install = function(vue){
  // 保存vue供上面响应式设置
  Vue = vue
  vue.mixin({
    // 每个实例都会调用beforeCreate
    beforeCreate(){
      // 判断,拿到main.js中new vue()传递的router对象
      if(this.$options.router){
        vue.prototype.$router = this.$options.router
      }
    }
  })

  // 全局组件注册
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        required: true
      }
    },
    // runtime-only不带编译器,预编译,打包的时候编译。这里不能用template
    // render函数只要用到响应式数据就会被依赖收集起来,等待重新调用
    render(h){
      return h(
        'a', 
        { attrs: { href: `#${this.to}` }, style: { display:'inline-block',padding:'0 10px' } }, 
        [this.$slots.default]
      )
    }
  })

  // current发生改变render函数重新渲染,实现了组件切换
  Vue.component('router-view', {
    render(h) {
      // 当前是个router-view
      this.$vnode.data.routerView = true
      // 标记当前router-view深度
      let depth = 0;
      let parent = this.$parent
      while(parent){
        const vnodeData = parent.$vnode && parent.$vnode.data
        if(vnodeData){
          if(vnodeData.routerView){
            // 说明当前parent是一个router-view
            // 有多少父router-view,自己深度+多少
            depth++
          }
        }
        parent = parent.$parent
      }

      // 获取path对应的component
      let component = null;
      const route = this.$router.matched[depth]
      if(route){
        component = route.component
      }
      return h(component)
    }      
  })
}

export default MyRouter

测试没毛病了

1603710865096.png

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis