内置指令与渲染

本文详细介绍了Vue.js的内置指令,包括v-if/v-show的条件渲染,v-for的列表渲染,v-bind/v-on的属性与事件绑定,以及v-pre、v-cloak和v-once的特殊用途。此外,还讨论了v-model、v-slot、v-once和v-text/v-html的区别。文章深入浅出地展示了如何在实际开发中运用这些指令,以提高应用性能和用户体验。
摘要由CSDN通过智能技术生成

内置指令与渲染

1. 内置指令

指令 (Directives) 是带有 v- 前缀的特殊属性,指令属性的值预期是单个 JavaScript 表达式

当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

基本指令:v-htmlv-textv-showv-ifv-elsev-else-ifv-forv-onv-bindv-modelv-slotv-prev-cloakv-once

1-1 v-cloak

在使用 Vue 过程中,引入 Vue 文件创建一个实例,当引入的 Vue 因为某些原因未加载完成时,未编译的 Mustache 标签就无法正常显示,只有等 Vue.js 完全加载后,才会渲染成正确的数据

  • 当在文档前面引入 Vue.js 文件时会阻塞文档渲染,会出现白屏加载情况页面不会渲染,必须等 Vue.js 文件加载完成才能正常显示 Mustache 标签
  • 当在文档后面引入 Vue.js 文件时文档会先渲染加载,文档加载 Mustache 标签中还未编译无法正常显示数据,必须得等 Vue.js 文件加载完成实例挂在完成才会渲染成正确的数据
<div id="app">
  <h3>{{title}}</h3>
</div>
<!-- 当网络比较慢或者其它原因时如果vue.js文件在文档后引入则会显示Mustache标签并且无法正常显示数据 -->
<script src="../vue.js"></script>

<!-- 在文档前面引入vue文件时,会阻塞文档加载, 此时的渲染必须等vue.js文件加载完成才渲染 -->
<script src="../vue.js"></script>
<div id="app">
  <h3>{{title}}</h3>
</div>

v-cloak 指令保持在元素上,当关联的实例对象加载完成时移除,防止出现闪屏以及未编译的 Mustache 标签情况。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕

[v-cloak] {
  display: none;
}
<!-- v-clock当实例对象加载完成时移除 放置白屏以及为编译的Mustache标签出现的情况 -->
<div id="app" v-cloak>
  <h3>{{title}}</h3>
</div>

1-2 v-once

v-once 指令只会初始化时渲染元素和组件一次

随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过,可以用于优化更新性能

<div id="app">
  <!-- v-once初始化时渲染一次,数据改变不会更新改变渲染视为静态内容跳过 -->
  <h3 v-once>{{title}}</h3>
  <button type="button" @click="changeName">修改内容</button>
  <!-- 组件初始化渲染 -->
  <my-component v-once></my-component>
</div>

<script src="../vue.js"></script>

<script>
  Vue.component('my-component', {
    template: `
    <div>
      <ul>
        <li v-for="(val, index) in arr" :key="val" v-once>{{val}}</li>
      </ul>
      <button type="button" @click="changArr">修改内容</button>
    </div>`,
    data() {
      return {
        arr: ['html', 'css', 'js']
      }
    },
    methods: {
      changArr() {
        this.arr.push('vue')
      }
    }
  })
  const vm = new Vue({
    el: '#app',
    data: function () {
      return {
        title: 'Hello Vue'
      }
    },
    methods: {
      changeName() {
        this.title = 'Hello JSX'
      }
    }
  })
</script>

1-3 v-html和v-text

  • v-html 指令更新元素的 innerHTML。内容按普通 HTML 插入不会作为 Vue 模板进行编译
  • v-text 指令更新元素的 textContent。将数据以字符串文本的形式更新
  • 更新部分文本内容可以使用插值语法更新,与 v-htmlv-text 不同的是:插值表达式只会更新原本占位插值所在的数据内容,而 v-htmlv-text 指令则是替换掉整个内容
<div id="app">
  <!-- v-html更新元素HTML标签 替换整个内容 -->
  <div v-html="html">内容</div>
  <!-- v-text更新元素文本内容 替换整个内容 -->
  <h3 v-text="text">内容</h3>
  <!-- mustache标签文本内容更新 替换原始占位插值的数据内容 -->
  <h3>内容{{mustache}}</h3>
</div>

<script src="../vue.js"></script>

<script>
  const vm = new Vue({
    el: '#app',
    data: function() {
      return {
        html: '<h3>Hello v-html</h3>',
        text: 'Hello v-text',
        mustache: 'Hello mustache'
      }
    }
  })
</script>

1-4 v-bind和v-on

  • v-bind 指令用来动态绑定元素的属性(imgsrctitle 属性等)与样式(可以用 style 形式进行内联样式绑定,也可以通过 class 形式进行类名绑定样式)以及绑定 prop
<div id="app">
  <!-- v-bind动态绑定属性、class、样式 -->
  <a v-bind:href="link">百度</a>
  <h3 v-bind:style="headTitleStyle" v-bind:class="headTitleClass">绑定class与style</h3>
</div>

<script src="../vue.js"></script>

<script>
  const vm = new Vue({
    el: '#app',
    data: function() {
      return {
        link: 'https://www.baidu.com',
        headTitleStyle: {
          color: '#736af7',
          fontSize: '30px'
        },
        headTitleClass: ['headtitle', 'box'],
        title: 'v-on'
      }
    }
  })
</script>
  • v-on 指令用来绑定事件监听器监听 DOM 事件,用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件
<div id="app">
  <!-- v-on绑定DOM事件 -->
  <h3>{{title}}</h3>
  <button type="button" @click="changValue">修改内容</button>
</div>

<script src="../vue.js"></script>

<script>
  const vm = new Vue({
    el: '#app',
    data: function() {
      return {
        title: 'v-on'
      }
    },
    methods: {
      changValue() {
        this.title = '绑定DOM事件'
      }
    }
  })
</script>

1-5 v-pre

v-pre 指令用来跳过这个元素和它的子元素的编译过程

  • 可以用来显示原始 Mustache 标签
  • 跳过大量没有指令的节点、没有使用插值语法的节点会加快编译
<div id="app">
  <!-- v-pre指令跳过元素以及子元素的编译 -->
  <h3 v-pre>{{title}}</h3>
  <!-- 没有指令没有插值语法的节点使用会加快编译 -->
  <h3 v-pre>我是JSX</h3>
</div>

<script src="../vue.js"></script>

<script>
  const vm = new Vue({
    el: '#app',
    data: function() {
      return {
        title: 'v-pre指令'
      }
    }
  })
</script>

2. 条件渲染

v-ifv-show 可以实现条件渲染,Vue 会根据表达式值的真假条件来渲染元素。还有 v-elsev-else-if 指令类似于 JavaScript 中的 if-elseif-else-if

2-1 v-if指令

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

JavaScript 中,truthy(真值)指的是在布尔值上下文中转换后的值为真的值。所有值都是真值,除非它们被定义为 falsy (即除了 false0''nullundefinedNaN的值)

<!-- v-if根据指令内的表达式条件渲染元素内容 -->
<!-- 当元素值为真值时渲染,值为假值时不渲染 -->
<h3 v-if="true">Hello Vue Boolean值</h3>
<h3 v-if="1 === 1">Hello Vue 等号运算符</h3>
<!-- 值为假值无法渲染 -->
<h3 v-if="1 > 2">Hello Vue</h3>
<h3 v-if="true ? true : false">Hello Vue 三元表达式</h3>

2-2 <template> 元素

v-if 是一个指令,必须将它添加到一个元素上

切换多个元素时,可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if,最终的渲染结果将不包含 <template> 元素

<div id="app">
  <!-- 当需要切换多个元素使用时 -->
  <!-- 1. 在元素外嵌套一个元素用来条件渲染 -->
  <div v-if="true">
    <h3>html</h3>
    <h3>css</h3>
    <h3>js</h3>
  </div>

  <!-- 2. 通过template元素包裹,该元素在页面渲染中不会显示 -->
  <template v-if="true">
    <h3>vue</h3>
    <h3>webpack</h3>
    <h3>node</h3>
  </template>
</div>

2-2 v-else指令

v-else 指令来表示 v-ifelse

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别

<div id="app">
  <!-- v-else指令用于表示v-if的else语句块 -->
  <!-- 当v-if不成立时则使用v-else语句块内容 -->
  <h3 v-if="Math.random() > 0.5">Hello Vue</h3>
  <h3 v-else>Hello JSX</h3>
  
  <!-- 必须在v-if与v-else-if指令后, 否则无法识别-->
  <div>Hello JavaScript</div>
  <div v-else>Hello Webpack</div>
</div>

2-3 v-else-if指令

v-else-if 指令来表示 v-ifelse-if 块,可以连续使用

类似于 v-elsev-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后

<!-- v-if用于表示v-if的 else-if 语句块 可以连续使用 -->
<button type="button" @click="num++">num++</button>
<h3 v-if="num === 0">html</h3>
<h3 v-else-if="num === 1">css</h3>
<h3 v-else-if="num === 2">javascript</h3>

<div>必须紧跟v-if或v-else-if</div>
<!-- 必须跟在v-if和v-else-if指令后 -->
<h3 v-else-if="num === 3">react</h3>

<h3 v-else>vue</h3>
el: '#app',
data: function() {
  return {
    num: 0
  }
}

2-4 key管理复用元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染

当两个模版使用了相同的元素的情况下,通过条件判断渲染时会复用元素,而不是重新渲染元素,这会在某些特定场景不符合实际需求

Vue 提供了一种方式来表达这两个元素是完全独立的,不要复用,添加一个具有唯一值的 key 属性标识即可

<!-- 使用key后可以区分元素,可以使元素独立不被复用 -->
<template v-if="isTrue">
  <label for="">用户名</label>
  <input type="text" placeholder="username" key="username">
</template>
<template v-else>
  <label for="">邮箱</label>
  <input type="text" placeholder="email" key="email">
</template>
<button type="button" @click="toggle">切换toggle</button>
el: '#app',
data: function() {
  return {
    isTrue: true,
    title: 'Hello Vue',
    message: 'Hello React'
  }
},
methods: {
  toggle() {
    this.isTrue = !this.isTrue
  }
}

2-5 v-show指令

v-show 用于根据条件展示元素的选项

带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性中的 display 属性

<!-- v-show用于展示元素的选项 -->
<!-- v-show指令相当于切换元素的display属性 -->
<h3 v-show="true">Hello Vue</h3>
<h3 v-show="false">Hello Vue</h3>

2-6 v-if与v-show区别

  • v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
  • v-if 是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
  • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

3. 列表渲染

在使用 v-for 指令时,可以对数组、对象、数字、字符串进行循环,来获取源数据中的每一个值

3-1 v-for数组

可以用 v-for 指令基于一个数组来渲染一个列表

v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名

  • v-for 块中,我们可以访问所有父作用域的属性
  • 循环数组时可使用 of 替代 in 作为分隔符来迭代,数组遍历有两个参数
    • value —— 数组循环出的数组项
    • index —— 当前项的索引
<ul>
  <!-- v-for遍历数组 -->
  <li v-for="(list, index) of arr">
    <!-- v-for块内元素可访问所有父级作用域属性 -->
    <h3>{{index}} {{list.name}}-{{list.message}}</h3>
  </li>
  <!-- index表示数组项索引 list表示遍历的数组项 -->
</ul>
el: '#app',
data: function() {
  return {
    arr: [
      {name: '林俊杰', message: '新地球'},
      {name: '邓紫棋', message: '新的心跳'},
      {name: '薛之谦', message: '丑八怪'}
    ]
  }
}

3-2 v-for对象

可以用 v-for 指令遍历一个对象的属性

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致

  • value —— 遍历对象的属性值
  • name —— 遍历对象的属性名
  • index —— 遍历对象的索引
<ul>
  <!-- 通过v-for遍历对象 遍历对象通过in分隔符 -->
  <li v-for="(value, key, index) in info">
    <h3>{{value}}-{{key}}-{{index}}</h3>
    <!-- value表示对象的属性值 key表示对象的属性名 index表示属性的索引 -->
  </li>
</ul>
el: '#app',
data: function() {
  return {
    // 定义对象数据
    info: {
      title: 'Hello Vue',
      author: 'Jiang Fei',
      date: '2022-05-16'
    }
  }
}

3-3 v-for使用

  • v-for 里使用值范围,可以接受整数。在这种情况下,它会把模板重复对应次数
  • <template> 上使用 v-for,渲染列表时,可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-for,最终的渲染结果将不包含 <template> 元素
<!-- v-for接受范围值,模版会重复渲染 -->
<h3 v-for="i in 5">{{i}}</h3>

<!-- 使用template元素,包裹渲染元素,渲染结果不会存在template元素 -->
<template v-for="(list, index) in lesson">
  <h3>{{list.name}}</h3>
  <h3>——————————————</h3>
</template>
el: '#app',
data: function() {
  return {
    lesson: [
      {name: 'html', isShow: false},
      {name: 'css', isShow: false},
      {name: 'javascript', isShow: true},
      {name: 'vue', isShow: true}
    ],
  }
}

3-4 v-for里的key的作用

Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用就地更新的策略,如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性

key 属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes 比较差异

  • 如果不使用 keyVue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法
  • 使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素

key 是为 Vuevnode 的唯一标记,通过这个 keydiff 操作可以更准确、更快速

  • 更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确
  • 更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

不要使用对象或数组之类的非基本类型值作为 v-forkey。请用字符串或数值类型的值

<ul>
  <!-- key作为元素唯一标识 当列表发生改变用来重用或者重新排序元素 -->
  <li v-for="(list, index) of lesson">
    <!-- key值不能使用引用类型,只能是字符串与数值类型 -->
    <div>{{list.name}} <input type="text"></div>
    <!-- 当不指定key时,元素会复用相同类型的元素 --> 
  </li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function() {
  return {
    title: 'Hello Vue',
    lesson: [
      {name: 'html', maxLength: 70},
      {name: 'css', maxLength: 70},
      {name: 'js', maxLength: 80 }
    ]
  }
},
methods: {
  addArr() {
    this.lesson.splice(2, 0, {name: 'vue', maxLength: 50})
  }
}

3-5 索引作为key的问题

不要使用数组的下标 index 作为 key

  • 数组下标 index 作为 key 时,当对数据进行逆序添加、逆序删除时会破坏数据顺序从而导致产生没必要的 DOM 更新

<ul>
  <!-- 数组下标index作为key时, -->
  <li v-for="(list, index) of lesson" :key="index">
    <!-- <li v-for="(list, index) of lesson" :key="list.id"> -->
    <div>{{list.name}}-{{list.value}}</div>
  </li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function () {
  return {
    title: 'Hello Vue',
    lesson: [
      { id: 01, name: 'html', value: 70 },
      { id: 02, name: 'css', value: 80 },
      { id: 03, name: 'js', value: 60 }
    ]
  }
},
methods: {
  addArr() {
    this.lesson.splice(2, 0, { id: 04, name: 'vue', value: 55 })
  }
}
  • 当元素结构中有 input 输入类表单元素时,会产生错误的 DOM 更新,导致 Vue 会复用错误的旧子节点

<ul>
  <!-- 数组下标index作为key时, -->
  <li v-for="(list, index) of lesson" :key="index">
    <!-- <li v-for="(list, index) of lesson" :key="list.id"> -->
    <div>{{list.name}}-{{list.value}} <input type="text"></div>
  </li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function () {
  return {
    title: 'Hello Vue',
    lesson: [
      { id: 01, name: 'html', value: 70 },
      { id: 02, name: 'css', value: 80 },
      { id: 03, name: 'js', value: 60 }
    ]
  }
},
methods: {
  addArr() {
    this.lesson.splice(2, 0, { id: 04, name: 'vue', value: 55 })
  }
}

3-6 数组更新检测

Object 对象的侦测方式是通过 getter/setter 来实现的

Array 数组的侦测无法通过 getter/setter 来完成,Vue 对将被侦听的数组的变更方法进行了包裹,只有包裹的方法操作数组时才会触发视图更新

  • push() —— 添加任意数量数组项,依次添加到数组末尾,并返回修改后数组长度
  • pop() —— 从数组末尾删除最后一项,并减少数组的长度,返回移除后的数组项
  • shift() —— 从数组开头删除第一项并返回该项,同时数组的长度减少
  • unshift() —— 添加任意数量数组项,依次添加到数组开头,并返回修改后数组长度
  • splice() —— 删除原数组一部分数据项,可以在被删除的位置插入新的数组项
  • sort() —— 调用每个数组项的 toString() 方法,转换为字符串进行比较排序,并返回排序过后的数组
  • reverse() —— 用于反转数组排序,返回反转排序后的数组

通过包裹数组更新元素的方法实现原理:

  • 调用原生对应的方法对数组进行更新

  • 重新解析模板,进而更新页面

<!-- 对象是通过getter与setter来实现数据监测的 -->
<!-- Vue能检测嵌套对象的变化 -->
<h3>{{message.title}} {{message.info.name}}</h3>
<ul>
  <li v-for="list in arr">
    <h3>{{list}}</h3>
  </li>
</ul>
<div>
  <button @click="changObj">修改对象数据</button>
  <button @click="changeArr">修改数组数据</button>
</div>
el: '#app',
data: function() {
  return {
    message: {
      title: 'Hello Vue',
      info: {
        name: 'jsx',
        age: 22
      }
    },
    arr: ['html', 'css', 'js']
  }
},
methods: {
  changObj() {
    // 直接修改对象属性,Vue会检测到数据修改并更新视图
    this.message.info = {
      name: 'ljj',
      age: 23
    }
    console.log(this.message.info)
  },
  changeArr() {
    // 通过[]修改数组。Vue无法检测数组变化,数据改变但不改变视图
    // this.arr[0] = 'vue'
    // 必须通过push, pop, shift, unshift, splice, sort, reverse方法操作才会触发视图更新
    this.arr.splice(this.arr.length, 0, 'Vue')
      console.log(this.arr)
  }
}

3-7 过滤与排序

变更方法会改变调用了这些方法的原始数组,非变更方法则是返回一个新的数组。使用非变更方法时,可以将新数组替换旧数组

  • concat —— 创建新数组,将传入的数据添加到新数组的末尾,最后返回这个添加后的新数组
  • slice —— 基于当前数组一个项或者多个项创建一个新数组,接收两个参数,即要返回项的起始和结束位置,最后返回新数组
  • map —— 对数组每一项执行指定函数,返回每次函数调用的结果组成的数组
  • filter —— 对数组每一项执行指定函数,该函数返回符合条件的项组成的数组

当需要展示数组过滤或者排序后的内容,而不实际变更或重置原始数据,在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组

<label for="">学习课程</label>
<input type="text" name="keyword" v-model="keyword">
<button type="button" @click="sortType = 1">升序</button>
<button type="button" @click="sortType = 2">降序</button>
<button type="button" @click="sortType = 0">原顺序</button>

<ul>
  <li v-for="list in selectStudy">
    <h3>{{list.lesson}} <----> 价格:{{list.money}}元</h3>
  </li>
</ul>
el: '#app',
data: function () {
  return {
    keyword: '',
    sortType: 0, // 0原顺序,1升序,2降序
    study: [
      { lesson: 'html', money: 50 },
      { lesson: 'css', money: 80 },
      { lesson: 'javascript', money: 120 },
      { lesson: 'vue', money: 90 }
    ]
  }
},
// 使用计算属性实现过滤与排序
computed: {
  selectStudy() {
    const newArr = this.study.filter((val) => {
      return val.lesson.includes(this.keyword) === true
    })
    if (this.sortType) {
      newArr.sort((a, b) => {
        return this.sortType === 1 ? b.money - a.money : a.money - b.money
      })
    }
    return newArr
  }
}

也可以使用 watch 监听去实现,当数据发生改变时去执行过滤或者排序操作,然后返回过滤或排序后的数组

<label for="">学习课程</label>
<input type="text" name="keyword" v-model="keyword">
<button type="button" @click="sortType = 1">升序</button>
<button type="button" @click="sortType = 2">降序</button>
<button type="button" @click="sortType = 0">原顺序</button>

<ul>
  <li v-for="list in studyArr">
    <h3>{{list.lesson}} ---- 价格:{{list.money}}元</h3>
  </li>
</ul>
el: '#app',
data: function () {
  return {
    keyword: '',
    sortType: 0, // 0原顺序,1升序,2降序
    study: [
      { lesson: 'html', money: 50 },
      { lesson: 'css', money: 80 },
      { lesson: 'javascript', money: 120 },
      { lesson: 'vue', money: 90 }
    ]
  }
},
// 使用监听器实现过滤与排序
watch: {
  keyword: {
    immediate: true,
    handler(val) {
      this.studyArr = this.study.filter((val) => {
        return val.lesson.includes(this.keyword) === true
      })
    }
  },
  sortType: {
    immediate: true,
    handler(val) {
      this.studyArr = this.study.filter((val) => {
        return val.lesson.includes(this.keyword) === true
      })
      if (val) {
        this.studyArr.sort((a, b) => {
          return val === 1 ? b.money - a.money : a.money - b.money
        })
      }
      return this.studyArr
    }
  }
}

3-8 v-for与v-if

不推荐在同一元素上使用 v-ifv-for

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中

如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素

  • v-for 渲染符合 v-if 条件判断的数据
<!-- v-for同一节点上优先级高于v-if -->
<!-- 可将v-if作为条件判断该循环那些元素 -->
<ul>
  <li v-for="list in lesson" v-if="list.isShow">
    <h3>{{list.name}}</h3>
  </li>
</ul>
el: '#app',
data: function() {
  return {
    lesson: [
      {name: 'html', isShow: false},
      {name: 'css', isShow: false},
      {name: 'javascript', isShow: true},
      {name: 'vue', isShow: true}
    ]
  }
}
  • 通过 v-if 条件判断跳过 v-for 循环的执行
<!-- 通过v-if条件判断跳过v-for循环 -->
<ul v-if="info.length">
  <li v-for="list in info" :key="list.name">
    <h3>{{list.name}}</h3>
  </li>
</ul>
<h3 v-else>没有数据</h3>
<button type="button" @click.once="addList">添加数据</button>
el: '#app',
data: function() {
  return {
    info: []
  }
},
methods: {
  addList() {
    this.info.splice(0, 0,  
      {name: 'html', isShow: false},
      {name: 'css', isShow: false},
      {name: 'javascript', isShow: true},
      {name: 'vue', isShow: true})
  }
}

3-9 组件上使用v-for

在自定义组件上,你可以像在任何普通元素上一样使用 v-for

直接在组件上绑定属性不会有效,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域,为了把迭代数据传递到组件里,我们要使用 prop

当在组件上使用 v-for 时,key 现在是必须的

<template>
  <my-component :todo="todoList"></my-component>
</template>
Vue.component('my-component', {
    // 在组件上使用v-for需要传递props, 组件上绑定的数据不会传入到组件内
	props: ['todo'],
	template: `<ul><li v-for="list in todo" :key="list.name">{{list.name}}</li></ul>`
})

el: '#app',
data: function() {
  return {
    lesson: [
      {name: 'html', isShow: false},
      {name: 'css', isShow: false},
      {name: 'javascript', isShow: true},
      {name: 'vue', isShow: true}
    ]
  }
}

4. 自定义指令

4-1 什么是自定义指令?

Vue 的项目中, v-ifv-showv-forv-model 等等称之为指令,也被称之为 Vue 的内置指令,它们为我们提供了不同的功能可以直接使用的

为了更好的满足需求,最大化的让开发者个性化开发,Vue 暴漏了自定义指令的 API 给我们使用,让我们除了使用内置指令外,我们还可以自己定义指令,定义好后和内置指令的方式非常类似

  • 自定义指令中命名必须小写,当有多个单词时不要使用驼峰式形式,要是有短横线小写形式
  • directive 中当指令名为短横线小写形式时,需要使用引号包裹不然无法使用
<!-- v-开头的指令被称为内置指令 -->
<h3 v-text="title"></h3>
<!-- Vue允许自定义指令 自定义属性在模版使用时使用小写 在directive上建议使用单引号包裹 -->
<h3 v-wenben:write="value"></h3>
el: '#app',
data: function () {
  return {
    title: 'v-text指令',
    value: '自定义指令'
  }
},
directives: {
  // 该写法只在bind和update时触发
  wenben: function(el, binding) {
    console.log(el, binding)
    el.innerText = binding.value
  }
}

4-2 局部与全局自定义指令

**Tips:**全局注册指令使用的是 directive,局部注册指令使用的是 directives,局部指令一次性注意注册很多个,全局指令依次只能注册一个

  • 局部自定义指令,在 Vue 中提供了一个 directives 选项供我们注册局部自定义指令,该指令只允许在组件内部使用
  • 全局自定义属性,通过 Vue.directive 注册全局自定义指令,可以在任意组件中的元素上使用自定义指令
<div id="app">
  <h3 v-wenben="title"></h3>
  <!-- v-texts指令是在全局定义任何地方都可使用 -->
  <h3 v-texts="title"></h3>
</div>

<div id="box">
  <!-- v-wenben指令是在vm实例定义的不能在全局使用 -->
  <!-- <h3 v-wenben="title"></h3> -->
  <h3 v-texts="title"></h3>
</div>
// 1. directive和directives参数可以为函数或者对象
// 2. 直接传入函数则表示在update和binding函数触发时会执行,其它钩子触发不会执行

// 通过Vue.directive在全局中定义
Vue.directive('texts', {
  // 指令第一次绑定元素时触发
  bind(el, binding) {
    el.innerText = binding.value
  },
  // 组件虚拟DOM更新时调用
  update(el, binding) {
    el.innerText = binding.value
  }
})

// vue实例1
const vm = new Vue({
  el: '#app',
  data: function() {
    return {
      title: 'Hello Directive'
    }
  },
  directives: {
    // 局部定义通过directives选项在options中定义
    wenben: function(el, binding) {
      el.innerText = binding.value
    }
  }
})

// vue实例2
const vm1 = new Vue({
  el: '#box',
  data: function() {
    return {
      title: 'Hello Vue'
    }
  }
})

4-3 自定义指令钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  • update:所在组件的 VNode 虚拟DOM更新时调用,但是可能发生在其子 VNode 虚拟DOM更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 虚拟DOM全部更新后调用
  • unbind:只调用一次,指令与元素解绑时调用,通过 $destroy() 销毁一个组件的实例解绑所有指令与事件

**Tips:**钩子函数里的 this 指向的是全局 window 对象

<h3>
  <my-component :title="title"></my-component>
</h3>
<button type="button" @click="changeTitle">修改数据</button>
<button type="button" @click="destroy">实例销毁</button>
Vue.component('my-component', {
  template: `<div>
          <h3 v-wenben="getTitle"></h3>
        </div>`,
  data() {
    return {

    }
  },
  props: ['title'],
  computed: {
    getTitle() {
      console.log(this.title)
      return this.title
    }
  }
})
// 自定义指令钩子函数中this都指向window
Vue.directive('wenben', {
  // 指令与元素初始化绑定触发
  bind(el, binding) {
    console.log('bind')
    console.log(this)
    el.innerText = binding.value
  },
  // 被绑定元素插入父节点时触发
  inserted(el, binding) {
    console.log('inserted')
    console.log(this)
    el.innerText = binding.value
  },
  // 虚拟DOM更新时触发
  update(el, binding) {
    console.log('update')
    console.log(this)
    el.innerText = binding.value
  },
  // 组件以及子组件虚拟DOM全部更新完成触发
  componentUpdated() {
    console.log('componentUpdated')
    console.log(this)
  },
  unbind() {
    // 指令与元素解除绑定时触发
    console.log('unbind')
    console.log(this)
  }
})

const vm = new Vue({
  el: '#app',
  data: function() {
    return {
      title: 'hello vue'
    }
  },
  methods: {
    changeTitle() {
      this.title = 'Hello JSX'
    },
    destroy() {
      // 组件实例销毁时解除绑定指令与事件
      this.$destroy()
    }
  }
})

4-4 钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用
    • expression:字符串形式的指令表达式,例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象,例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行

<div id="app">
  <h3 v-wenben:show.end="title + '壮壮'"></h3>
</div>
el: '#app'
data: function() {
  return {
    title: 'hello vue'
  }
},
directives: {
  wenben: {
    bind(el, binding, vnode, oldNode) {
      console.log(el) // 指令绑定的元素
      console.log(binding) // 对象包含多个属性
      console.log(vnode) // 编译生成的虚拟DOM
      console.log(oldNode) // 上一个虚拟DOM的节点只能在update或者componentUpdate使用
      el.innerText = binding.value
    },
    update(el, binding, vnode, oldNode) {
      console.log(oldNode); // 能获取上一个虚拟DOM节点属性内容
      el.innerText = binding.value
    }
  }
},
methods: {
  changeValue() {
    this.title = 'Hello Vue'
  }
}

4-5 动态指令参数

指令的参数可以是动态的,参数可以根据组件的实例属性进行更新

<!-- <div v-mydirective:[argument]="value"></div> -->
<h3 v-pos:[title]="20">Hello Vue</h3>
el: '#app'
data: function() {
  return {
    // 通过[]自定义指令参数,将定义的date数据作为指令参数实现动态指令参数
    title: 'bottom'
  }
}
directives: {
  pos: {
    bind(el, binding) {
      el.style.position = 'fixed';
      let result = binding.arg === 'bottom' ? 'bottom' : 'top';
      el.style[result] = binding.value + 'px'
    }
  }
}

4-6 简写与对象字面量

自定义指令 directive 中可以简写参数,可以直接传入一个函数作为钩子函数,相当于指定 bindupdate 钩子函数

// 全局自定义指令简写
Vue.directive('focus', function(el, binding) {})

// 全局完整形式
Vue.directive('focus', {
  bind() {},
  inserted() {},
  update() {},
  componentUpdate() {},
  unbind() {}
})

const vm = new Vue({
  el: '#app',
  // 局部自定义指令简写
  directives: {
    // 简写形式,相当于定义bind与update钩子,其它钩子函数不会触发执行
    focus: function(el, binding) {},

    // 局部自定义指令完整钩子函数
    focus: {
      bind() {},
      inserted() {},
      update() {},
      componentUpdate() {},
      unbind() {}
    }
  }
})

如果一个指令需要多个值可以传入对象作为自定义指令的内容,指令函数能接受任何 JavaScript 表达式

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text)  // => "hello!"
})

4-7 实现一个自定义指令

实现一个拖拽指令,可在页面可视区域任意拖拽元素

  • 设置需要拖拽的元素为相对定位,其父元素为绝对定位
  • 鼠标按下 (onmousedown) 时记录目标元素当前的 lefttop
  • 鼠标移动 (onmousemove) 时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 lefttop
  • 鼠标松开 (onmouseup) 时完成一次拖拽
Vue.directive('draggable', {
  inserted: function(el) {
    el.style.cursor = 'move'
    el.onmousedown = function(e) {
      let disx = e.pageX - el.offsetLeft
      let disy = e.pageY - el.offsetTop
      document.onmousemove = function(e) {
        e.preventDefault();
        let x = e.pageX - disx
        let y = e.pageY - disy
        let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el)
          .width)
        let maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el)
          .height)
        if (x < 0) {
          x = 0
        } else if (x > maxX) {
          x = maxX
        }

        if (y < 0) {
          y = 0
        } else if (y > maxY) {
          y = maxY
        }

        el.style.left = x + 'px'
        el.style.top = y + 'px'
        el.style.position = 'fixed'
      }
      document.onmouseup = function(e) {
        e.preventDefault();
        document.onmousemove = document.onmouseup = null
      }
    }
  }
})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值