1、kvue-router.js
import Link from './krouter-link'
import View from './krouter-view'
//1.创建一个krouter对象,只需要把krouter挂载到Vue.prototype.$router = router上 这样在所有的组件中都可以使用$router了
//krouter是一个对象,只需要实现一个{install()} 方法就可以了
let Vue;
class kVueRouter {
constructor(options) { //options接收用户传进来的配置及属性
this.$options = options
//创建响应式的current
// Vue.util.defineReactive(this, 'current', '/') //看vue文档
this.current = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'matched', [])
//match方法可以递归遍历路由表,获得匹配关系数据matched
this.match()
/*
//这里注释掉是不希望有重复的代码
window.addEventListener('hashchange', () => {
console.log('window.location.hash:===', window.location.hash);
this.current = window.location.hash.slice(1)
})
window.addEventListener('load', () => {
console.log('window.location.hash:===', window.location.hash);
this.current = window.location.hash.slice(1)
}) */
//这里使用bind(this)的原因是因为是window调用的,用bind(this)就是重新指向当前类KVueRouter
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
//创建一个path和component之间的路由映射表
/* this.routeMap = {}
console.log('options.routes:===', options.routes)
options.routes.forEach(route => {
this.routeMap[route.path] = route //这里还得再看看看why????? 2020.01.11
}) */
}
onHashChange() {
console.log('window.location.hash:===', window.location.hash);
this.current = window.location.hash.slice(1)
//当路由变化的时候,把matched数组清空,重新匹配
this.matched = []
this.match()
}
match(routes) {
routes = routes || this.$options.routes
//递归遍历
for (const route of routes) {
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
//this.current是/about/info时的判断
if (route.path !== '/' && this.current.indexOf(route.path) != -1) {
this.matched.push(route)
//路由/info
if (route.children) {
this.match(route.children)
}
return
}
}
}
}
kVueRouter.install = function (_vue) {
//保存构造函数,在KVueRouter中使用
Vue = _vue
//挂载$router
//怎么获取根实例下的router选项
Vue.mixin({
beforeCreate() {
// console.log(this);
//确保根实例的时候才执行,只有根实例的时候才会存在router,所以用下面的判断
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
//任务2:实现两个全局组件router-line 和router-view
/* Vue.component('router-link', {
template:'' //在这里不能使用template的原因是现在是run-time only,即纯运行时的环境,没有编译器,所以不能使用template
}) */
Vue.component('router-link', Link)
Vue.component('router-view', View)
}
export default kVueRouter
2、krouter-view.js
export default {
render(h) {
//获取path对应的component 这里性能太低,path每变一次都需要都去循环一下
/* let component = null;
this.$router.$options.routes.forEach(route => {
if (route.path === this.$router.current) {
component = route.component
}
}) */
//标记当前router-view深度
this.$vnode.data.routerView = true;
let depth = 0
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData) {
if (vnodeData.routerView) {
//说明当前parent是一个router-view
depth++
}
}
parent = parent.$parent
}
let component = null
const route = this.$router.matched[depth]
if (route) {
component = route.component //当前的组件component设为匹配到的路由的组件
}
return h(component)
}
}
3、krouter-link.js
export default {
props: {
to: {
type: String,
required: true
},
},
render(h) {
//abc url中最终渲染的
//XXXX 最终使用时的用法
//h(tag ,data, children)
console.log('this.$slots:===', this.$slots);
return h('a', {
attrs: { href: '#' + this.to }, class: 'router-link'
}, this.$slots.default)
//下面是jsx的写法 但是脱离了vue-cli没办法成功,因为vue-cli有webpack可以进行编译
// return {this.$slots.default}
}
}
4、全局引入krouter :main.js
import router from './krouter'
import store from './kstore'
Vue.config.productionTip = false
new Vue({
//Vue.prototype.$router = router 在所有组件中都可以使用$router
router,
store,
render: h => h(App)
}).$mount('#app')
5、在router/index.js中引入手写的router:
import Vue from 'vue'
import VueRouter from './kvue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
//路由表
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
children: [
{
path: '/about/info',
component: {
render(h) {
return h('div', 'about child :info page')
}
}
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
6、About.vue页面中使用手写的嵌套路由:about/info
This is an about page
总结:
手写router的大致思路:
1、作为一个插件存在:创建VueRouter类和install方法。kvue-router.js
2、实现两个全局组件:router-view用于显示匹配组件内容,router-link用于组件之间跳转。krouter-view.js和krouter-link.js
3、监控url变化:使用hashChange或popChange事件。kvue-router.js
4、响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示。kvue-router.js
1、kvuex.js
let Vue;//保存vue构造函数,避免打包时import导致文件过大
class Store {
constructor(options) {
this._mutations = options.mutations
this._actions = options.actions
this._wrappedGetters = options.getters
//定义computed选项
const computed = {}
this.getters = {}
//doubleCounter(state){}
const store = this
Object.keys(this._wrappedGetters).forEach(key => {
//获取用户定义的getter
const fn = store._wrappedGetters[key]
//转换为computed可以使用的无参数形式,因为在使用时doubleCounter(state){}需要传参,但是computed计算属性不能传参数所以在这里进行封装
//key就是上面的doubleCounter
computed[key] = function () {
return fn(store.state) //6666这步操作把computed赋值为一个函数,这个函数返回fn,fn里面把state传进去
}
//为getters定义只读属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key] //想一下这里为什么可以这样写 ,因为我们上面定义的computed对象会把所有的key都放到new Vue实例的computed上
})
})
//响应化处理state
/* this.state = new Vue({
data:options.state
}) */
//这种方式比上面的更好
this._vm = new Vue({
data: {
//加两个$,Vue不做代理
$$state: options.state
},
computed
})
//绑定commit,dispatch的上下文为Store实例
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
//存取器 store.state
get state() { return this._vm._data.$$state } //这里不太清楚,还得再看看
set state(v) {
console.error('你造吗?你这样直接改store.state不好!')
}
//store.commit('add',1)
//type:mutation的类型
//paylod是载荷,是参数
commit(type, payload) {
const entry = this._mutations[type]
if (entry) {
entry(this.state, payload)
}
}
dispatch(type, payload) {
const entry = this._actions[type]
if (entry) {
//把this传进来,那用的时候就可以解构赋值传个add({commit,state,type}),{commit,state,type}这个是解构赋值,对应着这里传入的this
entry(this, payload)
}
}
}
function install(_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
},
})
}
export default {
Store,
install
}
2、在kstore/index.js中使用手写的vuex
import Vue from 'vue'
import Vuex from './kvuex' //引入我们自己手写的vuex
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0
},
getters: {
doubleCounter(state) {
return state.counter * 2
}
},
mutations: {
add(state) {
state.counter++
}
},
actions: {
add({ commit }) {
setTimeout(() => {
commit('add')
}, 1000)
}
},
modules: {
}
})
3、在main.js全局引入
import store from './kstore' //引入我们手写的store
new Vue({
//Vue.prototype.$router = router 在所有组件中都可以使用$router
router,
store,
render: h => h(App)
}).$mount('#app')
4、在Home.vue页面组件中使用
state counter: {{$store.state.counter}}
actions counter:{{$store.state.counter}}
getters counter:{{$store.getters.doubleCounter}}
kvuex总结:
1、实现一个插件:声明Store类,挂载$store
2、$store的具体实现:
- 创建响应式state,保存mutations、actions和getters。
- 实现commit根据用户传入的type(即上例中的add),执行对应mutation
- 实现dispatch根据用户传入的type(即上例中的add),执行对应action,同时传递上下文。
- 实现getters,按照getters定义对state作派生(即getters中属性的改变依赖于state)