这里的 this 是一个 renderProxy 实例,他有一个属性 _self 可以拿到当前的组件实例,进而访问到 routerMap ,可以看到路由实例 history 的 current 本质上就是我们配置的路由表中的 path 。
接下来我们看一下 Router 要做哪些初始化工作。对于 hash 路由而言,url上 hash 值的改变不会引起页面刷新,但是可以触发一个 hashchange 事件。由于路由 history.current 初始为 null ,因此匹配不到任何一个路由,所以会导致页面刷新加载不出任何路由组件。基于这两点,在 init 方法中,我们需要实现对页面加载完成的监听,以及 hash 变化的监听。对于 history 路由,为了实现浏览器前进后退时准确渲染对应组件,还要监听一个 popstate 事件。代码如下:
Router.prototype.init = function () { if (this.mode === 'hash') { fixHash() window.addEventListener('hashchange', () => { this.history.current = getHash(); }) window.addEventListener('load', () => { this.history.current = getHash(); }) } if (this.mode === 'history') { removeHash(this); window.addEventListener('load', () => { this.history.current = location.pathname; }) window.addEventListener('popstate', (e) => { if (e.state) { this.history.current = e.state.path; } }) } }
当启用 hash 模式的时候,我们要检测url上是否存在 hash 值,没有的话强制赋值一个默认 path , hash 路由时会根据 hash 值作为 key 来查找路由表。 fixHash 和 getHash 实现如下:
const fixHash = () => { if (!location.hash) { location.hash = 'https://www.jb51.net/'; } } const getHash = () => { return location.hash.slice(1) || 'https://www.jb51.net/'; }
这样在刷新页面和 hash 改变的时候, current 可以得到赋值和更新,页面能根据 hash 值准确渲染路由。 history 模式也是一样的道理,只是它通过 location.pathname 作为 key 搜索路由组件,另外 history 模式需要去除url上可能存在的 hash , removeHash 实现如下:
const removeHash = (route) => { let url = location.href.split('#')[1] if (url) { route.current = url; history.replaceState({}, null, url) } }
我们可以看到当浏览器后退的时候, history 模式会触发 popstate 事件,这个时候是通过 state 状态去获取 path 的,那么 state 状态从哪里来呢,答案是从 window.history 对象的 pushState 和 replaceState 而来,这两个方法正好可以用来实现 router 的 push 方法和 replace 方法,我们看一下这里它们的实现:
Router.prototype.push = (options) => { this.history.current = options.path; if (this.mode === 'history') { history.pushState({ path: options.path }, null, options.path); } else if (this.mode === 'hash') { location.hash = options.path; } this.route.params = { ...options.params } } Router.prototype.replace = (options) => { this.history.current = options.path; if (this.mode === 'history') { history.replaceState({ path: options.path }, null, options.path); } else if (this.mode === 'hash') { location.replace(`#${options.path}`) } this.route.params = { ...options.params } }
pushState 和 replaceState 能够实现改变url的值但不引起页面刷新,从而不会导致新请求发生, pushState 会生成一条历史记录而 replaceState 不会,后者只是替换当前url。在这两个方法执行的时候将 path 存入 state ,这就使得 popstate 触发的时候可以拿到路径从而触发组件渲染了。我们在组件内按照如下方式调用,会将 params 写入 router 实例的 route 属性中,从而在跳转后的组件 B 内通过 this.$route.params 可以访问到传参。
this.$router.push({ path: '/b', params: { id: 55 } });
router-link实现
router-view 的实现很简单,前面已经说过。最后,我们来看一下 router-link 的实现,先放上代码: