Vue – 基礎學習(1):對生命周期和鉤子函的理解

来源:https://www.cnblogs.com/donghuang/archive/2019/05/09/10840584.html
-Advertisement-
Play Games

一、簡介 先貼一下官網對生命周期/鉤子函數的說明(先貼為敬):所有的生命周期鉤子自動綁定 this 上下文到實例中,因此你可以訪問數據,對屬性和方法進行運算。這意味著你不能使用箭頭函數來定義一個生命周期方法 (例如 created: () => this.fetchTodos())。這是因為箭頭函數 ...


一、簡介

  先貼一下官網對生命周期/鉤子函數的說明(先貼為敬):所有的生命周期鉤子自動綁定 this 上下文到實例中,因此你可以訪問數據,對屬性和方法進行運算。這意味著你不能使用箭頭函數來定義一個生命周期方法 (例如 created: () => this.fetchTodos())。這是因為箭頭函數綁定了父上下文,因此 this 與你期待的 Vue 實例不同,this.fetchTodos 的行為未定義。

  上面是官方文檔對生命周期/鉤子函數的總覽介紹。如果單看這個總覽介紹,絕對是一頭霧水,不清不楚看不出個所以然。雖然文檔後面對各個鉤子函數的使用有具體說明,但具體實例卻不是很清楚,所以在玩了一段時間的Vue項目後,閑來打算自己總結下生命周期和鉤子函數的使用。下麵先來一張官方生命周期圖示:

  

  生命周期:描述Vue實例或組件從創建到銷毀(包括銷毀前和銷毀)的全部經歷和過程。就像人一樣,從母親懷胎開始,然後出生,成長,衰老,一直到迴光返照(銷毀前),最後死去一把火(銷毀)回歸大自然,著重是介紹一種經歷和過程

  鉤子函數:鉤子函數則是Vue實例或組件在生命周期過程中各個階段自執行的回調函數。就如同新生兒出生後,餓了他會哭,上學途中被高年級學生欺負了會找家長告狀,長大了要出去掙錢養家,老了會戴老花鏡一樣。在不同的階段Vue實例或組件內部,結構也在發生著變化,隨著節點結構的變化就需要執行一些特定的鉤子函數,去繼續下一步變化和新節點的建立,也正是這些鉤子函數的執行為實際開發過程中能夠添加自定義功能提供了入口。

 

  下麵結合官方文檔先對各個鉤子函數做一個簡略的總結。

 

二、代碼實測

   各個鉤子函數的執行位置以及執行時間點,在上面的官方生命周期圖示中已經標註得很清楚,下麵通過代碼實測來逐個加深認識。測試代碼如下:

<!DOCTYPE html>
<html>
<head>
    <title>Vue – 基礎學習(1):對生命周期和鉤子函的理解</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
    <div>靜態元素</div>
    <div style="margin-top: 5px">{{ testInfor }}</div>
    <div style="margin-top: 20px">
        <button @click.stop="editTestInfor">更新內容</button>
        <button @click.stop="destroyedNode">銷毀實例</button>
    </div>
</div>

<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.5.20/vue.min.js"></script>
<script type="text/javascript">
    new Vue({
        el: '#app',
        data() {
            return {
                testInfor: '測試信息!',
            };
        },
        beforeCreate() {
            console.group('beforeCreate:實例創建完成,但數據對象data、屬性、event/watcher事件均未完成配置和初始化。掛載階段還未開始,$el屬性未初始化,$el元素不可見========》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // undefined
            console.log(this.$el);                                                                  // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // undefined
            this.testFuntion('beforeCreate');                                                       // undefined    this.testFuntion is not a function
            debugger;
        },
        created() {
            console.group('created:實例數據對象data、屬性、event/watcher事件均配置和初始化完成。但掛載階段還未開始,$el屬性未初始化,$el元素不可見=========================》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // undefined
            console.log(this.$el);                                                                  // undefined
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 初始化完成
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 初始化完成
            this.testFuntion('created');                                                            // event事件初始化完成
            debugger;
        },
        beforeMount() {
            console.group('beforeMount:在開始掛載之前被調用,相關的render函數首次被調用。$el屬性初始化完成,但處於虛擬dom狀態,具體的data.filter尚未替換,$el元素可見=======》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // $el屬性初始化完成
            console.log(this.$el);                                                                  // 節點掛載完成,但數據尚未渲染,處於虛擬DOM狀態
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 已被初始化
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 已被初始化
            this.testFuntion('beforeMount');                                                        // event事件已被初始化
            debugger;
        },
        mounted() {
            console.group('mounted:掛載完成,data.filter成功渲染,頁面整體渲染完成,可進行DOM操作===================》');
            console.log(this);                                                                      // object
            console.log('%c%s', 'color:red', 'el     : ' + this.$el);                               // $el屬性已被初始化
            console.log(this.$el);                                                                  // 節點掛載完成,數據渲染成功,頁面全部渲染完成
            console.log('%c%s', 'color:red', 'data   : ' + this.$data);                             // 已被初始化
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 已被初始化
            this.testFuntion('mounted');                                                            // event事件已被初始化
            debugger;
        },
        beforeUpdate() {
            console.group('beforeUpdate:頁面依賴的參數數據更改之後,DOM結構重新渲染之前觸發執行(此時DOM結構還沒有重新渲染)=============》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 點擊按鈕調用方法更新後的數據
            this.testFuntion('beforeUpdate');
            this.testInfor = 'beforeUpdate修改後的信息!';                                           // 在beforeUpdate函數內再次修改頁面依賴參數數據
            console.log('%c%s', 'color:blue', 'data   : ' + this.testInfor);                        // 在beforeUpdate函數內修改後的數據
            debugger;
        },
        updated() {
            console.group('updated:數據更新完成=====================================================================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 最終更新後數據
            this.testFuntion('updated');
            debugger;
        },
        beforeDestroy() {
            console.group('beforeDestroy:實例或組件銷毀之前調用,在這一步,實例或組件仍然完全可用===================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);
            this.testFuntion('beforeDestroy');                                                      // 此時實例內功能函數功能依然正常
            this.testInfor = 'beforeDestroy修改後的信息!';                                          // 在beforeDestroy函數內再次修改頁面依賴參數數據,用以驗證beforeUpdate和updated函數是否還監聽執行
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 在beforeDestroy函數內修改後的數據
            debugger;
            // 此時實例、組件雖然頁面結構完整,各種功能正常。但,頁面依賴參數更新後生命周期函數beforeUpdate和updated均不再執行,說明實例或組件的銷毀一旦啟動則不可逆轉或中途打斷。
        },
        destroyed() {
            console.group('destroyed:實例或組件已被銷毀=============================================================》');
            console.log(this.$el);
            console.log('%c%s', 'color:red', 'data   : ' + this.testInfor);                         // 在beforeDestroy函數內修改的頁面依賴參數,依然能正確讀取
            this.testFuntion('destroyed');                                                          // 此時實例內功能函數功能依然正常
            debugger;
            // 此時雖然"beforeDestroy"執行完畢,但實例指向的所有東西(參數,方法等)尚未解綁。所以此時實例內各參數、方法功能依然正常。等待"destroyed"執行完畢後,所有的東西才會解綁,塵歸塵,土歸土。
        },
        methods: {
            testFuntion(type) {
                console.log('當前運行鉤子函數:' + type);
            },

            editTestInfor() {
                this.testInfor = '修改後的信息!';
            },

            destroyedNode() {
                this.$destroy();
            }
        }
    });
</script>
</body>
</html>

  1. beforeCreate 和created

    beforeCreate:實例創建完成,但數據對象data、屬性、event/watcher事件均未完成配置和初始化。掛載階段未開始,$el屬性尚未初始化,$el屬性不可見,$el元素不可見。

     

    created:實例數據對象data、屬性、event/watcher事件均配置和初始化完成。但掛載階段尚未開始,$el屬性未初始化,$el屬性不可見,$el元素不可見。

     

    小結:雖然此時$el屬性尚未初始化,頁面元素不可見,但數據對象data、屬性、event/watcher事件均已配置和初始化完成,所以一些需要先頁面執行的方法(如ajax請求,頁面功能許可權檢測(頁面是否能載入、頁面依賴參數是否合法)和配置(如按鈕點擊許可權等))在created階段可以執行,但不允許操作DOM節點和調用操作DOM節點的方法(頁面整體結構未渲染完成)。

 

  2. beforeMount和mounted

    beforeMount:在開始掛載之前被調用,相關的render函數首次被調用。$el屬性初始化完成,$el屬性可見,el元素可見。但此時el節點並沒有渲染進數據,el節點尚處於“虛擬”節點狀態,可看到還是取值表達式{{testInfor }}。這就是Virtual DOM(虛擬Dom)的巧妙之處,先占坑,然後到mounted掛載階段時再渲染值。

    

    mounted :節點掛載完成,數據成功渲染,頁面整體渲染完成,實例或組件完全成熟,可進行DOM操作。

       

 

  3. beforeDestroy 和 destroy

    人生看似很漫長,但在不經意之間就走向了她的終點。Vue實例或組件也一樣,在經歷了多姿多彩的絢爛時光後,它也逐漸走向了它生命的終點。這裡提一下為啥先不說 beforeUpdate 和 updated 而是直接跳到 beforeDestroy destroy,因為 beforeUpdate updated 不是生命周期過程中必須執行的鉤子函數。beforeUpdate updated 是基於組件內數據發生變化時觸發執行,如果當期實例或組件內數據只是進行顯示,不進行任何修改,那麼這兩個鉤子函數將一直不會被觸發,也就不會被執行。

    beforeDestroy:實例或組件銷毀之前調用,在這一步,實例或組件內各參數,方法功能依然完整,實例仍完全可用

     

    destroy:Vue實例或組件銷毀後調用。調用後,Vue 實例指示的所有東西都會自動解綁,所有的事件監聽器會被移除,所有的子實例也會被銷毀。

    

    實例或組件銷毀完成後,再次點擊“更新內容”按鈕,此時系統不再做任何響應,但,已渲染完成的Dom結構和節點元素依然存在,所以當執行完destroy操作後,實例或組件就不再受Vue系統控制。此時Vue實例或該子組件已經不存在了,實例或組件內的各參數,屬性,方法均已被記憶體回收清空。

    小結:beforeDestroy階段,此時實例、組件雖然頁面結構完整,各種功能正常,但,頁面依賴參數更新後生命周期函數beforeUpdate和updated均不再執行說明實例或組件的銷毀過程一旦啟動則不可逆轉或中途打斷

    destroy階段,此時雖然"beforeDestroy"執行完畢,但實例指向的所有東西(參數,方法等)尚未解綁。所以此時實例內各參數、方法功能依然正常。等待"destroyed"執行完畢後,所有的東西才會被解綁,資源被回收。塵歸塵,土歸土,從哪來回哪去!

    到此為止,Vue實例或組件從開始初始化到最終銷毀,數據清空的六個鉤子函數均測試完畢。這六個鉤子函數是實例或組件生命周期歷程中最主要的六個鉤子函數,也是必須執行的六個函數,無法繞過

 

  4. beforeUpdate 和 updated

    現在,返回來看beforeUpdate 和 updated。Vue實例或組件在掛載完成後就標志著功能健全,功能健全的組件就如成年的人生一樣豐富多彩,每時每刻都可能發生變化。接下來通過修改testInfor的值,來看看beforeUpdate和updated都各自做了什麼。

點擊頁面“更新內容”按鈕,修改testInfor的值。

    beforeUpdate:頁面依賴的參數數據更改之後,虛擬DOM重新渲染和打補丁之前執行(此時DOM結構還未重新渲染)。

     

    updated:頁面依賴的參數數據更改之後,beforeUpdate鉤子函數執行完畢,會立即進行DOM結構的重新渲染。DOM結構渲染完成之後才會調用updated鉤子函數,而不是渲染時就調用

     

    

    此圖就可以完全看出,調用updated時組件DOM結構已重新渲染完成,所以此時updated函數內是可以進行相關DOM操作的。

 

    小結:在debugger beforeUpdate鉤子函數時發現一個小細節,既然beforeUpdate是在頁面依賴數據修改之後,虛擬DOM重新渲染之前執行,那麼我在beforeUpdate函數內,是可以對依賴數據進行再次修改的,而不會導致多重渲染,也不會多次調用updated函數。

     

    

    從上兩圖可以看出,即使在beforeUpdate函數內修改無數次頁面依賴參數數據,組件Dom結構也只會重新渲染一次,即 將最後修改的依賴參數數據渲染到對應節點,updated函數也只會執行一次。只是這樣做沒有多大實際意義,畢竟其他地方調用其他方法更新後的數據,是頁面功能需求的數據,在beforeUpdate這又瞎改一通,於功能於系統毫無益處。當然你膽肥不怕死,整一些惡搞和亂操作還是可以玩的。

    雖然beforeUpdate和updated是基於頁面依賴參數數據更改後觸發和執行,對於頁面依賴參數的變化可以起到監控作用,以及在參數變化之後執行其他後續操作,但,它們無法判定是哪個參數發生了變化。雖然每次參數數據變化之後可以通過比較各個參數值的前後值是否相等來判定是哪個參數發生了變化,但,那是基於參數量少,參數數據類型是基本數據類型的情況。一旦需要監控的參數量大,參數數據類型複雜,beforeUpdate和updated就將變得很難處理。所以實際開發過程中,除非一些特別的參數和操作,絕大部分參數的更新監聽和後續操作,都是使用watch對象進行監聽,因而在實際開發過程中beforeUpdate和updated使用得相當少。

    另外Vue是數據驅動頁面刷新,所以必然是在數據更新之後系統才會驅動虛擬DOM View層的刷新,因而beforeUpdate必然是在參數數據更新之後View(視圖)層數據(節點內數據)更新之前觸發

 

三、總結

  總體而言,生命周期函數雖然有這麼多個,但實際開發過程中使用最頻繁的也就那麼幾個,如:created,mounted,beforeDestory,destoryed。開發人員可以:

  在created內進行:頁面是否載入 許可權判定或頁面依賴參數初始化(如按鈕許可權配置)、ajax數據請求、自執行函數調用等操作。

  在mounted內進行:數據過濾、數據渲染賦值(如下拉框選項賦值)、DOM節點操作等功能。

  在beforeDestroy內進行:參數判定,確定當前頁面是否允許切換或刷新、必要數據緩存,操作記錄上傳等操作。

  在destoryed內進行:清除當前頁面其他緩存數據,如sessionStorage、定時器等。

  而其他生命周期函數,並不是說它們就不重要,只是它們在平常的開發過程中,使用得不是那麼頻繁而已。它們也有它們自己獨特的用處和用法,所以對於生命周期和生命周期函數的善加利用,可以讓實際開發事半功倍,並收到良好的效果。 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • html 包含兩大部分:head 和 body ; head 部分主要用來放置文檔的頭部,關鍵詞,描述,引入一些外部文件等; body 部分:所有要顯示在瀏覽器中的網頁內容和內容標簽都放在body部分。 ...
  • 使用方法: 調用initMessagebox(“要顯示的文字”)方法即可 用到的css ...
  • 去除jquery-easui tab頁div自帶滾動條 by:授客 QQ:1033553122 測試環境 jquery-easyui-1.5.3 需求場景 打開tab頁面時,自動載入一個iframe頁面,除了iframe頁面本身會出現一個滾動條,tab標簽頁也出現一個滾動條,如下圖所示,需求就是去掉 ...
  • 概念: 2D 動畫要是使用 transform 屬性來實現文字或圖像的的各種變形效果,如位移、縮放、旋轉、傾斜等 transform屬性變形方法: translate():位移 將元素沿著水平方向(X軸)和垂直方向(Y軸)移動 translateX(x):元素僅在水平方向移動(X軸移動) trans ...
  • JS基本概念 JS操作符 JS基本語法 JS數組 JS Date用法 JS 字元串用法 JS編程風格 JS實踐 ...
  • 伺服器端字體 在CSS3中可以使用@font-face屬性來利用伺服器端字體。 @font-face 屬性的使用方法: font-family屬性值中使用webfont來聲明使用的是伺服器端字體 src屬性值中首先指定了字體文件所在的路徑 format聲明字體文件的格式,可以省略文件格式的聲明,單獨 ...
  • 小程式生命周期函數 APP: onlauch:啟動時最先觸發,且全局僅觸發一次! onshow:程式啟動後或者小程式由前臺 後臺觸發 onhide:後臺 前臺觸發 onerroe:報錯 page: onload:文檔載入的時候執行1 onshow: 頁面顯示的時候執行2 onready:頁面除此渲染 ...
  • 想自己用 js寫一個原生的ajax請求,訪問本地文件,json/txt。但是demo,寫了一個後,發現 原來是跨域了。 js 寫的原生ajax 請求代碼如下 html代碼 將獲取的txt 文件 展示出來 通過 AJAX 改變內容 js 代碼 function loadX (){ / 老版本的 Int ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...