Vue.js 源碼分析(十七) 指令篇 v-if、v-else-if和v-else 指令詳解

来源:https://www.cnblogs.com/greatdesert/archive/2019/07/03/11127935.html
-Advertisement-
Play Games

v-if 指令用於條件性地渲染一塊內容。這塊內容只會在指令的表達式返回 truthy 值的時候被渲染。 v-else-if,顧名思義,充當 v-if 的“else-if 塊”,可以連續使用: 也可以使用 v-else 指令來表示 v-if 的“else 塊”: 挺好理解的,就和大多數的語言的if() ...


v-if 指令用於條件性地渲染一塊內容。這塊內容只會在指令的表達式返回true值的時候被渲染。

v-else-if,顧名思義,充當 v-if 的“else-if 塊”,可以連續使用:

也可以使用 v-else 指令來表示 v-if 的“else 塊”:

挺好理解的,就和大多數的語言的if()....else if()...else邏輯語句是一樣的,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body>
    <script>
        Vue.config.productionTip=false;
        Vue.config.devtools=false;
    </script>
    <div id="app">
        <p v-if="no<0">n小於0</p>
        <p v-else-if="no==0">no等於0</p>
        <p v-else>no大於0</p>
    </div>
    <script>var app = new Vue({el:'#app',data:{no:2}})</script>
</body>
</html>

渲染為:

有兩個註意點:

  v-else和v-else-if 必須緊跟在帶 v-if 或者 v-else-if 的元素之後,一會兒將源碼的時候會講到為什麼

  因為 v-if 是一個指令,所以必須將它添加到一個元素上。但是如果想切換多個元素呢?此時可以把一個 <template> 元素當做不可見的包裹元素,

 

 源碼分析


 Vue內部會把v-if、v-else、v-else-if解析稱為一個三元運算符,如果有多個v-else,則三元運算符內再嵌套一個三元運算符,以例子里的為例:

解析模板解析到<p v-if="no<0">n小於0</p>這個DOM元素時會執行到processIf()函數

function processIf (el) {               //第9402行  解析v-if指令
  var exp = getAndRemoveAttr(el, 'v-if');   //獲取表達式,例如:"no<0"  
  if (exp) {                                  //如果存在v-if屬性
    el.if = exp;                                  //增加if屬性
    addIfCondition(el, {                          //調用addIfCondition()函數給el增加一個ifConditions屬性,值是一個對象,其中 exp表示當前v-if的值,block是當前AST對象的引用(一會兒給v-else和v-else-if用的)
      exp: exp,
      block: el
    });
  } else {                                  //如果不存在v-if屬性
    if (getAndRemoveAttr(el, 'v-else') != null) {     //如果存在else命令,則在el.else上增加一個else屬性
      el.else = true;
    }
    var elseif = getAndRemoveAttr(el, 'v-else-if'); //如果存在v-else-if指令,則添加elseif屬性
    if (elseif) {
      el.elseif = elseif;
    }
  }
}
function addIfCondition (el, condition) { //第9453行 增加一個ifConditions屬性,
  if (!el.ifConditions) {
    el.ifConditions = [];                     //如果ifConditions屬性不存在則初始化為一個空數組
  }
  el.ifConditions.push(condition);            //將參數condition這個對象push進來
}

對於v-if節點只是增加一個if和ifConditions屬性,對於<p v-if="no<0">n小於0</p>來說,對應的AST對象增加的屬性如下:

解析模板時,對於v-else和v-else-if來說,並沒有把當前對應的AST對象加到AST樹中,而是把自己對應的AST對象添加到最近的v-if的ifConditions里,代碼如下:

if (currentParent && !element.forbidden) {      //第9223行 如果當前對象不是根對象, 且不是style和text/javascript類型script標簽
  if (element.elseif || element.else) {             //如果有elseif或else指令存在(設置了v-else或v-elseif指令)
    processIfConditions(element, currentParent);      //則調用processIfConditions()函數
  } else if (element.slotScope) { // scoped slot    //如果element是作用域插槽
    currentParent.plain = false;
    var name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
  } else {
    currentParent.children.push(element);
    element.parent = currentParent;
  }
}

 processIfConditions會在之前的AST節點,也就是v-if的AST節點的ifConditions上把當前的ast對象添加進去,如下:

function processIfConditions (el, parent) {       //第9421行  解析v-else、v-else-if指令
  var prev = findPrevElement(parent.children);         //調用findPrevElement獲取el之前的AST對象(只查找普通元素AST)
  if (prev && prev.if) {                                //如果prev存在,且它含有v-if指令
    addIfCondition(prev, {                                  //則調用addIfCondition給prev的ifConditions添加一條語句
      exp: el.elseif,
      block: el
    });
  } else {
    warn$2(
      "v-" + (el.elseif ? ('else-if="' + el.elseif + '"') : 'else') + " " +
      "used on element <" + (el.tag) + "> without corresponding v-if."
    );
  }
}

function findPrevElement (children) {           //第9436行 查找children前一個文本AST對象
  var i = children.length;
  while (i--) {                                   //遍歷children,從後開始
    if (children[i].type === 1) {                     //如果是普通節點
      return children[i]                                //則直接返回該元素
    } else {
      if ("development" !== 'production' && children[i].text !== ' ') {   //開發模式下,如果該節點不是普通節點,則報錯
        warn$2(
          "text \"" + (children[i].text.trim()) + "\" between v-if and v-else(-if) " +
          "will be ignored."
        );
      }
      children.pop();
    }
  }
}

執行完後整個AST對象樹如下:

接下來執行generate生成rendre函數時時發現有有if屬性就執行genIf()函數:

function genIf (                //第10205行  //渲染v-if指令
  el,
  state,
  altGen,
  altEmpty
) {
  el.ifProcessed = true; // avoid recursion                                   //避免遞歸
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)    //調用genIfConditions函數
}
  
function genIfConditions (      //第10215行 拼湊if表達式 conditions:比如:[{exp: "ok", block: {…}}] 
  conditions,
  state,
  altGen,
  altEmpty
) {
  if (!conditions.length) {             //如果conditions不存在
    return altEmpty || '_e()'             //則直接返回altEmpty
  }

  var condition = conditions.shift();    //獲取內容,比如:{exp: "no<0", block: {…}}
  if (condition.exp) {                   //拼湊三元運算符
    return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
  } else {
    return ("" + (genTernaryExp(condition.block)))
  }

  // v-if with v-once should generate code like (a)?_m(0):_m(1)
  function genTernaryExp (el) {         //再次調用genElement()函數
    return altGen
      ? altGen(el, state)
      : el.once
        ? genOnce(el, state)
        : genElement(el, state)
  }
}

最後渲染的render函數為:

_c('div',{attrs:{"id":"app"}},[(no<0)?_c('p',[_v("n小於0")]):(no==0)?_c('p',[_v("no等於0")]):_c('p',[_v("no大於0")])])

其中

(no<0)?_c('p',[_v("n小於0")]):(no==0)?_c('p',[_v("no等於0")]):_c('p',[_v("no大於0")])

就是對應的例子里的v-if、v-else、v-else-if結構了

 


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

-Advertisement-
Play Games
更多相關文章
  • sub和sup元素會輕微地增大行高。 幸好,用一點CSS就可以修複這個問題。 來自Nicolas Gallagher和Jonathan Neal的normalize.css(http://necolas.github.com/normalize.css/)。 他們借用了https://gist.gi ...
  • 一、子元素選擇器 1.定義:找到指定標簽中所有特定的直接子元素,然後設置屬性 2.格式: 3.釋義:先找到叫做“標簽名稱1”的標簽,然後在這個標簽中查找所有直接子元素名稱叫做“標簽名稱2”的元素 : (1)子元素選擇器智慧查找兒子,不會查找孫子,重孫子等等 (2)子元素選擇器之間需要用“>”符號相連 ...
  • 表格的隔行變色樣式,checkbox的全選+反選功能實現商品購物車頁面的結算功能。 ...
  • [2019.07.03 學習筆記2] 1.定義列表,沒有項目符號,type無法起作用。 2.自定義列表以<dl>標簽開始,自定義列表項始於<dt>標簽,每個自定義列表項的定義始於<dd>。 ...
  • [2019.07.03 學習筆記1] 1.定義有序列表,有順序。 2.預設情況下採用數字進行標記,有序列表始於<ol>標簽,每個列表項始於<li>。 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <t ...
  • 傳統diff 通過迴圈遞歸對節點的依次對比,複雜度是O(n3) react diff react對傳統diff進行了優化,將複雜度降為O(n) react基於這幾個前提對diff進行了優化: 忽略跨層級操作,因為DOM節點跨層級操作很少。 不同類的組件,則會生成不同的樹形結構,相同類的組件,會生成相 ...
  • 一、包的查找規則: 1、在項目根目錄中找有沒有 node_modules 的文件夾; 2、在 node_modules 中根據包名,找對應的vue 文件夾; 3、在vue 文件夾中,找 一個叫做 package.json的包配置文件; 4、在package.json文件中,查找一個main 屬性【m ...
  • 一、Babel: (官網:https://www.babeljs.cn/docs/) 1、Babel 是一個 JavaScript 編譯器; 2、Babel 是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換為向後相容的 JavaScript 語法,以便能夠運行在當前和舊版本 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...