日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍?

發布時間:2025/4/5 vue 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家都用過 Vue-CLI 創建 vue 應用,在開發的時候我們修改了 vue 文件,保存了文件,瀏覽器上就自動更新出我們寫的組件內容,非常的順滑流暢,大大提高了開發效率。想知道這背后是怎么實現的嗎,其實代碼并不復雜。

這個功能的實現底層用了vue-hot-load-api[1]這個庫,得益于 vue 的良好設計,熱更新的實現總共就一個 js 文件,200 行代碼,綽綽有余。

而在這個庫里涉及到的技巧又非常適合我們去深入了解 vue 內部的一些機制,所以趕快來和我一起學習吧。

提要

本文單純的從vue-hot-load-api這個庫出發,在瀏覽器的環境運行 Vue 的熱更新示例,主要測試的組件是普通的 vue 組件而不是 functional 等特殊組件,以最簡單的流程搞懂熱更新的原理。
在源碼解析中貼出的代碼會省略掉一些不太相關的流程,更便于理解。

解析

從 github 倉庫示例入手

進入了這個 github 倉庫以后,最先開始看的肯定是 Readme 的里的示例,在看示例的時候作者給出的注釋就非常重要了,他會標注出每一個重要的環節。并且我們要結合自己的一些經驗排除掉和這個庫無關的代碼。(在這個示例中,webpack 的相關代碼就可以先不去過多關注)

第一步需要調用install方法,傳入 Vue 構造函數,根據注釋來看,這一步是要知道這個庫與 Vue 版本之間是否兼容。

//?make?the?API?aware?of?the?Vue?that?you?are?using.
//?also?checks?compatibility.
api.install(Vue);

接下來的這段注釋告訴我們,每個需要熱更新的組件選項對象,我們都需要為它建立一個獨一無二的 id,并且這段代碼需要在初始化的時候完成。

if?(初始化)?{
??//?for?each?component?option?object?to?be?hot-reloaded,
??//?you?need?to?create?a?record?for?it?with?a?unique?id.
??//?do?this?once?on?startup.
??api.createRecord('very-unique-id',?myComponentOptions);
}

最后就是激動人心的熱更新時間了,
根據注釋來看,這個庫的使用分為兩種情況。

  • rerender 只有 template 或者 render 函改變的情況下使用。
  • reload 如果 template 或者 render 未改變,則這個函數需要調用 reload 方法先銷毀然后重新創建(包括它的子組件)。
//?if?a?component?has?only?its?template?or?render?function?changed,
//?you?can?force?a?re-render?for?all?its?active?instances?without
//?destroying/re-creating?them.?This?keeps?all?current?app?state?intact.
api.rerender('very-unique-id',?myComponentOptions);

//?---?OR?---

//?if?a?component?has?non-template/render?options?changed,
//?it?needs?to?be?fully?reloaded.?This?will?destroy?and?re-create?all?its
//?active?instances?(and?their?children).
api.reload('very-unique-id',?myComponentOptions);

從這個簡單的示例里面可以看出,這個庫的核心流程就是:

  • api.install 檢測兼容性。
  • api.createRecord 為組件對象通過一個獨一無二的 id 建立一個記錄。
  • api.rerender 或api.reload 進行組件的熱更新。
  • 什么,Readme 的示例到此就結束了?這個 very-unique-id 到底是個什么東西,myComponentOptions 又是什么樣的。

    因為這個倉庫可能并不是面向廣大開發者的,所以它的文檔寫的非常的簡略。其實看完了這個簡短的示例,大家肯定還是一臉懵逼的。

    在看一個你沒有熟練使用的庫的源碼的時候,其實還有一個很關鍵的步驟,那就是看測試用例。

    探索測試用例

    測試用例[2]

    上面我們總結出兩個關鍵 api rerender 和 reload 之后,就帶著目的性的去看測試用例。

    const?Vue?=?require('vue');
    const?api?=?require('../src');

    //?初始化
    api.install(Vue);

    //?這個方法接受id和組件選項對象,
    //?通過createRecord去記錄組件
    //?然后返回一個vue組件實例。
    function?prepare(id,?Comp)?{
    ??api.createRecord(id,?Comp);
    ??return?new?Vue({
    ????render:?h?=>?h(Comp),
    ??});
    }

    rerender 用例

    const?id0?=?'rerender:?mounted';
    test(id0,?done?=>?{
    ??//?用'rerender:?mounted'作為這個組件對象的id,
    ??//?這個組件的內容應該是?foo
    ??//?調用?$mount生成dom節點
    ??const?app?=?prepare(id0,?{
    ????render:?h?=>?h('div',?'foo'),
    ??}).$mount();

    ??//?$el就是組件生成的dom元素,期望textContent文字內容為foo
    ??expect(app.$el.textContent).toBe('foo');

    ??//?rerender?后dom節點變成?bar
    ??api.rerender(id0,?{
    ????render:?h?=>?h('div',?'bar'),
    ??});

    ??//?通過nextTick保證dom節點已經更新
    ??//?期望textContent文字內容為bar
    ??Vue.nextTick(()?=>?{
    ????expect(app.$el.textContent).toBe('bar');
    ????done();
    ??});
    });

    reload 用例

    const?id1?=?'reload:?mounted';
    test(id1,?done?=>?{
    ??//?通過一個count來計數
    ??let?count?=?0;

    ??//?app組件會在created的時候讓count?+?1
    ??//?destroyed的時候讓count?-?1
    ??const?app?=?prepare(id1,?{
    ????created()?{
    ??????count++;
    ????},
    ????destroyed()?{
    ??????count--;
    ????},
    ????data:?()?=>?({?msg:?'foo'?}),
    ????render(h)?{
    ??????return?h('div',?this.msg);
    ????},
    ??}).$mount();
    ??//?確保內容正確
    ??expect(app.$el.textContent).toBe('foo');
    ??//?確保created周期執行?此時的count是1
    ??expect(count).toBe(1);

    ??//?調用created?傳入新組件的created時?count會-1
    ??api.reload(id1,?{
    ????created()?{
    ??????count--;
    ????},
    ????data:?()?=>?({?msg:?'bar'?}),
    ????render(h)?{
    ??????return?h('div',?this.msg);
    ????},
    ??});

    ??Vue.nextTick(()?=>?{
    ????//?確保內容正確
    ????expect(app.$el.textContent).toBe('bar');
    ????//?在reload之前?count是1
    ????//?調用reload之后?會先調用前一個組件的destory生命周期?此時count是0
    ????//?接下來調用新組建的created生命周期?此時count是-1
    ????expect(count).toBe(-1);
    ????done();
    ??});
    });

    具體流程已經在注釋里分析了,果然和示例代碼的注釋里寫的一樣,而且現在我們也更清楚這個 api 到底該怎么用了。

    總結一個最簡單的可用 demo

    import?api?from?'vue-hot-reload-api';
    import?Vue?from?'vue';

    //?初始化
    api.install(Vue,?true);

    const?appOptions?=?{
    ??render:?h?=>?h('div',?'foo'),
    };

    api.createRecord('my-app',?appOptions);

    new?Vue(appOptions).$mount('#app');

    setTimeout(()?=>?{
    ??api.rerender('my-app',?{
    ????render:?h?=>?h('div',?'bar'),
    ??});
    },?2000);

    這個 demo(源碼[3])是直接在瀏覽器可用的,效果如下:

    源碼分析

    源碼地址[4]

    全局變量

    進入 js 文件的入口,首先定義了一些變量

    //?Vue構造函數
    let?Vue;?//?late?bind
    //?Vue版本
    let?version;
    //?createRecord方法保存id?->?組件映射關系的對象
    const?map?=?Object.create(null);
    if?(typeof?window?!==?'undefined')?{
    ??//?將map對象存儲在window上
    ??window.__VUE_HOT_MAP__?=?map;
    }
    //?是否已經安裝過
    let?installed?=?false;
    //?這個變量暫時沒用
    let?isBrowserify?=?false;
    //?初始化生命周期的名字?默認是Vue的beforeCreate生命周期
    let?initHookName?=?'beforeCreate';

    其實看到 window 對象的出現,我們就已經可以確定這個 api 可以在瀏覽器端調用。

    install

    exports.install?=?function(vue,?browserify)?{
    ??//?如果安裝過了就不再重復安裝
    ??if?(installed)?{
    ????return;
    ??}
    ??installed?=?true;

    ??//?兼容es?modules模塊
    ??Vue?=?vue.__esModule???vue.default?:?vue;
    ??//?把vue的版本如2.6.3分隔成[2,?6,?3]?這樣的數組
    ??version?=?Vue.version.split('.').map(Number);
    ??isBrowserify?=?browserify;

    ??//?compat?with?//?兼容2.0.0-alpha.7以下版本if?(Vue.config._lifecycleHooks.indexOf('init')?>?-1)?{
    ????initHookName?=?'init';
    ??}//?只有Vue在2.0以上的版本才支持這個庫。
    ??exports.compatible?=?version[0]?>=?2;if?(!exports.compatible)?{console.warn('[HMR]?You?are?using?a?version?of?vue-hot-reload-api?that?is?'?+'only?compatible?with?Vue.js?core?^2.0.0.'
    ????);return;
    ??}
    };

    可以看出 install 方法很簡單,就是幫你看一下 Vue 的版本是否在 2.0 以上,確認一下兼容性,關于初始化生命周期,在這篇文章里我們就不考慮 2.0.0-alpha.7 以下版本,可以認為這個庫的初始化工作就是在 beforeCreate 這個生命周期進行。

    createRecord

    /**
    ?*?Create?a?record?for?a?hot?module,?which?keeps?track?of?its?constructor
    ?*?and?instances
    ?*
    ?*?@param?{String}?id
    ?*?@param?{Object}?options
    ?*/

    exports.createRecord?=?function(id,?options)?{
    ??//?如果已經存儲過了就return
    ??if?(map[id])?{
    ????return;
    ??}

    ??//?關鍵流程?下一步解析
    ??makeOptionsHot(id,?options);

    ??//?將記錄存儲在map中
    ??//?instances變量應該不難猜出是vue的實例對象。
    ??map[id]?=?{
    ????options:?options,
    ????instances:?[],
    ??};
    };

    這一步在把 id 和對應的 options 對象存進 map 后,就沒做啥了,關鍵步驟肯定在于makeOptionsHot這個方法。

    /**
    ?*?Make?a?Component?options?object?hot.
    ?*?讓一個組件對象變得性感...哦不,是支持熱更新。
    ?*
    ?*?@param?{String}?id
    ?*?@param?{Object}?options
    ?*/

    function?makeOptionsHot(id,?options)?{
    ??//?options?就是我們傳入的組件對象
    ??//?initHookName?就是'beforeCreate'
    ??injectHook(options,?initHookName,?function()?{
    ????//?這個函數會在beforeCreate聲明周期執行
    ????const?record?=?map[id];
    ????if?(!record.Ctor)?{
    ??????//?此時this已經是vue的實例對象了
    ??????//?把組件實例的構造函數賦值給record的Ctor屬性。
    ??????record.Ctor?=?this.constructor;
    ????}
    ????//?在instances里存儲這個實例。
    ????record.instances.push(this);
    ??});
    ??//?在組件銷毀的時候把上面存儲的instance刪除掉。
    ??injectHook(options,?'beforeDestroy',?function()?{
    ????const?instances?=?map[id].instances;
    ????instances.splice(instances.indexOf(this),?1);
    ??});
    }

    //?往生命周期里注入某個方法
    function?injectHook(options,?name,?hook)?{
    ??const?existing?=?options[name];
    ??options[name]?=?existing
    ??????Array.isArray(existing)
    ????????existing.concat(hook)
    ??????:?[existing,?hook]
    ????:?[hook];
    }

    看完了這幾個函數以后,我們對 createRecord 應該有個清晰的認識了。
    比如上面我們的例子中這段代碼

    const?appOptions?=?{
    ??render:?h?=>?h('div',?'foo'),
    };

    api.createRecord('my-app',?appOptions);
  • 在 map 中創建一個記錄,這個記錄有options字段也就是上面傳入的組件對象,還有instances用于記錄活動組件的實例,Ctor用來記錄組件的構造函數。
  • //?map
    {
    ????my-app:?{
    ????????options:?appOptions,
    ????????instances:?[],
    ????????Ctor:?null
    ????}
    }
  • 在 appOptions 中,混入生命周期方法 beforeCreate,在組件的這個生命周期中,把組件自身的示例 push 到 map 里對應 instances 數組中,并且記錄自己的構造函數在 Ctor 字段上。 beforeCreate 執行完了以后的 map 對象長這樣。
  • 接下來進入關鍵的 rerender 函數。

    rerender

    exports.rerender?=?(id,?options)?=>?{
    ??const?record?=?map[id];
    ??if?(!options)?{
    ????//?如果沒傳第二個參數?就把所有實例調用?$forceUpdate
    ????record.instances.slice().forEach(instance?=>?{
    ??????instance.$forceUpdate();
    ????});
    ????return;
    ??}
    ??record.instances.slice().forEach(instance?=>?{
    ????//?將實例上的?$options上的render直接替換為新傳入的render函數
    ????instance.$options.render?=?options.render;
    ????//?執行?$forceUpdate更新視圖
    ????instance.$forceUpdate();
    ??});
    };

    其實這個原函數很長,但是簡化以后核心的更改視圖的方法就是這些,平常我們在寫 vue 單文件組件的時候都會像下面這樣寫:

    {{?msg?}}


    這樣的.vue 文件,會被 vue-loader 編譯成單個的組件選項對象,template 中的部分會被編譯成 render 函數掛到組件上,最終生成的對象是類似于:

    export?default?{
    ??data()?{
    ????return?{
    ??????msg:?'Hello?World',
    ????};
    ??},
    ??render(h)?{
    ????return?h('span',?this.msg);
    ??},
    };

    而在運行時,組件實例(也就是生命周期或者 methods 中訪問的 this 對象)會通過option.render 去實現的。我們可以去 vue 的源碼里驗證一下我們的猜想。

    _render

    而在options.render 給替換成新的 render 方法了,這個時候再調用$forceUpdate,不就渲染新傳入的 render 了嗎?這個運行時的偷天換日我不得不佩服~

    reload

    reload 的講解我們基于這樣一個示例:
    一開始會顯示 foo 的文本,一秒以后會顯示成 bar。

    function?prepare(id,?Comp)?{
    ??api.createRecord(id,?Comp);
    ??return?new?Vue({
    ????render:?h?=>?h(Comp),
    ??});
    }

    const?id1?=?'reload:?mounted';
    const?app?=?prepare(id1,?{
    ??data:?()?=>?({?msg:?'foo'?}),
    ??render(h)?{
    ????return?h('div',?this.msg);
    ??},
    }).$mount('#app');

    //?reload
    setTimeout(()?=>?{
    ??api.reload(id1,?{
    ????data:?()?=>?({?msg:?'bar'?}),
    ????render(h)?{
    ??????return?h('div',?this.msg);
    ????},
    ??});
    },?1000);

    reload 的情況會更加復雜,涉及到很多 Vue 內部的運行原理,這里只能簡單的描述一下。

    exports.reload?=?function(id,?options)?{
    ??const?record?=?map[id];
    ??if?(options)?{
    ????//?reload的情況下?傳入的options會當做一個新的組件
    ????//?所以要用makeOptionsHot重新做一下記錄
    ????makeOptionsHot(id,?options);
    ????const?newCtor?=?record.Ctor.super.extend(options);

    ????newCtor.options._Ctor?=?record.options._Ctor;
    ????record.Ctor.options?=?newCtor.options;
    ????record.Ctor.cid?=?newCtor.cid;
    ????record.Ctor.prototype?=?newCtor.prototype;
    ??}
    ??record.instances.slice().forEach(function(instance)?{
    ????instance.$vnode.context.$forceUpdate();
    ??});
    };

    這段代碼關鍵的點開始于

    const?newCtor?=?record.Ctor.super.extend(options);

    利用新傳入的配置生成了一個新的組件構造函數 然后對 record 上的 Ctor 進行了一系列的賦值

    newCtor.options._Ctor?=?record.options._Ctor;
    record.Ctor.options?=?newCtor.options;
    record.Ctor.cid?=?newCtor.cid;
    record.Ctor.prototype?=?newCtor.prototype;

    注意第一次調用 reload 時,這里的 record.Ctor 還是最初傳入的 Ctor,是由

    const?app?=?prepare(id1,?{
    ??data:?()?=>?({?msg:?'foo'?}),
    ??render(h)?{
    ????return?h('div',?this.msg);
    ??},
    }).$mount('#app');

    這個配置對象所生成的構造函數,但是構造函數的 options、cid 和 prototype 被替換成了由

    api.reload(id1,?{
    ??data:?()?=>?({?msg:?'bar'?}),
    ??render(h)?{
    ????return?h('div',?this.msg);
    ??},
    });

    這個配置對象所生成的構造函數上的 options、cid 和 prototype,此時的 cid 肯定是不同的。

    也就是說,構造函數的 cid 變了!,這個點記住后面要考!

    繼續看源碼

    record.instances.slice().forEach(function(instance)?{
    ??instance.$vnode.context.$forceUpdate();
    });

    此時的 instance 只有一個,就是在 reload 之前運行的那個 msg 為 foo 的實例,它的$vnode.context 是什么呢?
    直接在放上控制臺打印出來的截圖,這個 context 是一個 vue 實例,注意這個 options 里的 render 函數,是不是非常眼熟,沒錯,這個 vue 實例其實就是我們的 prepare 函數中

    new?Vue({
    ??render:?h?=>?h(Comp),
    });

    返回的 vm 實例。

    那么這個函數的 $forceUpdate必然會觸發 render: h => h(Comp) 這個函數,看到此時我們似乎還是沒看出來這些操作為什么會銷毀舊組件,創建新組件。那么此時只能探究一下這個h到底做了什么,這個h就是對應著 $createElement 方法。

    $createElement方法

    $createElement 在創建 vnode 的時候,最底層會調用一個 createComponent 方法,

    這個方法把 Comp 對象當做 Ctor,然后調用 Vue.extend 這個 api 創造出構造函數,

    默認情況下第一次 h(Comp) 會生成類似于 vue-component-${cid}作為組件的 tag,

    在本例中最開始渲染 msg 為 foo 的組件時,tag 為 vue-component-1,

    并且會把這個構造函數緩存在_Ctor 這個變量上,這樣下次渲染再執行到 createComponent 的時候就不需要重新生成一次構造函數了,

    Vue 在選擇更新策略時調用一個sameVnode方法

    來決定是要進行打補丁,還是徹底銷毀重建,這個sameVnode如下:

    function?sameVnode(a,?b)?{
    ??return?(
    ????//?省略其他...
    ????a.tag?===?b.tag
    ??);
    }

    其中很關鍵的一個對比就是a.tag === b.tag

    但是 reload 方法偷梁換柱把 Ctor 的 cid 換成了 2,

    生成的 vnode 的 tag 是就 vue-component-2

    后續再調用 context.$forceUpdate 的時候,會發現兩個組件的 tag 不一樣,所以就走了銷毀 -> 重新創建的流程。

    總結

    這個庫里面還是能看出很多尤大的編程風格,很適合進行學習,只是 reload 方法必須要深入了解 Vue 源碼才有可能搞懂生效的原理。

    可以看出來 Vue 的很多第三方庫是利用 Vue 內部提供的一些機制,甚至是只有了解源碼細節才能想到的一些 hack 的方式去實現的,所以如果想更加深入的玩好 Vue,源碼是有必要去學習的,在學習 Vue 源碼的過程中會被尤大的代碼規范,還有一些精妙的設計所折服,肯定會有很大的收獲。

    rerender這個方法相對來說還是比較好理解的,但是reload方法是怎么生效的就非常讓人難以理解了,我一步一步斷點調試了大概六七個小時,才漸漸得出結論,只能說好用的 api 后面潛藏著作者用心良苦的設計啊!

    參考資料

    [1]

    vue-hot-load-api: https://github.com/vuejs/vue-hot-reload-api

    [2]

    測試用例: https://github.com/vuejs/vue-hot-reload-api/blob/master/test/test.js

    [3]

    源碼: https://github.com/sl1673495/vue-hot-reload-demo

    [4]

    源碼地址: https://github.com/vuejs/vue-hot-reload-api/blob/master/src/index.js

    [5]

    Vue.js 源碼全方位深入解析 (含 Vue3.0 源碼分析): https://coding.imooc.com/class/228.html

    相關推薦

    用Js實現人臉識別功能

    JavaScript 啟動性能瓶頸分析與解決方案

    從零看清Node源碼createServer和負載均衡整個過程

    【項目實戰】sass使用進階篇(下)

    【項目實戰】sass使用基礎篇(上)

    最詳細的從零開始配置 TypeScript 項目的教程

    5 款非常好用的開源 Docker 工具

    WebSocket 全面知識補全

    7個處理JavaScript值為undefined的技巧

    immutablejs 是如何優化我們的代碼的?

    Chrome 新功能嘗鮮!— CSS Overview

    又一個布局利器, CSS 偽類 :placeholder-shown

    封裝一個vue視頻播放器組件

    對于組件的可重用性,大佬給出來6個建議

    學習 TS 不要錯過的八個工具

    Node 中的全鏈路式日志標記及處理

    使用 Node 開發服務器項目時如何高效地打日志?

    用TypeScript學設計模式(享元模式)

    用TypeScript學設計模式(模板方法模式)

    TypeScript 設計模式之適配器模式

    用TypeScript學設計模式(觀察者模式)

    用TypeScript學設計模式(單例模式)

    點在看的人特別帥/美 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。