调用 init 时机
- 全局混入会影响后续创建的所有 Vue 实例,所以 beforeCreate 首次触发是在 Vue 根实例实例化的时候即 new Vue({router})时,触发后调用 router 实例的 init 方法并传入 Vue 根实例,完成初始化流程;
- 由于 router 仅存在于 Vue 根实例的 $options 上,所以整个初始化只会被调用一次;
// 1.用来避免将 Vue 做为依赖打包进来
export let Vue;
import RouterLink from './components/router-link';
import RouterView from './components/router-view';
const install = function (_Vue) {
if (install.installed && _Vue === Vue) return // 避免重复安装
install.installed = true
Vue = _Vue; // 将 Vue 构造函数存储起来
// 2.注册 router-view、router-link 全局组件
Vue.component('router-link', RouterLink);
Vue.component('router-view', RouterView);
// 3.全局混入将 router实例注册到了 new Vue,这样每个子组件都可以获取到 router 属性
Vue.mixin({
beforeCreate() { // mixin(当前options) 会调用 mergeOptions(全局Options,当前options) 进行合并
// 如果有 router 说明在根实例上增加了router实例,当前这个this实例是根实例
// 渲染流程先父后子,渲染完毕先子后父
if (this.$options.router) {
this._router = this.$options.router; // 将 router实例 挂载到根实例的 _router 上
this._routerRoot = this; // 将vm根实例放到了vm根实例的 _routerRoot 上
this._router.init(this); // 调用当前 router 实例中的 init 方法
// 若用户更改了 current 是没效果的,需要把 _route 也进行更新(history里面进行了更新)
// 将 _route 转成响应式的,_route 变化去更新视图
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else {
// 孩子组件获取 _routerRoot,由于渲染流程先父后子,父亲一定先有 _routerRoot 属性
this._routerRoot = this.$parent && this.$parent._routerRoot;
}
// 这样所有的组件都拥有了 this._routerRoot、this._routerRoot._router 属性
}
});
// 4.添加实例属性:将 $route、$router 代理到 vm 实例上
Object.defineProperty(Vue.prototype, '$route', { // 存放的都是属性 path,matched
get() {
return this._routerRoot && this._routerRoot._route; // 取 current
}
})
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot && this._routerRoot._router;
}
})
}
export default install;
init 方法
import install from './install';
import createMatcher from './create-matcher';
import HashHistory from './history/hashHistory';
import BrowserHistory from './history/browserHistory';
class VueRouter {
constructor(options) {
// 1.接收 RouterOptions
// 2.创建匹配器的过程,并返回 match, addRoutes 方法
// match函数:匹配功能
// addRoutes函数:可以添加匹配,动态路由添加
this.matcher = createMatcher(options.routes || []);
// 3.创建路由模式,默认值是 hash 模式
this.mode = options.mode || 'hash';
switch (this.mode) {
case 'hash':
this.history = new HashHistory(this)
break;
case 'history':
this.history = new BrowserHistory(this);
break;
}
// 4.初始化钩子队列(这里只初始化 beforeHooks)
this.beforeHooks = [];
}
match(location) {
return this.matcher.match(location);
}
init(app) { // 目前这个 app 指代的就是最外层 new Vue 根实例
// 需要根据用户配置,做出一个映射表
// 需要根据当前路径,实现页面跳转的逻辑
const history = this.history;
// 进行匹配操作,根据路径获取对应的记录
let setupHashListener = () => {
history.setupListener(); // 设置 hashchange 监听事件
}
// 根据 history(hash、browser)类型,调用 transitionTo 跳转到不同的初始页面
history.transitionTo(history.getCurrentLocation(), setupHashListener)
// 注册 updateRoute 回调,在 router 更新时,更新 app._route 完成页面重新渲染
history.listen((route) => { app._route = route })
}
push(location) {
const history = this.history;
window.location.hash = location;
}
beforeEach(fn) {
this.beforeHooks.push(fn);
}
}
VueRouter.install = install;
export default VueRouter;
transitionTo
// 根据匹配到的记录来计算匹配到的所有的记录
export const createRoute = (record, location) => {
let matched = []
if (record) {
while (record) {
matched.unshift(record);
record = record.parent; // 通过当前记录找到所有的父亲
}
}
// {path:'/',matched:[{}]}
return { ...location, matched };
}
const runQueue = (queue, iterator, complete) => {
function next(index) {
if (index >= queue.length) {
return complete();
}
let hook = queue[index]; // 取出hook钩子函数
iterator(hook, () => next(index + 1)); // 就是遍历的过程
}
next(0);
}
// 这个 current 就是一个普通的变量,希望 current 变化了可以更新视图
export default class History {
constructor(router) {
this.router = router;
// 这个代表的是,当前路径匹配出来的记录
// /about/a 匹配=> {path:'/about',component:about}、{path:'/about/A',component:A}
// this.current = {path:'/',matched:[]}
this.current = createRoute(null, { path: '/' }); // 默认值
}
transitionTo(location, complete) {
// 获取当前路径匹配出对应的记录,当路径变化时获取对应的记录 => 渲染页面 (router-view实现的)
// 通过路径拿到对应的记录,有了记录之后,就可以找到对象的匹配
// 当路径变化后 current 属性会进行更新操作
let current = this.router.match(location);
// 防止重复点击 不需要再次渲染
// 匹配到的个数和路径都是相同的,就不需要再次跳转了
if (this.current.path == location && this.current.matched.length === current.matched.length) {
return;
}
let queue = this.router.beforeHooks; // 钩子函数队列
const iterator = (hook, next) => { hook(current, this.current, next) };
runQueue(queue, iterator, () => {
this.current = current; // 这个 current 只是响应式的,他的变化不会更新 _route
this.cb && this.cb(current); // 手动更新 _route 的回调函数
complete && complete(); // 监听浏览器 url 变化的事件的初始化函数
});
}
listen(cb) { // 保存回调函数
this.cb = cb;
}
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
实例化流程
上一篇
评论