skip to content
天真笔录

vue-router/vuex源码实现

/ 3 min read

分析

  • vue-router是一个插件
    • 实现VueRouter类
      1. 处理路由选项
      2. 监听url变化,hashchange事件
      3. 响应这个变化
    • 实现install
      1. 挂载$router
      2. 注册router-view,router-link

创建VueRouter类

// VueRouter.js
import Link from "./RouterLink";
import View from "./RouterView";

let Vue;

class VueRouter {
	constructor(options) {
		this.$options = options;

		const initial = location.hash.slice(1) || "/";
		/*
      使current变成一个响应式对象(触发router-view更新)
      这里不能使用Vue.set(this, 'current', initial);因为Vue.set()第一个参数必须是响应式对象
    */
		Vue.util.defineReactive(this, "current", initial);
		window.addEventListener("hashchange", this.onHashChange.bind(this));
	}

	onHashChange() {
		this.current = location.hash.slice(1);
	}
}

// Vue.use时传入Vue
VueRouter.install = function (_Vue) {
	Vue = _Vue;
	Vue.mixin({
		beforeCreate() {
			// 只有根组件才有router
			if (this.$options.router) {
				Vue.prototype.$router = this.$options.router; // 全局挂在$router
			}
		},
	});

	Vue.component("router-link", Link);
	Vue.component("router-view", View);
};

export default VueRouter;
// RouterLink.js
export default {
	props: {
		to: {
			type: String,
			required: true,
		},
	},

	render(h) {
		return h(
			"a",
			{
				attrs: {
					href: "#" + this.to,
				},
			},
			[this.$slots.default],
		);
	},
};
// RouterView.js
export default {
	render(h) {
		let component = null;
		const { $options, current } = this.$router;
		const route = $options.routes.find((route) => route.path === current);

		route && (component = route.component);
		return h(component);
	},
};

使用

import Vue from "vue/dist/vue.esm";
import VueRouter from "./vue/vueRouter/VueRouter";

Vue.use(VueRouter);

const router = new VueRouter({
	routes: [
		{
			name: "hello",
			path: "/hello",
			component: () => import("./vue/vueRouter/Hello"),
		},
	],
});

new Vue({
	el: "#app",
	router,
});

创建vuex

let Vue;

class Store {
	constructor(options) {
		this._mutations = options.mutations;
		this._actions = options.actions;

		// Vue.util.defineReactive(this, 'state', options.state) // 避免state直接暴露

		this._vm = new Vue({
			data() {
				return {
					$$state: options.state,
				};
			},
		});

		this.commit = this.commit.bind(this); // 防止dispatch时this丢失
		this.dispatch = this.dispatch.bind(this); //
	}

	get state() {
		return this._vm._data.$$state;
	}

	set state(v) {
		console.error("you have to commit mutation to change state");
	}

	commit(type, payload) {
		const entry = this._mutations[type];
		entry && entry(this.state);
	}

	dispatch(type, payload) {
		const entry = this._actions[type];
		entry && entry(this);
	}
}

function install(_Vue) {
	Vue = _Vue;

	Vue.mixin({
		beforeCreate() {
			if (this.$options.store) {
				Vue.prototype.$store = this.$options.store;
			}
		},
	});
}

export default { Store, install };

使用

import Vue from "vue/dist/vue.esm";
import VueRouter from "./vue/vueRouter/VueRouter";
import Vuex from "./vue/vuex/vuex";

Vue.use(VueRouter);
Vue.use(Vuex);

const router = new VueRouter({
	routes: [
		{
			name: "hello",
			path: "/hello",
			component: () => import("./vue/vueRouter/Hello"),
		},
	],
});

const store = new Vuex.Store({
	state: {
		counter: 0,
	},
	mutations: {
		add(state, payload) {
			state.counter++;
		},
	},
	actions: {
		add({ commit }, payload) {
			setTimeout(() => {
				commit("add");
			}, 1000);
		},
	},
});

new Vue({
	el: "#app",
	router,
	store,
});