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

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

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结构了

原文地址:https://www.cnblogs.com/greatdesert/p/11127935.html

时间: 2024-10-11 16:54:02

Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解的相关文章

Vue.js 源码分析(二十) 指令篇 v-once指令详解

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<span>Message: {{ msg }}</span>,以后每当msg属性发生了改变,插值处的内容都会自动更新. 可以给DOM节点添加一个v-once指令,这样模板只会在第一次更新时显示数据,此后再次更新该DOM里面引用的数据时,内容不会自动更新了,例如: <!DOCTYPE html> <html lang="en"> <head>

Vue.js 源码分析(二十二) 指令篇 v-model指令详解

Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语法糖.它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理.例如: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <ti

Vue.js 源码分析(二十三) 高级应用 自定义指令详解

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染.插入到父节点.更新.解绑时可以执行一些特定的操作(钩子函数() 自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指令,局

Vue.js 源码分析(十) ref属性详解

用法 ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <scrip

Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解

普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单元格数据的显示格式,我们可以通过作用域插槽自定义数据的显示格式,对于二次开发来说具有很强的扩展性. 作用域插槽使用<template>来定义模板,可以带两个参数,分别是: slot-scope    ;模板里的变量,旧版使用scope属性 slot              ;该作用域插槽的nam

Vue.js 源码学习之Flag篇

The Progressive JavaScript Framework --vuejs.org 起因 第一次接触 Vue.js 是因为要做一个通讯录的外包项目,这个项目要有前台展示和中后台管理,从轮子做起肯定是不明智的选择,所以当时初步定下的是 Vue.js + Element UI 的技术栈. 项目过程很漫长,因为给的钱实在是可有可无,权当是学习了. 项目的接口是交给了同学. 整个项目采用的是钱后端分离的开发模式,我做我的页面,他做他的接口. 项目出了两个版本,做的时候,中间就强行的看文档.

MyVoix2.0.js 源码分析 WebSpeech与WebAudio篇

楔 子 随着移动互联网时代的开启,各种移动设备走进了我们的生活.无论是日常生活中人手一部的手机,还是夜跑者必备的各种智能腕带,亦或者是充满未来科技感的google glass云云,它们正渐渐改变着我们的生活习惯以及用户交互习惯.触摸屏取代了实体按键,Siri开始慢慢释放我们的双手,而leap motion之类的硬件更是让我们彻底不需要接触IT设备便能通过手势控制它们.在这样的大背景下,前端的交互将涉及越来越多元的交叉学科,我们正如十几年前人们经历Css的诞生一样,见证着一场带动整个行业乃至社会的

MVVM大比拼之vue.js源码精析

VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多来自knockout.angularjs等大牌框架影响,但作者相信 vue 在性能.易用性方面是有优势.同时也自己做了和其它框架的性能对比,在这里.今天以版本 0.10.4 为准 入口 Vue 的入口也很直白: ? 1 var demo = new Vue({ el: '#demo', data: { message: 'Hello V

某课网 - Vue.js 源码全方位深入解析(同步更新)

第1章 准备工作介绍了 Flow.Vue.js 的源码目录设计.Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程. 1-1 课程简介1-2 准备工作1-3 认识 Flow-文档1-4 认识 Flow1-5 Vue.js 源码目录设计-文档1-6 Vue.js 源码目录设计1-7 Vue.js 源码构建-文档1-8 Vue.js 源码构建1-9 从入口开始-文档1-10 从入口开始 第2章 数据驱动详细讲解了模板数据到 DOM 渲染的过程,从 new Vue 开始,分