[TOC]

模板指令

React的开发模式:

  • React使用的jsx,所以对应的代码都是编写的类似于js的一种语法;
  • 之后通过Babel将jsx编译成 React.createElement 函数调用;

Vue也支持jsx的开发模式:

  • 但是大多数情况下,使用基于HTML的模板语法;
  • 在模板中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定在一起;
  • 在底层的实现中,Vue将模板编译成虚拟DOM渲染函数

    Mustache 双大括号语法

    如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值。

  • data返回的对象是有添加到Vue的响应式系统中;当data中的数据发生改变时,对应的内容也会发生更新。

  • 当然,Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式。
    • 计算、调用函数、三元运算符、计算属性

注意:mustache 中不可以用代码语句,比如赋值,if 等

<div id="app"></div>

<template id="my-app">
  <!-- 1.mustache的基本使用 -->
  <h2>{{message}} - {{message}}</h2>
  <!-- 2.是一个表达式 -->
  <h2>{{counter * 10}}</h2>
  <h2>{{ message.split(" ").reverse().join(" ") }}</h2>
  <!-- 3.也可以调用函数 -->
  <!-- 可以使用computed(计算属性) -->
  <h2>{{getReverseMessage()}}</h2>
  <!-- 4.三元运算符 -->
  <h2>{{ isShow ? "哈哈哈": "" }}</h2>
  <button @click="toggle">切换</button>

  <!-- 错误用法 -->
  <!-- var name = "abc" -> 赋值语句 -->
  <!-- <h2>{{var name = "abc"}}</h2>
    <h2>{{ if(isShow) {  return "哈哈哈" } }}</h2> -->
</template>

<script src="https://unpkg.com/vue@next"></script>

<script>
  const App = {
    template: '#my-app',
    data() {
      return {
        message: "Hello World",
        counter: 100,
        isShow: true
      }
    },
    methods: {
      getReverseMessage() {
        return this.message.split(" ").reverse().join(" ");
      },
      toggle() {
        this.isShow = !this.isShow;
      }
    }
  }

  Vue.createApp(App).mount('#app');
</script>

v-once 指令

v-once用于指定元素或者组件只渲染一次:

  • 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
    • 值会变化一次,但变化后的值不会被渲染出来
  • 相当于在打开页面后数据就不会变了,所以该指令可以用于性能优化; ```html

    {{ count }}

<a name="aADuf"></a>
## v-text 指令
用于更新元素中的 textContent 文本内容
```html
<h2> {{ count }} </h2>

<!-- hhh 将会被忽略,并填充 count 的值 -->
<h3 v-text="count"> hhh </h3>

v-html

默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析,比如双花括号直接把标签直接给你解析成字符串。
如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;

可以看到 v-text 和 v-html 都能清空作用标签的子内容,并且一个是添加文本,一个是添加子标签。那它们存在覆盖关系吗?答案是肯定的,哪个指令后发挥作用,哪个指令就会覆盖之前指令的效果。

<template id="templ"> 
  <h3 v-html="element">这里的内容会被清空</h3>
  <!-- vue3 支持多个根标签 -->
  <h3 v-html="element" v-text="message">message 内容会覆盖 element 的标签</h3>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        message: 666,
        element: '<h2> hhh </h2>' // h2 将会成为 h3 的子标签。(看起来很奇怪)
      }
    }
  }).mount("#app")
</script>

v-pre

v-pre用于跳过元素和它的子元素的编译过程,加快编译的速度;

<!-- 跳过编译,直接显示 {{ count }}  -->
<h2 v-pre> {{ count }} </h2>

v-cloak

这个指令将会成为标签中的一个属性,知道标签被编译完,这个属性就会消失。

比如存在这么一个场景:message 的 js 代码没有第一时间解释完,网页上可能会先出现 {{ message }} 然后又立马变成 message 内容的情况。
v-cloak 的作用是当该组件实例化还没完成时,作为属性出现在标签上,等实例化完成,自动消失。所以在还没实例化完成时,css 可以通过 v-cloak 这个属性选中该标签,让里面的内容隐藏,等实例化完成属性消失,css 就选不中该标签了。不影响后续正常展示内容。

但是现在 vue3 从根源上解决了这个问题,所以这个模板语法用的很少。

<style>
  [v-cloak] {
    display: none;
  }
</style>

<div id="app"></div>

<template id="templ">
  <!-- <div> 不会显示,直到编译结束 -->
  <div v-clock> {{ message }} </div>
</template>

属性绑定

v-bind

前面的一系列指令,主要是将值插入到模板内容中。但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。

  • 比如动态绑定a元素的href属性;
  • 比如动态绑定img元素的src属性;

v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值。语法糖:
在开发中,有哪些属性需要动态进行绑定呢?比如图片的链接src、网站的链接href、动态绑定一些类、样式等等

<template id="templ">
  <img v-bind:src="imgUrl" alt="">
  <a :href="link">百度一下</a>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        imgUrl: "https://tva4.sinaimg.cn/large/006BNqYCly1h1dvqzv9lrj320818g4d0.jpg",
        link: "https://www.baidu.com"
      }
    }
  }).mount("#app")
</script>

绑定 class 属性

在开发中,有时候我们的元素class也是动态的,比如:当数据为某个状态时,字体显示红色。当数据另一个状态时,字体显示黑色。
绑定class有两种方式:

  • 对象语法
  • 数组语法

    对象语法

    我们可以传给:class 一个对象,以动态地切换 class。
    这个对象的属性就是绑定的类,属性值是一个布尔值,通过布尔值可以控制这个属性起不起作用,也就是这个类是否绑定到了元素中。
    属性可以不用引号包裹。 ```html

<a name="e38kz"></a>
### 数组语法
我们可以把一个数组传给 :class,以应用一个 class 列表
```html
<style>
  div {
    width: 200px;
    height: 200px;
    background-color: black;
  }  
  .class1 {
    /* background-color: red; */
    width: 300px;
  }  
  .class2 {
    background-color: aqua;
  }
</style>

<div id="app"></div>

<template id="templ">
  <!-- 数组绑定 -->
  <!-- 数组元素必须被引号包裹 -->
  <div :class="['class1', 'class2']"></div><br>
  <!-- 数组中可以使用三元运算符 -->
  <div :class="['class1', 1 > 0 ? '' : 'class2']"></div><br>
  <!-- 数组元素还可以是对象 -->
  <div :class="['class1', {class2: false}]"></div><br>

  <div :class="[classObj]"></div>
  <div id="box" :class="classArr"></div>
  <button @click="changeClass">changeClass</button>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        className: 'class1',
        classObj: {
          class1: true,
          class2: false
        },
        classArr: ['class1', 'class2']
      }
    },
    methods: {
      changeClass() {
        this.classObj.class1 = !this.classObj.class1
        this.classObj.class2 = !this.classObj.class2
      }
    },
  }).mount("#app")
</script>

绑定 style 行内样式

对象语法

两点注意:

  • 对象中的属性值如果没有引号包裹,那它会被认为是个变量,会去 data 中查找该变量的值。
  • 属性 key 如果是短横线的写法,则一定要引号包裹。 ```html

<a name="MkOXb"></a>
### 数组语法
style 的数组语法和对象一样可以将多个样式应用到同一个元素上
```html
<div :style="[color: 'red', 'font-size': '30px']">哈哈哈哈</div>

动态绑定属性

前面我们无论绑定src、href、class、style,属性名称都是固定的;在某些情况下,我们要绑定的属性可能也不是固定的:
如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义;这种绑定的方式,我们称之为动态绑定属性;

<template id="templ">
  <!-- 添加了一个名为 zs 的属性,且属性值为 123 -->
  <div :[key]="value"></div>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        key: 'zs',
        value: 123
      }
    }
  }).mount("#app")
</script>

批量添加属性

v-bind 直接绑定一个对象,则会将对象中的所有键值对全部添加到元素中,成为元素的一个个属性。

<template id="templ">
  <div v-bind="keys"></div>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        keys: {
          key1: 'zs',
          key2: 123
        }
      }
    }
  }).mount("#app")
</script>

事件监听

v-on

v-on 的使用:
缩写:@
预期:Function | Inline Statement | Object
参数:event
修饰符:

  • stop - 调用 event.stopPropagation(),阻止冒泡
  • prevent - 调用 event.preventDefault(),阻止默认行为
  • capture - 添加事件侦听器时使用 capture 模式。
  • self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • {keyAlias} - 仅当事件是从特定键触发时才触发回调。
  • once - 只触发一次回调。
  • left - 只当点击鼠标左键时触发。
  • right - 只当点击鼠标右键时触发。
  • middle - 只当点击鼠标中键时触发。
  • passive - { passive: true } 模式添加侦听器

用法:绑定事件监听

如果我们希望一个元素绑定多个事件,这个时候可以传入一个对象:

<template id="templ">

  <h2> {{ count }}</h2>
  <!-- 点击加 10 -->
  <button @click="muilt">+10</button>
  <!-- 批量监听事件 -->
  <h1 v-on="{click: muilt, mousemove: mouseMove}">{{ count }}</h1>
</template>

<script>
  Vue.createApp({
    template: "#templ",
    data() {
      return {
        count: 0,
      }
    },
    methods: {
      muilt() {
        this.count += 10
      },
      mouseMove() {
        this.count++
      }
    },
  }).mount("#app")
</script>

v-on 参数传递

监听事件的时候,标签中指定回调函数,如果该回调函数不需要额外参数,那么函数后的( )可以不添加。

  • 没有添加括号,则此时会默认传递一个实参:事件对象 event。methods 中定义事件回调函数时,可以随便拿一个形参来接收该事件对象。
  • 如果添加了括号,则需要在括号中显式传递事件对象$event

    <!-- 没括号 -->
    <button @click="fn1">+10</button>
    <!-- 有括号 -->
    <button @click="fn2()">+10</button>
    <!-- 有括号,显式传递事件对象 -->
    <button @click="fn3(123, $event)">+10</button>
    <script>
    Vue.createApp({
      template: "#templ",
    
      methods: {
        fn1(e) {
          console.log(e); // PointerEvent {...}
        },
        fn2(e) {
          console.log(e); // undefined
        },
        fn3(n,e) {
          console.log(n); // 123
          console.log(e); // PointerEvent {...}
        }
      }
    }).mount("#app")
    </script>
    

    v-on 的修饰符

    v-on支持修饰符,修饰符相当于对事件进行了一些进一步的处理
    尤其键盘事件的修饰符要搭配修饰符使用:
    比如输入框添加一个键盘按键抬起的事件,@keyup。keyup 后面一般会跟一个修饰符规定具体哪个键弹起的时候才执行响应函数,比如 @keyup.enter = fn 。如果没有指定,就会在打字输入时,按键每弹起一次就响应一次回调函数体验不好。 ```html

<a name="xEAju"></a>
### template 元素
当我们想要循环多个标签元素的时候都需要容器包裹,这个时候就要想到 template 元素
```html

  <template id="templ">
    <ul>
      <template v-for="(value, key) in people">
        <li> {{ key }} </li>
        <li> {{ value }} </li>
      </template>
    </ul>
  </template>

数组更新检测

对于循环的数据源数组,我们肯定会对数组进行一些操作,这些操作方法被 vue 包裹处理过了,用这些方法一操作数组,数组的变化会实时更新到视图上。几乎涵盖了 js 中提供的所有数组操作方法。
对数组本身进行修改:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

放回修改后的新数组:

  • filter()
  • concat()
  • slice()
  • … ```html

<a name="KD9gN"></a>
## v-for 中的 key 是什么作用?
 在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性。
```html
<li v-for="(device, index) in devices" :key="device"> {{ device }} </li>

这个key属性有什么作用呢?我们先来看一下官方的解释:

  • key属性主要用在Vue的虚拟DOM diff 算法,在新旧nodes对比时辨识VNodes;
  • 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
  • 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁 key 不存在的元素;

    认识 VNode

    我们先来解释一下VNode的概念:
    VNode的全称是Virtual Node,也就是虚拟节点;

  • 事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode;

  • VNode的本质是一个JavaScript的对象;

image.png
image.pngimage.png
VNode 方便多端渲染,跨平台是最大的特点。

虚拟 DOM

网页中肯定不止一个元素,会有多个 VNode,这些 VNode 会形成一个 VNode Tree。这个就是虚拟 dom 树。
image.png

diff 算法

  • diff 算法的本质就是:找出两个对象之间的差异,目的是尽可能做到节点复用。

渲染真实 DOM 的开销很大,有时候我们修改了某个数据,直接渲染到真实dom上会引起整个dom树的重绘和重排。我们希望只更新我们修改的那一小块dom,而不是整个 dom,diff 算法就帮我们实现了这点。
此处说到的对象,指的其实就是 vue 中的 virtual dom(虚拟dom树),也就是 VNode 节点对象来表示页面中的 dom 结构。


diff 算法可以比较新旧 VNode 对象的差异,而 key 就是一种更高效的 diff 算法实现方式。

插入F的案例

在 v-for 的循环遍历中,有 a、b、c、d 四个

  • 被渲染,现在想插入一个 f 到 b 的后面。有 key 和没有 key 的情况下, 那么 Vue 中对于列表的更新究竟是如何操作的呢 ?

    Vue 事实上会对于有 key 和没有 key 会调用两个不同的 diff 算法;

    • 有 key,那么就使用 patchKeyedChildren 方法;
    • 没有 key,那么久使用 patchUnkeyedChildren 方法;

    image.png

    没有 key 的 diff 算法

    image.png
    image.png

    1. 先获取短的节点列表长度,然后以此 length 进行遍历比较。
    2. 遍历到 f 和 c 发现不一样,就会让 f 替代掉 c 的位置,原 dom 的 c 和 d 则依次往后退一格。
      • 这和连续存储结构数组中间插入值的过程是一样的。
    3. 如果新节点列表比旧节点列表的节点少了,则旧 dom 多出来的节点将被删掉
    4. 最后将处理好的虚拟 dom 渲染成真实 dom。

      有 key 的 diff 算法

      image.png
      有了 key ,key 将会成为判断两个节点是否相同的重要依据之一。这样就提高了效率。

    5. 第一步的操作是从头开始进行遍历、比较:

    • a 和 b 是一致的会继续进行比较;
    • c 和 f 因为 key 不一致,所以就会 break 跳出循环;

    image.png

    1. 第二步的操作是从尾部开始进行遍历、比较:

    image.png

    1. 第三步是如果旧节点遍历完毕,发现有新的节点,那么就新增节点,并且挂载上去。
    • 这个比较就不是按位置顺序来了,而是按 key,这样就能尽可能的找到相同的节点
    • 挂载类似于链表插入,在中间新增了一个节点,将节点插入,而不需要移动后面的节点,效率很高。

    image.png

    1. 第四步是如果新的节点遍历完毕,发现有多于的旧节点,那么就移除旧节点:

    image.png

    1. 第五步是最特色的情况,中间还有很多未知的或者乱序的节点:这个时候 key 提升的效率更明显。
    • vue 将通过 key 建立索引,尽可能的复用旧节点

    image.png
    总结:Vue 在进行 diff 算法的时候,会尽量利用 key 来进行优化操作:

    • 在没有 key 的时候效率是非常低效的;
    • 在进行插入或者重置顺序的时候,保持相同的 key 可以让 diff 算法更加的高效;