手寫vue-router與vue-vuex

一、手寫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)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章