一、手寫vue-router
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:'<a></a>' //在這裏不能使用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) {
//<a href="#/about">abc</a> url中最終渲染的
//<router-link :to="/about">XXXX</router-link> 最終使用時的用法
//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 <a href={'#' + this.to}>{this.$slots.default}</a>
}
}
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
<template>
<div class="about">
<h1>This is an about page</h1>
<router-view></router-view>
</div>
</template>
總結:
手寫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
二、手寫kvuex
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頁面組件中使用
<!-- 手寫kVuex -->
<div>
<p @click="$store.commit('add')">state counter: {{$store.state.counter}}</p>
<p @click="$store.dispatch('add')">actions counter:{{$store.state.counter}}</p>
<p>getters counter:{{$store.getters.doubleCounter}}</p>
</div>
kvuex總結:
1、實現一個插件:聲明Store類,掛載$store
2、$store的具體實現:
- 創建響應式state,保存mutations、actions和getters。
- 實現commit根據用戶傳入的type(即上例中的add),執行對應mutation
- 實現dispatch根據用戶傳入的type(即上例中的add),執行對應action,同時傳遞上下文。
- 實現getters,按照getters定義對state作派生(即getters中屬性的改變依賴於state)