大前端 -- 03-01-2 手写 Vue Router

手写 Vue Router

一、基础回顾?

Vue Router 基础回顾

二、使用步骤

1.创建 router 对象,router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
// 路由组件
import index from '@/views/index'
// 组成插件
Vue.use(VueRouter)
// 路由规则
const routes = [{
    name: 'index',
    path: '/',
    component: index
}]
// 路由对象
const router = new VueRouter({
    routes
})
export default router

2.注册 router 对象,main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

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

3.创建路由组建的占位,创建链接 App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/ablout">About</router-link>
    </div>
    <router-view/>
  </div>
</template>

三 动态路由

router/index.js

const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    // 开启props,会把URL中的参数传递给组件
    props: true,
    component: () => import(/* webpackChunkName: "about" */ '../views/Detail.vue')
  }
]

view/Detail.vue

<template>
  <div>
    这是Detail页面
    <!-- 方式一:通过当前路由规则,获取数据 -->
    通过当前路由规则获取:{{ $route.params.id }}

    <br>

    <!-- 方式二:路由规则中开启props传参  (推荐)-->
    通过开启props获取: {{ id }}
  </div>
</template>

<script>
export default {
  name: 'Detail',
  // 将路由参数配置到props中
  props: ['id']
}
</script>

四 嵌套路由

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '../components/Layout.vue'
import Login from '../views/Login.vue'
import Index from '../views/Index.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'login',
    component: Login
  },
  // 嵌套路由
  {
    path: '/',
    component: Layout,
    children: [
      {
        path: '',
        name: 'index',
        component: Index
      },
      {
        path: 'detail/:id',
        name: 'detail',
        props: true,
        component: () => import('@/views/Detail.vue')
      }
    ]
  }
]

const router = new VueRouter({
  routes
})

export default router

components/Layout.vue

<template>
  <div>
    <div>
      <img width='80px' src='@/assets/logo.png'>
    </div>
    <div>
      <router-view></router-view>
    </div>
    <div>
      Footer
    </div>
  </div>
</template>

五 编程式导航

View/Index.vue

<template>
  <div>
    <router-link to="/">首页</router-link>
    <button @click="replace"> replace </button>
    <button @click="goDetail"> Detail </button>
  </div>
</template>

<script>
export default {
  name: 'Index',
  methods: {
    replace () {
      this.$router.replace('/login')
    },
    goDetail () {
      this.$router.push({ name: 'Detail', params: { id: 1 } })
    }
  }
}
</script>

View/Detail.vue

<template>
  <div>
    这是Detail页面

    路由参数: {{ id }}

    <button @click="go"> go(-2) </button>
  </div>
</template>

<script>
export default {
  name: 'Detail',
  // 将路由参数配置到props中
  props: ['id'],
  methods: {
    go () {
      this.$router.go(-2)
    }
  }
}
</script>

六 Hash 模式 和 History 模式的区别

1. 表现形式的区别

  • hash模式 https://music.163.com/#/playlist?id=3102961863
  • history模式 https://music.163.com/playlist/3102961863

2. 原理的区别

  • hash 模式
    Vue Router 默认使用的是 hash 模式,是基于锚点,以及onHashChange事件
  • History模式是基于HTML5中的History API
    • History.pushState() IE10以后才支持
    • History.replaceState()
    • 开启 History 模式
const router = new VueRouter({
// mode: 'hash',
mode: 'history',
routes
})

3. History模式的使用

  • History 需要服务器的支持
  • 单页应用中,服务端不存在 http://www.testurl.com/login 这样的地址会返回找不到该页面
  • 在服务端应该除了静态资源外都返回单页应用的 index.html
  • Node.js服务器配置
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')))

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

  • Nginx服务器配置

    • 从官网下载 nginx 的压缩包
    • 把压缩包解压到 c 盘根目录,c:\nginx-1.18.0 文件夹
    • 修改 conf\nginx.conf 文件

nginx.conf

http: {
	server: {
		location / {
			root html;
			index index.html index.htm;
			# 尝试查找,找不到就回到首页
			try_files $uri $uri/ /index.html;
		}
	}
}

  • 打开命令行,切换到目录 c:\nginx-1.18.0
  • nginx 启动、重启和停止
# 启动
start nginx
# 重启
nginx -s reload
# 停止
nginx -s stop

4. Hash模式

  • URL中#后面的内容作为路径地址
  • 监听hashchange事件
  • 根据当前路由地址找到对应组件重新渲染

5. History模式

  • 通过History.pushState()方法改变地址栏(不会向服务器发送请求,但会将这次URL记录到历史中)
  • 监听popstate事件
  • 根据当前路由地址找到对应组件重新渲染

七 实现自己的vue-router

前置的知识:插件、slot 插槽、混入、render 函数、运行时和完整版的 Vue

  • 回顾 Vue Router 的核心代码
// 注册插件
// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
    routes: [{
        name: 'home',
        path: '/',
        component: homeComponent
    }]
})
// 创建 Vue 实例,注册 router 对象
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')
  • 实现思路
    • 创建 VueRouter 插件,静态方法 install
      • 判断插件是否已经被加载
      • 当 Vue 加载的时候把传入的 router 对象挂载到 Vue 实例上(注意:只执行一次)
    • 创建 VueRouter 类
      • 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路径)
      • initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
      • 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
      • 创建 router-link 和 router-view 组件
      • 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view

注意点:

  • vue-cli 创建的项目默认使用的是运行时版本的 Vue.js
  • 如果想切换成带编译器版本的 Vue.js,需要修改 vue-cli 配
    • 项目根目录创建 vue.config.js 文件,添加 runtimeCompiler
module.exports = {
  // 完成版本的Vue(带编译器版)
  runtimeCompiler: true
}

八 完整版代码

let _Vue = null
export default class VueRouter {
  static install (Vue) {
    // 1 判断当前组件是否安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true

    // 2 把Vue构造函数记录到全局变量
    _Vue = Vue

    // 3 把创建Vue实例收传入的router对象注入到vue实例上
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _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.initComponent(_Vue)
    this.initEvent()
  }

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

  initComponent (Vue) {
    // 创建router-link组件
    Vue.component('router-link', {
      props: {
        to: String
      },
      // render函數
      render (h) {
        // h函數三個參數,第一个位选择器,2:标签的属性 3标签内的子元素
        return h('a', {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickHandle
          }
        }, [this.$slots.default])
      },
      methods: {
        clickHandle (e) {
          history.pushState({}, '', this.to)
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
      // template: "<a :href='to'>[<slot></slot></a>"
    })
    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
    })
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值