【vue-debug-plugin 重構版 】| 不來體驗一下vue數據瞭如指掌的快樂?

前言

上個月入職了新公司,開始新的造夢(填坑)之旅,開發過程當中遇到了VueDevTool沒法使用的狀況,歸納而言即因項目過大致使VueDevTool崩潰的狀況,console了幾天頂不住了,遂嘗試開發vue插件解決,效果以下,純原創毫不是重複造輪子,瓜包熟,走過路過別錯過。html

最重要的是:每日一冰,幹活賣命,明年老闆換新車前端

640-1

前言以外

若是VueDevTool用得很快樂,也建議看看,本菜在實現插件過程當中遇到不少問題,最後發現解決或優化方案竟然大多來自於以前學習vue源碼時的積累,更感受厚積薄發的重要性,在這裏暫舉兩個我的以爲比較有表明性的問題及解決關鍵點vue

  • 我但願拿到頁面全部已掛載的局部組件實例,思路是根據組件樹,由頁面組件(根)深度遞歸從而動態進行後代組件收集,很天然想到beforeRouteEnter,那beforeRouteEnter在什麼時機執行?該怎麼拿到組件實例?如何區分全局組件和局部組件?若是有子路由,我想拿最深一層的頁面組件實例又該怎麼拿?node

    # 關鍵點
    next Or 洋蔥模型
    路由守衛在父子組件中的生命週期鉤子執行順序
    複製代碼
  • 後代組件實例及其dom如何獲取?(但願無侵入,因此不能用refs)在數據變化致使後代組件裝載卸載時,如v-if,我該如何監聽到變化的後代組件、而且從新構建插件?若是觸發了屢次組件裝、卸載,我只但願重建一次且局部構建,如何實現?git

    # 關鍵點
    $children
    父組件mounted必定在子組件mounted後
    nextTick實現原理
    domdiff
    複製代碼

先看效果

無父子路由狀況

存在父子路由狀況

2021-05-09 20.06.32

補充一下,當切換到【中藥預覽頁】時(這裏的切換是用過v-if控制的,),加載了兩個組件,因而會觸發兩次構建的訂閱(構建具體含義後文會詳細說的),但只發布了一次,這裏就是用到了nextTick的思路github

面向問題

主要面向的實際上是VueDevTool這個谷歌插件,實現的功能也和其類似(少俠休走,存在如快速切換定位組件、直接操做組件狀態必定程度節省更改代碼時間等優化);vue-router

先說咱們熟悉的VueDevToolnpm

image-20210430134147910

即時查看組件數據和必定程度修改組件數據的功能一度讓我愛不釋手,但有些問題也讓我咬牙切齒,主要是下面三點數組

  • VueDevTool若是沒有設置過濾公共組件,層級會很深很深,點點點以後若是刷新了頁面又得從新點點點

image-20210508163612982

  • 屬性修改麻煩,尤爲引用類型,還不如改代碼熱更新,但改代碼又挺麻煩。

image-20210508163722130

  • 最關鍵一點,Vue項目(如我司)若是體積龐大,常常會出現VueDevTool崩潰從而沒法查看組件屬性值的狀況

最後一點至爲關鍵,畢竟感覺過了美好,誰還想回到刀耕火種?一直打一個log看一下頁面確實煩惱,就嘗試看能不能優化下這個問題,也會有缺陷,但進步是一步接一步嘛。promise

v2-9c6182a96443f811e00edd8774d5cc4b_720w

解決思路

如今來思考下咱們要解決的問題:

只考慮匹配一個路由的狀況:如何獲取到頁面組件對應的全部已掛載後代組件,從而完成組件初始化(註冊)

路由組件自己能夠經過beforeRouteEnter鉤子獲取;獲取後代組件,主要在於獲取組件掛載名(key)和組件實例(val),而自己就是一顆組件樹,問題就轉變爲了由根節點遞歸一棵樹,查閱源碼後,知道了在vue中主要利用兩個屬性:

  • 組件實例:$children

  • 組件名:$options.__file

要注意兩點:

  1. 註冊邏輯要發生在頁面根組件的mounted中,這樣才能保證全部子組件掛載完成(參見【前置條件】中生命週期順序)
  2. 數據變化致使後代組件裝載卸載時,咱們能夠進行「dom diff」(幼稚園版,別笑啦),在組件掛載的mounted中判斷其父組件的uid在不在當前已經收集到的組件列表中(用一個數組ids存儲全部掛載組件的id),在則進行局部註冊,不在則直接無論(說明其父組件是全局組件)

遞歸函數以下

function _registerComp(rootCompInstance,rootCompName){
  		// 註冊當前組件
      setVmInstance.call(rootCompInstance,rootCompName)
  		// 收集當前已註冊組件的id,用以實現後面v-if致使的組件裝、卸載狀況
      ids.push(rootCompInstance._uid);
  		// 獲取全部已掛載子組件
      let compsInstance = rootCompInstance.$children;
      compsInstance.forEach(comp => {
          let {err,result} = getVal(comp,'$options._componentTag');
          let compName;
          if(err){
            // console.log('獲取失敗',rootCompInstance,comp);
              // compName = '獲取失敗'
          }else {
              compName = tf(result);
              // 遞歸加載 組件中的內容 w-todo 待添加引用關係 父子孫組件
              let filePathInfo = getVal(comp,`$options.__file`);
              // 若是找不到__file 說明是全局UI組件 忽略掉
              if(filePathInfo.err){ 

              }else {
                _registerComp(comp,compName)
              }
          }
          
      })
    }
複製代碼
匹配多個路由的狀況:註冊邏輯可複用,關鍵是如何獲取到全部匹配的路由對應組件

這道題,enenen,會者不難難者不會,有兩個方案:一是藉助洋蔥模型,不斷的在beforeRouteEnter的next中收集匹配到的實例;二是 r o u t e . m a t c h e d ,我也是很偶然的問了下坐旁邊的大神同事才知道能夠經過 route.matched,我也是很偶然的問了下坐旁邊的大神同事才知道能夠經過` route.matched獲取。其返回的是一個數組,存儲元素的數據結構中有兩個屬性components.default.__fileinstances.default`,分別能夠獲取組件名和組件實例,剩下的,直接複用上面的註冊邏輯就好啦。

擴展-洋蔥模型:next實現原理

此處作一個擴展,有個需求:給定一堆函數,但願按順序調用時,上一個函數控制下一個函數的執行時機,好比上一個函數執行時符合一個條件才繼續執行下面的函數,不符合則停止調用,如何實現?

這其實就是咱們常見的next的寫法,不過度擴展,附上實現next的代碼以供理解(此處假定數組中存入的是對象,其屬性handler是給定函數),只說兩句

  1. 遞歸+手動控制索引idx控制函數數組的執行;
  2. 裝飾者,用一個函數去包裹真正要執行的函數,深度遞歸併將之傳遞給上一個函數以決定是否繼續遞歸
function compass(arr){
  let idx = 0;
  let next = () => { 
    if (idx === arr.length) {
      return out()
    }
    let layer = arr[idx++];
    // 這個match就是上層函數判斷是否是須要繼續執行下一個函數的邏輯
    if (layer.match(pathname)) {
      layer.handler(req,res,next);
    }else {
      next();
    }
  }
  next();
}
複製代碼

(其實支持多級路由的思考也是大神同事給的,果真idea纔是區分碼農和大神的分水嶺,在此感謝)

image-20210510090951831

根據收集到的數據渲染頁面

.vue文件中,這是個很簡單的問題,但在插件中,卻牽扯了不少海面下的陳年往事,主要是:jsxh(creatElement)rendervnode的前世此生;如上面效果圖,我想渲染的是一個tabs效果,其對應的代碼是下面這樣的。

function generateTabs(opt,target,targetKey) {
        let {key,props = {}, style = {} , events = [],children = []} = opt;
        function _genPane(list) {
            return list.map(item=>{
                let {props={},content} = item;
                return h('el-tab-pane',{
                    props: {
                        ...props,
                        key: props.name
                    }
                },[content])
            })
        }
        return h('el-tabs',{
            props: {
                ...props,
                value: target[targetKey]
            },
            style,
            on: {
                ...events,
                'tab-click':(item)=>{
                    target[targetKey] = item.name
                }
            }
        },_genPane(children))      
    }
複製代碼

其中咱們常見的v-model對應在jsx屬性中value和事件tab-click;從中咱們也能真正理解以前背的八股文了:v-model是change和value的語法糖。

至於幾個關鍵詞的關係,限於篇幅只能在下一篇文章中詳細解釋,只說一句話:真實dom根據虛擬dom即vnode生成,而render就是返回虛擬dom的函數,h函數是creatElement函數的別名,用於根據配置項生成vnode。

前置條件
  • 考慮無侵入,採用vue的插件機制:Vue的插件機制其實就是傳遞use函數一個帶install方法的對象,會默認執行install方法並傳遞Vue構造函數。

  • 考慮是debug插件,對外暴露isDev的配置項(具體閱讀【配置項】),若是是測試環境才接入插件

  • 路由守衛在父子組件中的生命週期鉤子執行順序

    父beforeRouteEnter - 父beforeMount - 子beforeMount  - 父beforeRouteEnter-next - 子mounted - 父mounted
    複製代碼
  • beforeRouteEnter中支持以下寫法獲取組件實例,假設三級父子路由嵌套,此鉤子會匹配三次,而根據洋蔥模型原則,咱們能夠持有一個變量用於存儲路由匹配到的組件,最後一次next函數的賦值就是咱們要的最深的路由匹配組件

    beforeRouteEnter (to, from, next) {
              next(vm => {
                console.log('beforeRouteEnter==',vm.$options.name); 
                // 最後一個被路由匹配上的就是頁面組件
                pageVm = vm;
              })
            },
    複製代碼

    image-20210508173227578

具體實現

獲取組件實例

分兩種狀況:

  • 第一次頁面初始化 :在最外層頁面組件的mounted才進行插件的初始化,這樣就能夠避免重複註冊的性能浪費
  • v-if致使頁面中組件的裝載卸載 :若是其組件的父組件在當前收集中心中,就根據當前頁面實例重置收集中心列表
    • 假設多個組件掛載, 就會觸發屢次重置, 此處藉助nextTick實現原理,進行一層優化,採用微任務進行註冊,且使用防抖。
部分具體實現相關代碼以下
mounted(){
  let ids = getCurrentIds()
  if( this.$parent && ids.indexOf(this.$parent._uid) !== -1){
    emitInitVmDebuPlugin()
  }
  if(currentPageVm && currentPageVm._uid == this._uid){
    emitInitVmDebuPlugin()
  }
}
// 觸發初始化插件邏輯 根據全部頁面組件進行收集中心初始化
export function emitInitVmDebuPlugin(){
    // console.log('開始註冊邏輯 訂閱');
    // 根據頁面組件獲取其對應的收集中心 (考慮都要支持多級路由 此處爲將全部PageVmHandler進行收集中心的初始化)
    function initVmDebuPlugin(){
      // console.log('開始註冊邏輯 發佈')
    	// 初始化邏輯
    }
    // 避免重複調用
    nextTick(initVmDebuPlugin)
}
複製代碼
擴展-nextTick:延遲執行原理

此處作一個擴展,有個需求,異步數據使得vue數據改變致使頁面變動,但願能拿到dom。很天然的咱們會想到this.$nextTick,那他是怎麼作到的呢?還有,咱們屢次改變屬性值時,頁面其實只刷新了一次,又是怎麼實現的?

一樣言簡意賅的說,事件環微任務+防抖且去重,你們程度不一,本菜儘量快速科普一下。

事件環機制

事件環機制對客戶端而言,能夠簡單理解爲存在兩個隊列,分別存放宏任務(setTimeout、setInterval、Promise的構造函數是同步的、setImmediate、I/O、UIrendering)和微任務(Promise的回調(then)、process.nextTick),給個我的記憶思路:若是是語言原生提供的,就是微任務,如Promise;果是宿主環境提供的,如setTimeout則是宏任務。

  1. js文本在進行解析後,會將文件中的任務進行分配爲:主線程隊列,微任務隊列和宏任務隊列
  2. 主線程隊列會依次從隊列中pop到調用棧中執行,在執行中若是內部包含微任務/宏任務則會再次推入微任務隊列/宏任務隊列
  3. 主任務隊列執行完畢後,會查看微任務隊列&宏任務隊列中是否有須要執行的任務,擁有的話,將其推入到主任務隊列。
  4. 循環上述操做,造成js的執行環

根據這種機制,Vue中會將函數放在微任務中,好比Promise.resolve(),這樣就能等到頁面dom已經掛載上再去獲取了。

防抖且去重,這沒啥,設個開關和定時器就好;去重能夠用Set[...new Set(list)],廢話很少說,show me code

let callbacks = [];
let waiting = false;
function flushCallbacks(...params) {
  callbacks = [...new Set(callbacks)]
  for (let i = 0; i < callbacks.length; i++) {
      let callback = callbacks[i];
      callback(...params);
  }
  waiting = false;
  callbacks = [];
}

export function nextTick(cb,...params) {
  callbacks.push(cb); // 默認的cb 是渲染邏輯 用戶的邏輯放到渲染邏輯以後便可
  if (!waiting) {
      waiting = true;
      // 1.promise先看支持不支持 
      // 2.mutationObserver
      // 3.setImmdiate
      // 4.setTimeout Vue3 next-tick就直接用了promise
      Promise.resolve().then(()=>{
        flushCallbacks(...params)
      }); // 屢次調用nextTick 只會開啓一個promise
  }
}
複製代碼

nextTick的兼容判斷我在本身的插件裏沒用這麼複雜,對這個有興趣的同窗能夠自行了解下,核心源碼以下

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
複製代碼
解決持有實例致使內存泄漏問題

咱們知道,一個對象若是存在引用,就會干擾js的垃圾回收,假設咱們進行了頁面跳轉,收集中心仍保持舊頁面實例,就會出現這個問題,這裏咱們能夠採用路由守衛進行清空收集中心的處理。

這裏只能點到爲止,具體建議閱讀【迭代優化】模塊,是全部修復問題的心路歷程。

部分實現相關代碼以下
// 在頁面跳轉時清空插件
beforeRouteLeave(to, from, next){
  // 清空插件 考慮都要支持多級路由 調用全部的PageVmHandler的重置方法
  resetVmDebuPlugin()
  next()
},
複製代碼
涉及到微前端

因我司有微前端體系,因此有作兼容,稍微複雜點,不過也差很少,若是不瞭解微前端的小夥伴,打個廣告,能夠看下個人微前端系列文章:微前端之singleSPA實戰|技術點評,最關鍵點只有一個:在主應用中能夠獲取到子應用的全局變量,因此兼容思路以下

  1. 插件支持配置項isMappgetMappWinodow,前者用以判斷是子應用仍是主應用,後者用於在主應用中獲取子應用的全局變量
  2. 切換到子應用時,將收集中心切換到子應用的收集中心,後續邏輯複用

這樣,咱們就實現了子應用的兼容啦

if(callFn(isDev,window.location)){
    // 若是是子應用 則加載子應用的插件
    if(isMapp){
      return mappPluginFn(options)
    }
    // 不然 加載主應用的插件
    else{
      return vmPluginFn(options)
    }
  }
複製代碼
解決思路小結

其實核心思路也講的差很少了,一句話:以頁面爲單元,收集當前已渲染的組件實例,用戶能夠自行切換(如目前的點擊切換)當前查看變量指向哪一個組件實例,至此,咱們就能夠快樂的拋棄console.log啦(至少別那麼常見zzz)。

(很簡單,但小夥伴們說挺實用的,大佬們別嫌棄哈,若是能夠,煩勞貴手幫南方本小菜點個star鼓勵一下吧)

v2-6bb953c88d6fac3d523b8d7aedcd0535_720w

解決後結果

  1. 能夠直接操做vue實例從而查看和修改數據(順帶解決了VueDevTool層級很深很深的麻煩)
  2. 能夠任意切換當前頁面已掛載的組件,查看和控制數據(這就意味着在測試環境咱們能夠直接看更改vue組件數據看效果了,必定程度簡化修復bug的成本)
  3. 兼容了微前端的場景,必定程度解決了微前端項目中子應用難以調試的問題

如何使用

安裝npm包
npm i vue-debug-plugin -D
複製代碼
Main.js中引入
import vueDebugPluginFn from 'vue-debug-plugin';
let vueDebugPlugin = vueDebugPluginFn({
  	 /** 無微應用能夠忽略 * @param {*} to 路由對象 詳情可見vue-router官網 * @param {*} pageVm 當前頁面組件實例 * @returns  */
    getMappWinodow(to,pageVm){
        // 若是存在且只存在微前端控制面板 且 子應用存在 則返回
        if(to.fullPath == '/'){
            return pageVm.app.sandbox.proxy;
        }
    },
    isDev(location){   // 默認爲false 不傳插件會直接不加載
        let localIdentifyings = ['8082','test'];
        let isDev = localIdentifyings.some(id => location.href.indexOf(id) !== -1)
        return isDev
    },
    hasElementUI: true  // 項目是否接入了elementUi 
    // isMapp: true 若是是子應用 須要設置爲true 無微應用能夠忽略
})
Vue.use(vueDebugPlugin)
複製代碼
配置項
配置項 類型 做用 是否必填
getMappWinodow Function 獲取子應用的全局變量 (若是不須要承接微應用能夠不傳遞);默認參數會傳遞路由對象和當前頁面組件實例
isDev Function | Boolean 判斷是不是開發環境 若是函數返回爲true才接入插件(傳入true也行);默認參數會傳遞當前頁面路徑(window.location)
hasElementUI Boolean 項目是否接入了elementUi ,若是接入了則採用其進行優化
isMapp Boolean 微應用場景下,子應用也須要引入此插件,若是是子應用 須要將此值設置爲true

舉例(DEMO倉庫

運行效果見文首

頁面代碼
Home
<template>
  <div class="home">
     <h1>{{name}}</h1>
     <w-dialog/>
  </div>
</template>

<script>
import wDialog from '../components/w-dialog';
export default {
  name: 'Home',
  components: {
    wDialog
  },
  data(){
    return {
      name: '老媽身體健康'
    }
  }
}
</script>

複製代碼

其中組件代碼

<template>
  <div v-show="show" class="wrapper">
      {{dianame}}
  </div>
</template>

<script>
export default {
  components: {},
  props: {},
  data() {
    return {
      dianame: '我是彈窗數據-小侄女愈來愈可愛',
      show: false
    };
  }
};
</script>
<style scoped>
.wrapper{}
</style>
複製代碼
About
<template>
  <div class="about">
    <h1>{{name}}</h1>
  </div>
</template>
<script>
export default {
    data(){
      return {
        name: '清哥新婚快樂!!'
      }
    }
}
</script>
複製代碼

迭代記錄及對應思路整理

在mixin中定義setVmInstance 實現組件的訂閱

在須要調試的組件中調用setVmInstance,傳入key,在window上會掛載$vm${key}的變量,指向當前組件的實例

  • 存在問題
    • 每次要調用 代碼侵入性太強、不便後人理解,並且麻煩
    • window上全局變量太多
    • vue實例沒法被垃圾回收
    • 須要記憶不少key
    • mixin中代碼污染
mixin中定義setVm 實如今控制檯中進行組件的切換 setVmInstance用於訂閱 實現保存組件實例

維護一個map數據結構,key爲setVmInstance傳入的key,值爲組件實例,此爲訂閱;定義setVm函數,傳入key則會在map上取出對應的實例,並將$vm指向此實例,此爲發佈;

  • 存在問題

    • 每次要調用 代碼侵入性太強、不便後人理解,並且麻煩
    • vue實例沒法被垃圾回收
    • 須要記憶不少key
    • mixin中代碼污染
  • 解決問題

    • window上全局變量太多 此時window只存在了兩個變量,setVm$vm
採用d_name進行setVmInstance的調用 從而避免暴露setVmInstance這個對內的接口;改寫成插件形式,解耦合

改寫爲插件形式,進行解耦合;在生命週期中進行判斷,若是實例上有定義d_name,則進行訂閱(調用setVmInstance),從而減小對外接口的顯示調用

  • 存在問題

    • 每次要調用 代碼侵入性太強、不便後人理解,並且麻煩
    • vue實例沒法被垃圾回收
    • 須要記憶不少key
  • 解決問題

    • mixin中代碼污染
    • setVmInstance的對外暴露
採用渲染面板的形式,點擊切換組件實例,從而避免暴露setVm這個對內的接口

渲染一個按鈕,點擊顯示渲染面板,將全部訂閱的key渲染在面板上,全部的key包裹節點監聽點擊事件,調用setVm從而切換組件

  • 存在問題

    • vue實例沒法被垃圾回收
    • 沒法承接微前端場景(由於子應用中的window是被封裝過的)
    • 樣式太醜了(zzz,太忙了,有時間再優化)
  • 解決問題

    • 須要記憶不少key
    • 每次要調用 代碼侵入性太強、不便後人理解,並且麻煩
    • setVm的對外暴露
提供getMappWinodow接口 承接微前端場景

考慮微前端場景,將插件分場景判斷,若是是主應用,正常渲染;若是是子應用,在子應用的window(被代理)上掛載vmMap變量,不佳渲染邏輯(使用主應用的),用戶傳遞一個函數getMappWinodow,用戶判斷和獲取子應用的window,若是有返回值,則在主應用中進行vmMap的切換,從而達到承接子應用的效果

  • 存在問題

    • vue實例沒法被垃圾回收
    • 樣式太醜了(zzz,太忙了,有時間再優化)
    • 應該避免污染生產環境
  • 解決問題

    • 沒法承接微前端場景(由於子應用中的window是被封裝過的)
暴露配置項:解決生產環境區分問題和承接子應用問題

分離主應用和子應用插件功能,暴露配置項isMapp用於判斷是否爲子應用; 暴露配置項getMappWinodow用於主應用獲取子應用的全局變量; 暴露isDev用於判斷需不須要加載插件;

  • 存在問題

    • vue實例沒法被垃圾回收
    • 樣式太醜了(zzz,太忙了,有時間再優化)
  • 解決問題

    • 避免污染生產環境
暴露配置項:支持ElementUI 美化樣式

暴露hasElementUI用於判斷項目是否接入了elementUI 若是接入了則使用jsx渲染其組件;

  • 存在問題
    • 代碼污染問題:須要在須要調試的組件里加d_name
  • 半解決問題
    • 樣式太醜了(zzz,太忙了,有時間再優化,暫時支持elementUi,後期有時間本身去優化下,支持任意的組件庫)
  • 解決問題
    • vue實例沒法被垃圾回收
實現去除手動註冊d_name邏輯

搞了五一三天放假的改進,註冊方式再也不放在mixin的mounted中,而是採用beforeRouteEnter,經過當前頁面組件深度遞歸後代組件進行組件註冊

  • 存在問題

    • 樣式太醜了(zzz,太忙了,有時間再優化,暫時支持elementUi,後期有時間本身去優化下,支持任意的組件庫)
    • 後代組件若是在最開始沒有加載(如v-if),則不會被加載,需後期添加動態加載的功能
  • 解決問題

    • 代碼污染問題:須要在須要調試的組件里加d_name
此時獲取組件實例的思路

採用訂閱發佈模式,在插件的做用域中維繫一個對象,key爲setVmInstance傳入的key,值爲組件實例,

維護一個map數據結構(這樣能夠保證插入順序和渲染順序一致,下文代指收集中心),此爲訂閱;定義setVm函數,傳入key則會在map上取出對應的實例,並將$vm指向此實例,此爲發佈;(重點思路,單頁應用,也就意味着應用的單元爲「頁面」,也就是說咱們研究的是頁面組件及其對應的後臺組件)

  • 前置:mixin中定義setVmInstance,在生命週期中其this就是指向當前渲染的組件實例,調用setVmInstance存入vmMap`實現組件的訂閱
  • 訂閱:首先在beforeRouteEnter得到頁面組件實例,進行深度遞歸註冊,其中核心點是根據$children屬性獲取子組件
  • 發佈:組件的切換就是經過key從vmMap中獲取對應組件而後切換效果圖裏的$vm指向便可。
重構:支持多級路由及動態裝卸載組件

這是目前最新一版,思路在正文中,就不贅述啦

  • 存在問題
    • 強依賴elementUi待擴展爲能夠動態支持UI庫,提高擴展性(最近公司太忙了,下次必定,下次必定~)
  • 解決問題
    • 支持多級路由
    • 支持v-if致使的組件裝卸載

我的感觸

​ 最近感觸頗多,在上週一的日記看到這樣一段話

腦子裏忽然想到另外一句詩「念天地之悠悠,獨倉然而涕下」,換我如今,一天12個小時+的學或寫代碼,應該改爲「念代碼之悠悠」吧,但好像很搞笑的樣子。
複製代碼

​ 由於業務的龐大,第一次就獨立承接一個比較大的需求時不免疲於奔命,忙忙碌碌,但仍是想寫,想去優化,五一解決了須要在組件中註冊的問題,說不出的開心,搞笑的是昨晚作夢竟然都是同事用個人插件加載不出來而後我去排查的場景,有種看什麼東西長大的感受。

​ 每晚很晚走,但不是一我的,還有一個實習生,也是我室友,每次晚上看我都會說:「哎呀志遠,太捲了太捲了,別學了」,但每次都會和我一塊兒學到很晚。回去的時間大可能是深夜和凌晨的交接線,兩我的走在空無一人的大街上,月明星稀,會很肆無忌憚的打打鬧鬧,偶爾一兩輛小吃車,百無聊賴的老闆看到有人路過會很快的精神一下,發現不是顧客又會很快的百無聊賴,後來見得次數多了,擡頭低頭百無聊賴的狀態無縫銜接,但仍是幾乎每晚都能見到。

​ 老闆的堅持出攤,室友的堅持學習,咱們都在堅持着一些東西,無關辛苦,只是願意,記得高中的時候老師讓咱們學魯迅刻字,寫座右銘,當時寫的是:一念既定,萬山無阻。不是當初想像中的本身,或好或壞,但最起碼在必定程度上作到了些什麼。

尾聲

沒啥說的了,若使諸君稍有啓發,不枉此文心力^-^;

哦對了,還有一件事:八年室友結婚啦,新婚快樂!!!(小夥伴方便的話,star能夠不點,幫小菜去掘金留個言【清哥新婚快樂】吧~~~)

哦對了,還有一件事:微醫招聘,地標杭州,團隊大、大佬多、好看小姐姐多,速來(大佬們留言,我去撈人)

哦對了,還有一件事:能夠的話,輕點小手幫小弟點個star

哦對了,還有一件事:咱們家冰冰真好看

thread_214569626974086_20201111224348_s_363620_o_w_224_h_155_96920

相關連接

相關文章
相關標籤/搜索