下面是一段简单的代码,通过这段代码分析一下如何去实现一个自己的 vue-router,并且思考下面两个问题:
思考1:为什么要在创建 vue 实例时指定 router? 思考2:如何实现 router-view 和 router-link 这两个组件以及这两个组件的作用是什么?
import Vue from 'vue'import Router from 'vue-router'const layout = () => import('@/pages/layout')const home = () => import('@/pages/home')Vue.use(Router)const redirect = {path: '*',redirect: '/'}let router = new Router({mode: 'history',routes: [{path: '/',name: 'layout',component: layout,redirect: '/home',children: [{path: 'home',name: 'home',component: home,meta: {title: '首页',noLogin: true}}]},redirect]})export default router// login.vue<router-link :to="{name: 'home'}"></router-link>// app.vue<router-view/>// main.jsimport router from './router'new Vue({el: '#app',router,store,components: { App },template: '<App/>'})
经过分析我们得出 Vue Router 需要满足下面四个基本功能:
- 作为 Vue 插件 — 实现 install 方法
- 监听url变化,渲染对应的组件
- 路由配置解析,
{ path: '/', component: 'home'} => '/' —> home - 实现两个全局组件:router-link、route-view
实现插件功能
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
// 实现 install 方法VueRouter.install = function(Vue) {// 全局混入Vue.mixin({beforeCreate() {// this 是 Vue 实例if (this.$options.router) {// 仅在根组件执行一次Vue.prototype.$router = this.$options.router;this.$options.router.init();}}});};
创建 Router 类
import Vue from "vue";class VueRouter {constructor(options) {this.$options = options;this.routeMap = {};// 通过 Vue 实现路由响应式,所以 Vue Router 只能用于 Vue 中,它们之间是强绑定的this.app = new Vue({data: {current: "/"}});}init() {this.bindEvents();this.createRouteMap(this.$options);this.initComponent();}// 监听url变化,渲染对应的组件bindEvents() {window.addEventListener("load", this.onHashChange.bind(this));window.addEventListener("hashchange", this.onHashChange.bind(this));}// hash 值改变时,current 值也跟着改变onHashChange() {this.app.current = window.location.hash.slice(1) || "/";}// 解析路由配置createRouteMap(options) {options.routes.forEach(item => {this.routeMap[item.path] = item.component;});}// 实现两个全局组件 router-link、router-view,当组件实例中的 current 变化时,这两个全局组件都会重新渲染initComponent() {Vue.component("router-link", {props: { to: String },render(h) {// h(tag, data, children)return h("a", { attrs: { href: "#" + this.to } }, [this.$slots.default]);}});Vue.component("router-view", {// 使用箭头函数, 通过 this 获取 routeMap 中对应 current 的组件render: h => {const comp = this.routeMap[this.app.current];return h(comp);}});}}
思考解答
// 思考1保证通过router对象仅在根组件的时候挂载和执行init方法// 思考2使用 Vue.component 注册为全局组件router-link:改变 hash 值,触发浏览器的历史状态管理route-view:根据 hash 值,渲染对应的组件
完整代码
import Home from "./views/Home";import About from "./views/About";import Vue from "vue";class VueRouter {constructor(options) {this.$options = options;this.routeMap = {};// 通过 Vue 实现路由响应式,所以 Vue Router 只能用于 Vue 中this.app = new Vue({data: {current: "/"}});}init() {this.bindEvents();this.createRouteMap(this.$options);this.initComponent();}// 监听url变化,渲染对应的组件bindEvents() {window.addEventListener("load", this.onHashChange.bind(this));window.addEventListener("hashchange", this.onHashChange.bind(this));}onHashChange() {this.app.current = window.location.hash.slice(1) || "/";}// 解析路由配置createRouteMap(options) {options.routes.forEach(item => {this.routeMap[item.path] = item.component;});}// 实现两个全局组件 router-link、router-view,当组件实例中的 current 变化时,这两个全局组件都会重新渲染initComponent() {Vue.component("router-link", {props: { to: String },render(h) {// h(tag, data, children)return h("a", { attrs: { href: "#" + this.to } }, [this.$slots.default]);}});Vue.component("router-view", {// 使用箭头函数, 通过 this 获取 routeMap 中对应 current 的组件render: h => {const comp = this.routeMap[this.app.current];return h(comp);}});}}// 实现 install 方法VueRouter.install = function(Vue) {// 全局混入Vue.mixin({beforeCreate() {// this 是 Vue 实例if (this.$options.router) {// 仅在根实例执行一次 (main.js中 new Vue 时)Vue.prototype.$router = this.$options.router;this.$options.router.init();}}});};Vue.use(VueRouter);export default new VueRouter({routes: [{ path: "/", component: Home },{ path: "/about", component: About }]});
