vue-router源码实现
vue-router
安装:vue add router 核心步骤:
- 步骤一: 使用vue-router插件, router.js
import Router form 'vue-router'
Vue.use(Router)
- 步骤二: 创建Router实例 并导出, router.js
import Router from 'vue-router
export default new Router({....})
- 步骤三: 在根组件上添加该实例
import router from './router'
new Vue({
router
}).$mount("#app")
- 步骤四: 添加路由视图, App.vue
<router-view></router-view> - 添加导航
<router-link to="/">Home</router-link>
this.$router.push('/')
源码实现
需求分析
- spa 页面不能刷新
- hash #/about
- history api /about
- 根据url显示对应的内容
- router-view
- 数据响应式:current变量持有url地址, 一旦变化, 动态重新执行render
任务
- 实现一个插件, kvue-router.js
- 实现VueRouter类
- 处理路由选项
- 监控url变化, hashchange
- 响应这个变化
- 实现install方法
- $router 注册
- 两个全局组件
- 实现VueRouter类
//实现一个插件
//返回一个函数
//或者返回一个对象, 有一个install放法
let _Vue
class VueRouter {
// 选项: routers - 路由表
constructor(options) {
this.$options = options
//定义一个响应式的current 属性
//利用Vue提供的defineReactive做出响应化
//const initial = window.locath.hash.slice(1) || '/'
//_Vue.util.defineReactive(this, 'current', initial)
this.current = window.location.hash.slice(1)
_Vue.util.defineReactive(this, 'matched', [])
this.match()
//this.routeMap = {}
//options.routes.forEach(
//route => {
//this.routeMap[route.path] == route
//}
//)
// 监控url变化
window.addEventListner('hashchange', this.onHashChange.bind(this))
window.addEventListner('load', this.onHashChange.bind(this))
}
onHashChange(){
this.current = window.location.hash.slice(1)
this.matched = []
this.match()
}
match(routes) {
routes = routes || this.$options.routes
//递归遍历
//Object.keys(routes).forEach(route => {
//})this.
for(const route of routes) {
if(route.path === '/' && this.current === '/') {
this.mathed.push(route)
return
}
if(route.path !== '/' && cthis.current.indexof(route.path) !== -1) {
this.mathed.push(route)
if(route.children) {
this.match(route.children)
}
return
}
}
}
}
VueRouter.install = function(Vue) {
//引用Vue构造函数,在上面VueRouter中使用
_Vue = Vue
// 1.挂载$router
Vue.mixin({
beforeCreate(){
if(this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 2.定义两个全局组件router-link, router-view
Vue.component('router-link', {
props: {
to: {
type: String,
require: true
}
},
render(h) {
//<a href=“#/abaout”>xxxxx</a>
return h('a', {
attrs: {
href: '#' + this.to
}
}, this.$slots.default)
}
})
Vue.component('router-view', {
// 1.标记router-view 深度
// 2.路由匹配
render(h) {
//标记当前router-view深度
this.$vnode.data.routerView = true
//深度
let deep = 0
let parent = this.$parent
while(parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if(vnodeData) {
if(vnodeData.routerView) {
//当前paret是一个router-view
deep++
}
}
parent = this.$parent
}
//const {routeMap, current} = this.$router
//const component = routeMap[current] ? routeMap[current].component : null
//获取path对应的component
let comonent = null
const route = this.$router.matched[depth]
return h(component)
}
})
}
export default VueRouter
Vuex
- state
- mutations -- $commit
- getters
- actions --$dispach
要解决问题
任务
- 实现插件
- 实现Store类
- 维持一个响应式状态
- 实现 commit()
- 实现 dispatch()
- getters
- 挂载$store实例
- 实现Store类
//1. 实现一个插件
let _Vue
class Store {
constructor(options) {
this._mutations = options.mutations
this._actions = options.actions
this._wrapedGetters = options.getters
//定义computed选项
const computed = {}
this.getters = {}
const store = this
Object.keys(this._wrapedGetters).forEach(key => {
//获取用户定义的getter
const fn = store._wrappedGetters[key]
//转换为computed可以使用的无参数形式
computed[key] = function() {
return fn(store.state)
}
//为getters定义只读属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})
//创建响应式的state
this._vm = new _Vue({
data() {
return {
//不希望被代理, 就加上$
$$state: options.state
}
},
computed
})
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
//getters
this.getters = {}
for(let prop in options.getters) {
Object.defineProperty(this.getters, prop, {
get() {
return options.getters.prop(this.state)
}
set(v) {
console.error('please use replaceState to reset state')
}
})
}
}
get state() {
return this._vm._data.$$state
}
//只读
set state(v) {
console.error('please use replaceState to reset state')
}
//修改state
// this.$store.commit('add', 1)
commite(type, payload) {
//获取type对应的mutation
const entry = this._mutations[type]
if(!entry) {
console.error('unknow mutation')
return
}
//传入state作为参数
entry(this.state, playoad)
}
dispatch(type, payload){
//获取type对应的action
const entry = this._actions[type]
const entry = this._mutations[type]
if(!entry) {
console.error('unknow action')
return
}
//传入state作为参数
return entry(this, playoad)
}
}
function install(Vue) {
_Vue = Vue
//混入
Vue.mixin({ //解决install插件注册时实例还不存在的问题
beforeCreate(){
if(this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
//导出的对象是Vuex
export default {Store, install}
补充组件
//传入的是组件配置对象
const Ctor = Vue.extend(Component);
// propsData选项传递属性 -- VueComponent实例
const ctor= new Ctor({propsData: props})
//vm Vue实例, 根实例, $root
// App 根组件、 实例化
//vm.children[0] VueComponent
comp.$mount();
document.body.appendChild(comp.$el)
comp.remove = () => {
//移除dom
document.body.removeChild(cokmp.$el)
//销毁组件
comp.$destroy()
}
- 使用插件进一步封装便于使用, create.js
import Notice from '@/components/Notice.vue'
export default {
install(Vue) {
Vue.prototype.$notice = function(options) {
const comp = create(Notice, options)
comp.show()
return comp
}
}
}
递归组件
组件内部调用自身, 一般生成树形结构