Vue Router
写之前先分析 Vue Router 究竟实现了哪些核心功能?
- 构建单页面应用
- 根据url的变化能够展示不同内容
router-view组件以及router-link组件- 响应url的变化渲染路由表中配置的内容
分析之后,我们将简单的实现一下它的核心功能,主要要做两件事情:构造一个 VueRouter 类,它负责处理路由选项,监控url变化并响应变化;将类封装成一个Vue插件,也就是实现它的 install 方法,在这里我们要实现路由注册,并注册router-view 组件和 router-link 组件。
实现VueRouter类
1. 初始化VueRouter类
将路由配置保存到 VueRouter 实例中,监听 hashchange 事件,利用Vue提供的工具函数,将路由地址变为响应式变量(只要变量变化,引用了该变量的组件都会重新渲染)。
let _Vue
class VueRouter {
// 传入routes路由表
constructor(options) {
this.$options = options
// 提供路由表映射
this.routerMap = new Map()
options.routes.forEach(route => {
this.routerMap.set(route.path, route)
})
// 利用Vue提供的工具函数将current变为响应式变量
_Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/')
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1) || '/'
}
}
// 插件安装
VueRouter.install = (Vue) => {
// 引用 Vue 实例
_Vue = Vue
}
2. 插件安装
在插件安装过程中,需要将 VueRouter 实例挂载到 Vue 实例中,然而使用过程中我们会发现 Vue.use(VueRouter) 出现在 new VueRouter(routes) 之前。也就是说,安装时还没有 VueRouter 实例。所以这里需要延迟挂载:
// 插件安装
VueRouter.install = (Vue) => {
// 1.引用 Vue 实例
_Vue = Vue
// 2.在beforeCreate中挂载VueRouter实例
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
}
接着注册两个全局组件:
// 插件安装
VueRouter.install = (Vue) => {
...
// 3. 注册全局组件
Vue.component('router-link', {
props: {
to: {
type: String,
default: '/'
},
},
render(h) {
return h('a', {
attrs: {
href: `#${this.to}`
}
},this.$slots.default)
}
})
Vue.component('router-view', {
render(h) {
// 找到匹配当前路由的组件
const {routerMap, current} = this.$router
const route = routerMap.get(current)
let component = route ? route.component : null
return h(component)
}
})
}
至此,我们实现一个超简单的路由插件。
Vuex
Vuex是一个集中式的状态管理插件,具体机制在此就不赘述。同样先分析我们需要实现的需求:
- 实现一个插件:声明 Store 类,挂载$store
- Store的具体实现:
- 创建响应式的state,保存mutations、actions和getters
- 实现commit根据⽤户传⼊type执⾏对应mutation
- 实现dispatch根据⽤户传⼊type执⾏对应action,同时传递上下⽂
- 实现getters,按照getters定义对state做派⽣
初始化
let _Vue
class Store {
constructor(options = {}) {
this._mutations = options.mutations
this._actions = options.actions
this._wrappedGetters = options.getters
}
}
function install(Vue) {
_Vue = Vue
// 和VueRouter类似,使用混入绑定$store
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
export default {
Store,
install
}
提供响应式的state
class Store {
constructor(options = {}) {
...
// 创建响应式的数据
this._vm = new _Vue({
data() {
return {
// $$ 不会被代理
$$state: options.state
}
}
})
}
get state() {
return this._vm._data.$$state
}
set state(v) {
console.error('please use replaceState api to reset state')
}
}
实现getters
class Store {
constructor(options = {}) {
...
const computed = {}
this.getters = {}
const store = this
Object.keys(this._wrappedGetters).forEach(key => {
const fn = store._wrappedGetters[key]
computed[key] = function() {
return fn(store.state)
}
// 定义只读的自定义属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
set: () => {
console.error('can not set value for getters')
}
})
})
// 创建响应式的数据
this._vm = new _Vue({
data() {
return {
// $$ 不会被代理
$$state: options.state
}
},
computed
})
}
...
}
实现mutation
class Store {
...
// 修改state
commit(type, payload) {
// 获取type对应的mutations
const entry = this._mutations[type]
if (!entry) {
console.error('unknown mutation')
return
}
entry(this.state, payload)
}
}
实现action
class Store {
...
dispatch(type, payload) {
// 获取type对应的actions
const action = this._actions[type]
if (!action) {
console.error('unknown action')
return null
}
return action(this, payload)
}
}