10分钟教你 手写 Vue-Router

}




### []( )constructor



再次我们不需要过多的操作,只需要声明 3 个属性



1.  this.options = options `记录构造函数中传入的选项 options`

2.  this.routeMap = {} `把 options 中传入的 routes 也就是路由规则解析出来,键值对的形式,键---路由地址,值---路由组件`

3.  this.data = \_Vue.observable({ current: ‘/’ })} `observable 创建响应式对象,对象中存储当前路由地址,默认是 /`



constructor (options) {

// 记录构造函数中传入的选项 options

this.options = options

// 把 options 中传入的 routes 也就是路由规则解析出来,键值对的形式,键---路由地址,值---路由组件

this.routeMap = {}

// data 是一个响应式对象,因为 data 中存储的是当前路由地址,路由变化时要自动加载组件

this.data = _Vue.observable({ // observable 创建响应式对象

  current: '/' // 存储当前路由地址,默认是 /

})

}




### []( )createRouteMap



`把构造函数中传过来的 选项中的 routes 也就是路由规则转换为键值对的形式转换到routeMap对象中 键---路由地址,值---路由组件`



createRouteMap () {

// 遍历所有的路由规则,以键值对的形式存储在 routeMap 对象中

this.options.routes.forEach(route => {

  this.routeMap[route.path] = route.component

})

}




### []( )initComponents



`该方法用来创建 router-link 与 router-view`



#### []( )router-link



initComponents (Vue) {

Vue.component('router-link', {

  props: {

    to: String

  },

  template: '<a :href="to"><slot></slot></a>'

})

}




##### []( )注意点



*   如果我们使用 vue\_cli 创建项目并且运行时,使用上述创建方法会出现一些报错,原因在于创建标签时使用了 template

    

*   而 vue\_cli 使用的 运行时版的 vue,不支持 template 模板,需要打包的时候提前编译

    



解决方法有两种



1.  修改 vue.config.js 配置使用完整版的 vue

    

    > 完整包含运行时和编译器,体积比运行时版大 10k 左右,程序运行的时候把模板转换成 render 函数

    

    ```

    module.exports = {

        // 渲染完整版 vue 

        runtimeCompiler: true

    }

    

    ```

    

2.  使用 render 函数

    

    > 运行时版本的 vue 不带编译器,所以不支持组件中的 template 选项,编译器的作用就是把 template 编译成 render 函数,运行时的组件可以直接写 render 函数

    > 

    > 单文件组件时一直使用 template 没写 render 是因为在打包的过程中把单文件的 template 编译成 render 函数了,这叫做预编译

    



#### []( )render



*   render 函数接受一个参数,通常叫做 h 作用是帮我们创建虚拟 DOM,h 由 vue 传递

    

*   h 函数接受 3 个参数

    

    1.  创建这个元素的选择器。

    2.  给标签设置一些属性,如果是 DOM 对象的属性,需要添加到 attrs 中。

    3.  生成的元素的子元素,所以是数组形式

    

    ```

      initComponents (Vue) {

        Vue.component('router-link', {

          props: {

            to: String

          },

    	  /*

    	    运行时版本的 vue 不带编译器,所以不支持组件中的 template 选项,编译器的作用就是把 template 编译成 render 函数,运行时的组件可以直接写 render 函数

    

    		单文件组件时一直使用 template 没写 render 是因为在打包的过程中把单文件的 template 编译成 render 函数了,这叫做预编译

    	  */

          render (h) { // 该函数接收一个参数,通常叫做 h 作用是帮我们创建虚拟 DOM,h 由 vue 传递

            return h('a', {	// h 函数接受 3 个参数,1. 创建这个元素的选择器。2. 给标签设置一些属性,如果是 DOM 对象的属性,需要添加到 attrs 中。3. 生成的元素的子元素,所以是数组形式

              attrs: {

                href: this.to

              }

            }, [this.$slots.default])

          }

          // template: '<a :href="to"><slot></slot></a>'

        })

      }

    

    ```

    



#### []( )router-view



const self = this

Vue.component('router-view', {

  // 获取到当前路由地址对应的路由组件

  render (h) {

    const component = self.routeMap[self.data.current]

    return h(component)

  }

})



### []( )集合上述方法 检查是否存在问题



*   因为我们默认只会调用 install 方法,但是我们还有一些其他方法需要调用,还需要一个 **init** 方法,在 install 方法 被调用后,调用 `createRouteMap() 与 initComponents()`



init () {

this.createRouteMap()

this.initComponents(_Vue)

}




*   将上述所有方法整合(为了便于阅读,去掉注释)

    

    ```

    // ../vuerouter/index.js

    let _Vue = null

    export default class VueRouter {

      static install (Vue) {

        if (VueRouter.install.installed && _Vue === true) return

        VueRouter.install.installed = true

    

        _Vue = Vue

    

        Vue.mixin({

          beforeCreate () {

            if (this.$options.router) {

              _Vue.prototype.$router = this.$options.router

              this.$options.router.init()

            }

          }

        })

      }

    

      constructor (options) {

        this.options = options

        this.routeMap = {}

        this.data = _Vue.observable({

          current: '/'

        })

      }

    

      init () {

        this.createRouteMap()

        this.initComponents(_Vue)

    	  this.initEvent()

      }

    

      createRouteMap () {

        this.options.routes.forEach(route => {

          this.routeMap[route.path] = route.component

        })

      }

    

      initComponents (Vue) {

        Vue.component('router-link', {

          props: {

            to: String

          },

    	  render (h) {

            return h('a', {

              attrs: {

                href: this.to

              }

            }, [this.$slots.default])

          }

        })

        const self = this

        Vue.component('router-view', {

          render (h) {

            const component = self.routeMap[self.data.current]

            return h(component)

          }

        })

      }

    }

    // 记得替换 router/index.js 中引入的 VueRouter

    import Vue from 'vue'

    // import VueRouter from 'vue-router'

    import VueRouter from '../vuerouter'

    import Home from '../views/Home.vue'

    import Home from '../views/About.vue'

    

    Vue.use(VueRouter)

    

    const routes = [

      {

        path: '/',

        name: 'Home',

        component: Home

      },

      {

        path: '/about',

        name: 'About',

        component: About

      }

    ]

    

    const router = new VueRouter({

      routes

    })

    

    export default router

    

    

    ```

    

    *   实际测试后,我们会发现,上述仍然存在一些小问题,也就是我们在点击 router-link 时,会改变路径刷新页面,但是在单页面组件中,我们是不需要刷新页面的,所以我们需要对 router-link 在做一点小调整

    

    ### []( )完善 router-link

    

    *   我们需要给 a 标签添加 1 个 方法,这个方法有三个作用

        

        1.  通过 pushState 方法 改变浏览器地址栏,但不给服务器发请求

        2.  将当前路由路径 同步给默认值

        3.  阻止标签默认事件

        

        > pushState 方法接受 3 个参数

        > 

        > 1.  data。

        > 2.  title 网页的标题。

        > 3.  url 地址。

        

    

    ```

        Vue.component('router-link', {

          props: {

            to: String

          },

          render (h) {

            return h('a', {

              attrs: {

                href: this.to

              },

    		  on: {

    			  click: this.clickHandler

    		  }

            }, [this.$slots.default])

          },

    	  methods: {

    		  clickHandler (e) {

              // 1. 通过 pushState 方法 改变浏览器地址栏,但不给服务器发请求

              history.pushState({}, '', this.to)	// 该方法接受 3 个参数,1. data。2. title 网页的标题。3. url 地址

              this.$router.data.current = this.to

              e.preventDefault()

    		  }

    	  }

        })

    

    ```

    



### []( )最终的完善



将上边的 router-link 完善后,我们的 Vue-Router 就实现了,但是还差一个小功能,也就是浏览器左上角的前进后退就失效了,所以我们需要在添加一个小方法来实现最终的完善



*   其实就是通过 监听 popstate 将当前的路由路径赋值给 current 来达到左上角的前进后退功能\*\*(记得将其添加到 init 方法中)\*\*



initEvent () {

  window.addEventListener('popstate', () => {

	  this.data.current = window.location.pathname

  })

}




*   最终完整版代码(分为两版,有注释的在最后)



let _Vue = null

export default class VueRouter {

static install (Vue) {

if (VueRouter.install.installed && _Vue === true) return

VueRouter.install.installed = true



_Vue = Vue



Vue.mixin({

  beforeCreate () {

    if (this.$options.router) {

      _Vue.prototype.$router = this.$options.router

      this.$options.router.init()

    }

  }

})

}

constructor (options) {

this.options = options

this.routeMap = {}

this.data = _Vue.observable({

  current: '/'

})

}

init () {

this.createRouteMap()

this.initComponents(_Vue)

this.initEvent()

}

createRouteMap () {

this.options.routes.forEach(route => {

  this.routeMap[route.path] = route.component

})

}

initComponents (Vue) {

Vue.component('router-link', {

  props: {

    to: String

  },

  render (h) {

    return h('a', {

      attrs: {

        href: this.to

      },

	  on: {

		  click: this.clickHandler

	  }

    }, [this.$slots.default])

  },

  methods: {

	  clickHandler (e) {

      history.pushState({}, '', this.to)

      this.$router.data.current = this.to

      e.preventDefault()

	  }

  }

})

const self = this

Vue.component('router-view', {

  render (h) {

    const component = self.routeMap[self.data.current]

    return h(component)

  }

})

}

initEvent () {

  window.addEventListener('popstate', () => {

	  this.data.current = window.location.pathname

  })

}

}


let _Vue = null

export default class VueRouter {

// 传入两个参数,一个是 vue 的构造函数,第二个是 可选的 选项对象

static install (Vue) {

// 1. 如果插件已经安装直接返回

if (VueRouter.install.installed && _Vue === true) return

VueRouter.install.installed = true // 表示插件已安装



// 2. 把 vue 的构造函数记录到全局变量中,因为将来我们要在 VueRouter 的实例方法中使用 vue 的构造函数

_Vue = Vue



// 3. 把创建 Vue 实例时候传入的 router 对象注入到 Vue 实例上

Vue.mixin({

  beforeCreate () {

    if (this.$options.router) {

      _Vue.prototype.$router = this.$options.router

      this.$options.router.init()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值