手寫自己的Vue全家桶之kvue-router

2021-03-06 前端光影


學習Vue的全家桶vue-router的實現原理和步驟


1、Vue的插件實現寫法

2、render函數的使用

3、數據響應式方法:Vue.util.defineReactive / new Vue ( { data: {} } )

一、預備知識回顧

Vue的插件一般是用來為Vue來添加全局功能的,可以用來添加全局方法或者屬性、添加全局資源(指令/過濾器等等)、通過全局混入來添加一些組件的選項以及可以添加Vue的實例方法。

1MyPlugin.install = function (Vue, options) {
2// 1.添加全局方法或者屬性
3    Vue.globalMethod = function () {}   
4// 2.添加全局資源
5    Vue.directive('my-directive', {}) 
6// 3.混入注入組件選項
7    Vue.mixin({
8        created: function (){}
9    })
10// 4.添加實例方法
11    Vue.prototype.$myMethod = function(methodOptions){}
12}

1插件的使用可以有2種形式
21.第一種那就是在實現的插件代碼裡面加上代碼:
3if (typeof window !== 'undefined' && window.Vue) {
4// 使用插件
5window.Vue.use(MyPlugin)
6}
72.第二種就是可以直接在main.js中直接使用Vue.use(MyPlugin)

Vue推薦在日常使用中應該使用模板來創建HTML,但是有的時候需要JS的能力,那麼這時候就可以使用渲染函數來完成

1render: function (createElement) {
2    // createElement函數返回結果是VNode
3    return createElement(
4        tag,     // 標籤名稱
5        data,   // 傳遞數據
6        children // 子節點數組
7    )
8}

Why:從例子中我們可以看到我們常見的都是使用的render(h),通過學習發現,其實在Vue的底層在生成虛擬DOM方面其實是借鑑的Snabbdom.js,而在這個裡面他的生成虛擬DOM的函數就叫h,所以其實h就是我們上面看到的createElement

1// 例子heading組件
2// <heading :title="title">{{title}}</heading>
3Vue.component('heading', {
4    props: {
5        title: {
6            type: String,
7            default: ''
8        }
9    },
10    render(h) {
11        return h(
12        'h2',    // 參數1:tagname
13        {attrs: {title: this.title} }  // 參數2:與模板中屬性對應的數據對象
14        this.$slots.default  // 參數3:子節點VNode數組
15        )
16    }
17})

3.數據響應式方法:Vue.util.defineReactive / new Vue ({data: {}})

我們知道Vue最大的亮點之一就是響應式,凡是寫入data裡面的都會被變成響應式數據,而在Vue當中想實現某個數據的響應式有幾種,比如可以藉助Vue工具包util中的defineReactive,來看下具體的用法:

1// defineReactive:定義一個對象的響應屬性
2    Vue.util.defineReactive(obj, key, value, fn)
3    obj:目標對象
4    key:目標對象屬性
5    value:屬性值
6    fn:只在node調試環境下set時調用

其實Vue.util中還有一些函數,如:

二、Vue全家桶之Vue-router的手動實現

    1.在我們手動實現vue-router之前,我們先看一下他在Vue中的使用

1import Router from 'vue-router'
2Vue.use(Router)

1export default new Router({...})

1import router from './router'
2new Vue({
3    router
4}).$mount('#app')

1<router-view></router-view>

1<router-link to="/">home</router-link>
2<router-link to="/about">about</router-link>
3
4this.$router.push('/')
5this.$router.push('/about')
6

2.我們發現作為Vue全家桶之一的vue-router使用起來非常方便,幫助我們很好的處理了路由的跳轉的問題,那麼現在我們就來看看怎麼實現一個簡化版的vue-router,從而更加了解他的內部原理

實現一個插件:創建VueRouter類和install方法

我們在使用vue-router的時候我們可以發現,我們是先使用use註冊的,然後再去創建的Router實例,但是我們在寫install方法的時候又需要用到,所以我們只能只用mixin來做延遲處理,等到有了組件實 例之後我們再進行使用

1// router-view
2// 新建一個kvue-router.js
3// 引用構造函數,VueRouter中要使用
4let Vue
5// 保存用戶的選項
6class VueRouter() {
7   constructor(options){
8        this.$options = options
9    }
10}
11// 實現VueRouter的install方法,註冊$router在Vue的原型之上
12VueRouter.install = function(_Vue) {
13    // 引用構造函數,VueRouter要使用
14    Vue = _Vue
15    Vue.mixin({
16        beforeCreate(){
17            if(this.$options.router) {
18                // 將$router掛載到Vue的原型之上,方便所有的組件都可以使用this.$router
19                Vue.prototype.$router = this.$options.router
20            }
21        }
22    })
23    // 實現倆個全局的組件router-link和router-view
24    Vue.component('router-link', RouterLink)
25    Vue.component('router-view', RouterView)
26}
27
28export default VueRouter

創建router-view和router-link

1// router-link
2Vue.component('router-link', {
3    props: {
4        to:{
5            type: String,
6            required: true
7        }
8    },
9    render(h) {
10        return h(
11            'a',
12            attrs: { href: '#'+ this.to },
13            this.$slots.default
14        )
15    }
16})
17
18// router-view
19Vue.component('router-view', {
20    render(h) {
21        return h(null)
22    }
23})

1class VueRouter(){
2    constructor(options) {
3        // 定義一個響應式的數據current表示路由的變化
4        const initial = window.location.hash.slice(1) || '/'
5        // Vue中的響應式方法
6        Vue.defineReactive(this, 'current', initial)
7        //  監聽hashChange事件
8        window.addEventListener('hashChange', this.onHashChange.bind(this))
9    }
10    onHashChange(){
11        this.current = window.location.hash.slice(1)
12    }
13}

1// router-view
2Vue.component('router-view', {
3    render(h) {
4        // 動態的獲取組件,進行內容的渲染
5        let component = null
6        const route = this.$router.$options.routes.find(route => route.path === this.$router.current)
7        if(route) component = route.component
8        return h(component)
9    }
10})

提前處理路由表:到這裡其實我們已經實現了整個vue-router的核心功能,我們去用我們寫好的kvue-router去替換掉vue-router,是可以實現我們想要的功能的

一個疑問:從上一步我們動態獲取對應組件的邏輯裡面我們不難發現,如果直接就這樣寫,我們會發現只要路由一變化,render就會執行一遍,這時候裡面的遍歷循環也會相應執行一遍,那怎麼處理這個問題呢?

回答:這個地方我們需要做一下優化,我們可以這樣想,如果我們可以提前去處理好path和route的映射關係,那麼不就可以避免每次都要循環了嗎

1// router-view
2class VueRouter {
3    constructor(options) {
4        // 緩存path和route映射關係
5        this.routeMap = {}
6        this.$options.routes.forEach(route => {
7            this.routeMap[route.path] = route
8        });
9    }
10}
11
12// router-view
13export default {
14    render(h) {
15        const {routeMap, current} = this.$router
16        const component = routeMap[current] ? routeMap[current].component : null;
17        return h(component);
18    }
19}

‍‍‍截止到這裡,咱們的kvue-router基本核心部分算是完成了,但是回想咱們日常使用的router,會發現現在我們寫的並不支持子路由,也就是說有嵌套的我們現在是不支持的,那怎麼解決這個問題呢,咱們接下來繼續

思考:子路由的嵌套更深層次的含義是什麼?

思路:我們想一下,其實子路由的嵌套在邏輯上是路由的嵌套,但是那在實際上呢?回到代碼中看,那實際上是不是就是router-view相互的嵌套,那麼我們現在需要做的不就是去遞歸遍歷路由表嗎?但是我們要思考如何找到每個子路由對應的組件呢?

源碼思想:通過看源碼發現,先給router-view增加一個routerView的屬性,然後初始化一個深度變量depth,然後去遞歸組件的父級看是否存在routervView為true,如果是則depth++,同時會定義一個響應式的數組matched,並且使用一個遍歷路由表的方法match來進行遞歸遍歷查看是否存在子路由,最後通過matched[depth]找到該層級

代碼實現:

1Vue.component('router-view', {
2    render(h) {
3      // 標記當前router-view的深度
4      this.$vnode.data.routerView = true
5      let depth = 0
6      let parent = this.$parent
7      while(parent) {
8        const vnodeData = parent.$vnode && parent.$vnode.data
9        if (vnodeData) {
10           if (vnodeData.routerView) {
11              depth++
12           }
13        }
14        parent = parent.$parent
15      }
16    }
17})

1class VueRouter {
2  constructor (options) {
3    Vue.util.defineReactive(this, 'matched', [])
4      // match方法可以遞歸遍歷路由表,獲得匹配關係數組
5      this.match()
6  }
7  //match遍歷路由表方法
8  match (routes) {
9    routes = routes || this.$options.routes
10    // 遞歸遍歷
11    for (const route of routes) {
12      if (route.path === '/' && this.current === '/') {
13        this.matched.push(route)
14        return
15      }
16      if (route.path !== '/' && this.current.indexOf(route.path) != -1) {
17        this.matched.push(route)
18        if (route.children) {
19           this.match(route.children)
20        }
21        return
22      }
23    }
24  }
25}

1Vue.component('router-view', {
2    render(h) {
3      ...
4      let component = null
5      const route = this.$router.matched[depth]
6      if (route) {
7         component = route.component
8      }
9      return h(component)
10    }
11})

1onHashChange() {
2  this.current = window.location.hash.slice(1)
3  this.matched = []
4  this.match()
5}


以上便是全部關於手寫kvue-router的內容了,代碼量其實不是很大,主要是關於源碼處理的思路,學會思路更為重要,寫完一遍之後會加深很多對於vue-router的理解,以後用起來也會更加順暢。

最後附上GitHub地址:

https://github.com/guangyingmi/Vue

                                  

給個三連唄

                   

相關焦點

  • Vue全家桶&vue-router原理
    // Vue2學習 第1篇  // 正文:1017字// 預計閱讀時間:8 分鐘前言 每天一點點,堅持學習Vue全家桶之vue-router原理 Vue全家桶:vue + vue-router(路由)+ vuex(狀態管理)+ axios(請求)本次主要分析並實現簡版vue-router
  • Vue-Router源碼學習之index.js(vue-router類)
    前言上一篇我們聊了:Vue-Router源碼學習之install方法雖然最近需求著實不少,但是感覺自己學習勁頭還是蠻足的正文vue-router類裡面都做了什麼?= {}),在vue-router中使用了flow.js做了類型的檢查,什麼是flow.js?
  • 從頭開始學習 vue-router
    為什麼我們不能像原來一樣直接用標籤編寫連結哪?vue-router如何使用?常見路由操作有哪些?等等這些問題,就是本篇要探討的主要問題。二、vue-router是什麼這裡的路由並不是指我們平時所說的硬體路由器,這裡的路由就是SPA(單頁應用)的路徑管理器。再通俗的說,vue-router就是WebApp的連結路徑管理系統。v
  • vue-router 實現分析
    是 Vue.js 官方的路由庫,本著學習的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其他感興趣的同學做個參考吧。參考初步我們分別從不同的視角來看 vue-router。})源碼(https://github.com/vuejs/vue-router/blob/v2.2.1/src/history/hash.js#L21)這個動作是什麼時候執行的呢?是在 router.init()(源碼)中調用的,而 router.init() 則是在根組件創建時(源碼)調用的。
  • Vue面試題(3)Vue-Router和Vuex
    希望另一個組件響應該變化時,localstorage無法做到,原因在區別1那裡6、vue-router的原理是什麼?實現原理:vue-router的原理就是更新視圖而不重新請求頁面vue-router可以通過mode參數設置為三種模式:hash模式、history模式、abstract模式。
  • 官方推薦:Vue之router路由最優美寫法
    Router from 'vue-router'import HelloWorld from '@/components/HelloWorld'Vue.use(Router)export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld
  • 快速掌握 Vue3 全家桶開發
    /Test.vue';Vue.component('Test', Test);// Vue3 使用 createApp().component 方式import { createApp, h } from 'vue';import Test from '.
  • 史上最全:Vue 相關開源項目庫匯總
    douban ★606 - 基於vue全家桶的精緻豆瓣DEMOvue-Meizi ★604 - vue最新實戰項目maizuo ★603 - vue/vuex/redux仿賣座網vue-WeChat ★558 - 基於Vue2高仿微信App的單頁應用vue-demo-kugou ★500 - vuejs仿寫酷狗音樂webapp
  • Vue全家桶使用免費內網穿透工具, 無需伺服器讓外網直接訪問
    Vue全家桶使用免費穿透工具, 無需伺服器讓外網直接訪問 Vue全家桶穿透認準穿透工具 網雲穿-最簡單易用的內網穿透軟體,最簡潔教程一鍵穿透網站
  • Vue-router-link跳轉頁面時不在頂部問題解決
    我們在使用vue router-link組件進行頁面跳轉時,會出現當前頁面在什麼位置,跳轉頁面也在什麼位置這樣如果當前頁面過長,跳轉的效果就不是很好只需要在router文件夾下的index.js文件中添加一段代碼即可如下圖所示,添加如下代碼,就完美解決這個問題了!
  • 2018尚矽谷Vue技術全家桶視頻教程發布
    vue技術全家桶
  • Vue.js+Node.js 移動端小米商城全棧開發
    課程簡介課程涉及的技術棧Vue全家桶:Vue、Vue-router、Vuex、Vue-cli、Vue-devtools、Vue-loader組件化:單Vue文件模塊化:ES2015 Module、CommonJS第三方模塊:axios、Swiper、Velocity、qs等接口管理平臺:RAP數據模擬:Mockjs基礎開發環境和包管理:Node、Yarn構建工具:webpack、gulp編輯器:VSCode前置知識 Vue全家桶的文檔 ES2015
  • vue題
    vue的數據雙向綁定 將MVVM作為數據綁定的入口,整合Observer,Compile和Watcher三者,通過Observer來監聽自己的model的數據變化,通過Compile來解析編譯模板指令(vue中是用來解析 {{}}),最終利用watcher搭起observer和Compile之間的通信橋梁,達到數據變化 —>視圖更新;視圖交互變化(input)—>數據model
  • Vue入門10 vue+elementUI
    注意:命令行都要使用管理員模式運行1、創建一個名為hello-vue的工程vue init webpack hello-vue2、安裝依賴, 我們需要安裝vue-router、element-ui、sass-loader和node-sass四個插件cd hello-vuenpm
  • 手寫React-Router源碼,深入理解其原理
    本文會繼續深入React-Router講講他的源碼,套路還是一樣的,我們先用官方的API實現一個簡單的例子,然後自己手寫這些API來替換官方的並且保持功能不變。React-Router架構思路 我之前另一篇文章講Vue-Router的原理提到過,前端路由實現無非這幾個關鍵點:其實React-Router的思路也是類似的,只是React-Router將這些功能拆分得更散,監聽URL變化獨立成了history庫,vue-router裡面的current
  • React + Redux + React-router 全家桶
    <Provider store={store}>      <Capp /></Provider>上邊提供的demo表明Provider在根組件外面包了一層,使App的所有子組件都可以拿到statereact-router-dom
  • Vue.js布局
    動態Vue.js布局組件前言vue.js是漸進增強的視圖庫,可以作為.html頁面部分使用,也可以結合vue-router、vuex、axios用來構建單頁面或多頁面應用。
  • 解密Vue SSR
    最後還將附上一個筆者實現的去除Vue全家桶的Demo案例。原來只是一個純靜態的HTML頁面啊,沒有任何的交互邏輯,所以啊,現在知道為啥子需要服務端跑一個vue客戶端再跑一個vue了,服務端的vue只是混入了個數據渲染了個靜態頁面,客戶端的vue才是去實現交互的!
  • 如何用 Vue + Vuetify 快速建站?
    Vuetify + Vuex + Vue-router + Vue-router-sync這裡側重講怎麼將這些都結合在一起使用,每個庫的細節還是要大家自己去看官方文檔,每個官方文檔連結附錄在最後。/router'import { sync } from 'vuex-router-sync'Vue.use(Vuetify);sync(store, router);new Vue({el: '#app',router,store,template: '<App/>',
  • Springboot Vue Login(從零開始實現Springboot+Vue登錄)
    創建 Vue 項目 (進入自己想創建的文件夾位置,我放在 D:\VSCodeWorkSpace),創建語句 vue create vue-spring-login-summed,方向鍵選擇創建方式,我選擇的默認2.