重学vue3 - 模板语法

·  阅读 1597
重学vue3 - 模板语法

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

data

data属性是传入一个函数,并且该函数需要返回一个对象

data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理

这也就是为什么可以在template中访问data中定义的数据,并且在data中对应数据发生改变的时候自动去修改界面

methods

methods属性是一个对象,通常我们会在这个对象中定义很多的方法

这些方法可以被绑定到模板中,并且可以使用this关键字来直接访问到data中返回的对象的属性

也就是说methods中的函数再被进行调用的时候

会通过bind方法将函数内部的this给绑定为data方法返回的那个对象的代理对象

所以methods中的方法不推荐定义为箭头函数,尤其是在函数内部需要使用this的时候

mustache语法

React使用的jsx来编写界面,所以对应的代码都是编写的类似于js的一种语法,

之后通过Babel将jsx编译成 React.createElement 函数调用

虽然vue也支持使用JSX来编写界面,但是在大多数情况下,使用基于HTML的模板语法

在模板中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定在一起

在底层的实现中,Vue将模板编译成虚拟DOM渲染函数

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

Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式

<div id="app">
  <!-- mustache中可以使用data和methods等中定义的属性和方法 -->
  <h1>{{ count }}</h1>
  <!-- mustache可以很好的和原生html结构进行结合 -->
  <h2>count: {{ count }}</h2>
  <!-- mustache中支持任何的合法的JS表达式 -->
  <div>{{ count % 2 ? 'even' : 'odd' }}</div>
  <div>{{ changeCount(10) }}</div>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        count: 100
      }
    },
    methods: {
      changeCount(step) {
        return this.count + step
      }
    },
  }).mount('#app')
</script>
复制代码

指令

名称说明
v-once1. v-once用于指定元素或者组件只渲染一次
2. 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
3. 该指令可以用于性能优
v-memo1. 记住一个模板的子树, 元素和组件上都可以使用
2. 该指令接收一个固定长度的数组作为依赖值进行记忆比对
3. 如果数组中的每个值都和上次渲染的时候相同,则整个该子树的更新会被跳过
4. 带有空依赖数组的 v-memo (v-memo="[]") 在功能上等效于 v-once
5. 在v-for中使用v-memo,请确保v-memo和v-for作用于同一元素,在v-for中的v-memo是不生效的
v-text1. 用于更新元素的 textContent
2. v-text中的内容会覆盖掉元素原本的textContent
3. v-text没有mustache语法灵活,所以一般使用mustache语法来替换v-text
v-html1. 默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析
2. 如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示
3. 为了避免XSS攻击,请确保需要解析的html内容为安全有效的html内容
v-pre1. v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签
2. 跳过不需要编译的节点,加快编译的速度
v-cloak1. 这个指令保持在元素上直到关联组件实例结束编译
2. 当元素还未被解析完成时,其对应元素会存在属性v-cloak
和 CSS 规则如 [v-cloak] { display: none } 一起用时
这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕
v-bindv-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
v-on方法的动态绑定

v-once

<!-- 指令可以看出是一种特殊的元素属性 -->
<div v-once>
  <h1>{{ name }}</h1>
  <div>{{ count }}</div>
</div>
复制代码

v-memo

<div id="app">
  <!-- 当依赖数组中任何一项的值发生改变,那么v-memo所在的元素及其子元素会进行更新 -->
  <!-- 否则会跳过v-memo所在的元素及其子元素的vNode的创建,而是直接复用之前的vNode -->
  <!-- 本例中当变量name 和 age的值发生改变的时候,对应内容会更新 -->
  <!-- 当height对应的值发生改变的时候,对应的内容并不会进行任何的更新 -->
  <div v-memo="[name, age]">
    <div>{{ name }}</div>
    <div>{{ age }}</div>
    <div>{{ height }}</div>
  </div>

  <button @click="changeName">changeName</button>
  <button @click="changeHeight">changeHeight</button>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        name: 'Klaus',
        age: 23,
        height: '1.88'
      }
    },
    methods: {
      changeName() {
        this.name = 'Alex'
      },
      changeHeight() {
        this.height = '2.00'
      }
    },
  }).mount('#app')
</script>
复制代码

v-text

<div>{{ name }}</div>
<!-- 等价于 -->
<div v-text="name"></div>
复制代码

v-html

<!-- htmlContent: '<h1>this is h1 title</h1>' -->
<div v-html="htmlContent"></div>
复制代码

v-pre

<div v-pre>
  <h1>{{ name }}</h1>
  <div>{{ count }}</div>
</div>
复制代码

v-cloak

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

<div id="app">
  <div v-cloak>{{ count }}</div>
</div>

<script>
  setTimeout(() => {
    Vue.createApp({
      data() {
        return {
          count: 100
        }
      }
    }).mount('#app')
  }, 3000)
</script>
复制代码

v-bind

基本使用

<img v-bind:src="imgUrl" />

<!-- v-bind 存在对应的语法糖 为 : -->
<img :src="imgUrl" />
复制代码

绑定class

<!-- 普通方式进行绑定 -->
<!-- 在data选项中 -> classObj: 'active hover foo' -->
<div :class="classObj">foo</div>

<!-- class中也可以编写对应的表达式 -->
<div :class="isActive ? 'active' : ''">foo</div>

<!-- 
  动态绑定class对应的值的结果如果为boolean类型值,那么对应的结果会自动忽略
  所以这种方式的写法和三目运算符写法的作用是一致的,但是因为使用了vue的容错机制,所以并不推荐
-->
<div :class="isActive && 'active'">foo</div>

<!-- vue中的class编写支持对象写法 -->
<!--  
   对象的key --- class属性名
   对象的value --- 必须是一个boolean类型值
	      --- 如果值为true的时候,显示对应的class
	      --- 如果值为false的时候,不显示对应的class
-->
<div :class="{ active: isActive, hover: isHover }">foo</div>

<!--
	既然对象语法动态绑定class 对应的值 是 对象形式
	所以我们可以将对应的值 抽离到data选项中,或者methods选项中
-->

<!-- data选项中 fooClass: { active: this.isActive } -->
<div :class="fooClass">foo</div>

<!-- methods选项中 getFooClass() { return { active: this.isActive }  } -->
<div :class="getFooClass()">foo</div>

<!-- 动态绑定的class 可以和 普通的class 共存 -->
<div class="foo" :class="{ active: isActive }">foo</div>
复制代码
<!-- 动态绑定class 除了对象语法外 也有数组语法 -->
<div :class="['active', 'foo']">foo</div>

<!-- 数组语法中可以传入变量 -->
<div :class="['active', foo]">foo</div>

<!-- 数组语法中,也可以编写合法的js表达式 -->
<div :class="[isActive ? 'active' : '', 'foo']">foo</div>

<!-- 数组语法中,可以嵌套使用对象语法 -->
<div :class="[{ active: isActive }, 'foo']">foo</div>

<!-- 数组语法可以和普通class 共存 -->
<div class="foo" :class="[{ active: isActive }]">foo</div>
复制代码

绑定style

我们可以利用v-bind:style来绑定一些CSS内联样式,因为某些样式我们需要根据数据动态来决定,比如某段文字的颜色,大小等等

CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,使用引号括起来) 来命名

<!-- 使用style绑定对象 也是支持数组写法和对象写法的 -->
<!-- 
  基本使用方式和动态绑定class的用法是一致的
  在对象中可以使用变量,任意合法的js表达式
  或者可以将整个对象单独抽离后,在进行引用
-->
<div :style="{ color: 'red', 'font-size': fontSize + 'px' }">foo</div>

<!-- 
	动态绑定style 也是支持 数组写法的
  数组写法可以将多个样式对象应用到同一个元素上
-->
<div :style="[{ color: 'red' }, { fontSize: '30px' }]">foo</div>
复制代码

动态绑定属性

在某些情况下,我们属性的名称可能也不是固定的

如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义

这种绑定的方式,我们称之为动态绑定属性

<!-- 
    如果是动态绑定属性,必须使用v-bind指令
    这也就意味着属性值会被作为变量来进行解析
    如果属性值是字符串,那么需要单独使用引号进行包裹
-->
<div :[propName]="'foo'">foo</div>
复制代码

绑定一个对象

如果我们希望将一个对象的所有属性依次绑定到元素(或组件)上时,我们可以直接使用 v-bind 绑定一个对象

vue在解析的时候,会自动遍历对应的对象中的属性名和属性值,并依次作为属性添加到对应的组件或元素上

<!-- 在data选项中 info: { name: 'Klaus', age: 24 } -->
<!-- 直接使用v-bind 即可 不需要加上对应属性名 因为这里是绑定多个属性 -->
<div v-bind="info">foo</div>

<!-- 简写形式 - 不推荐,可读性较差 -->
<div :="info">foo</div>

<!-- 上述的写法等价于 -->
<div :name="info.name" :age="info.age">foo</div>
复制代码

v-on

在前端开发中,我们需要经常和用户进行各种各样的交互

此时我们可以使用v-on指令来监听用户发生的事件,比如点击、拖拽、键盘事件等等

<div id="app">
  <!-- 基本使用 -->
  <div v-on:click="handleClick"></div>

  <!-- 简写方式 -->
  <div @mousemove="handleMove"></div>

  <!-- 可以通过这个方式绑定多个事件 -->
  <div @click="handleClick" @mousemove="handleMove"></div>
  <!-- 也可以通过这个当时绑定多个事件 -->
  <div v-on="{ click: handleClick, mousemove: handleMove }"></div>
  <!-- 或使用简写方式(可读性较差,不推荐) -->
  <div @="{ click: handleClick, mousemove: handleMove }"></div>

  <!--
如果事件逻辑简单,可以将其以表达式的方式绑定到元素上(可读性差,不推荐)
在模板中,可以直接访问声明的数据状态,方法等
-->
  <div @click="count++">{{ count }}</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.min.js"></script>
<script>
  Vue.createApp({
    data() {
      return {
        count: 0
      }
    },
    methods: {
      handleClick() {
        console.log('handleClick')
      },
      handleMove() {
        console.log('handleMove')
      }
    }
  }).mount('#app')
复制代码

参数传递

<div id="app">
  <!-- 在没有参数需要传递的时候 直接输入对应的函数引用地址 -->
  <div @click="handleClick"></div>

  <!--
在需要传递参数的时候, 直接在函数名后通过小括号的形式进行调用
vue会在该函数外进行包裹,确保其在事件执行的时候才会被触发 (fun(p1, p1) -> () => { fun(p1, p2) })
如果我们自己传递了对应的参数,那么默认就不会在传递对应的事件对象
如果我们需要在传递参数的同时使用事件对象,可以使用内置对象$event
-->
  <div @click="handleParamsClick('Klaus', 18, $event)"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.min.js"></script>
<script>
  Vue.createApp({
    methods: {
      // 在没有参数的情况下 默认情况下vue会将对应的事件对象作为参数传递进来
      handleClick(e) {
        console.log(e)
      },

      handleParamsClick(name, age, event) {
        console.log(name, age, event)
      }
    }
  }).mount('#app')
复制代码

修饰符

v-on支持修饰符,修饰符相当于对事件进行了一些特殊的处理

修饰符说明
.stop调用 event.stopPropagation()
.prevent调用 event.preventDefault()
.capture添加事件侦听器时使用 capture 模式
.self只当事件是从侦听器绑定的元素本身触发时才触发回调
.{keyAlias} (例如: .enter)仅当事件是从特定键触发时才触发回调
.once只触发一次回调
.left只当点击鼠标左键时触发
.middle只当点击鼠标中键时触
.right只当点击鼠标右键时触发
.passive{ passive: true } 模式添加侦听器

条件渲染

在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,此时就可以使用条件渲染

<div id="app">
  <div v-if="users.length > 1">有多个嘉宾</div>
  <div v-else-if="users.length === 1">只有一个嘉宾</div>
  <div v-else>没有嘉宾</div>
</div>
复制代码

v-if 的渲染是惰性

  • 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉
  • 当条件为true时,才会真正渲染条件块中的内容

v-show

v-if 和 v-show 在功能上都是一致的, 都是根据一个条件决定是否显示元素或者组件

但是对于v-if, 当条件为false时,其对应的元素压根不会被渲染到DOM中

v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进行切换

所以对于需要在显示和隐藏之间频繁的切换的元素,推荐使用v-show

v-show是通过CSS的display属性来决定元素是否显示的,所以

  1. v-show是不支持template的
  2. v-show不可以和v-else一起使用

列表渲染

在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染

这个时候我们可以使用v-for来完成,v-for类似于JavaScript的for循环,可以用于遍历一组数据

v-for的基本格式是 "(item, index) in 数组"

  • 数组通常是来自data或者prop,也可以是其他方式

  • item是我们给每项元素起的一个别名,可以自定义设置

  • v-for的循环支持使用of关键字,其作用和in关键字是一样的

    (item, index) in 数组 === (item, index) of 数组

v-for可以用来遍历的数据是所有的 可迭代对象 和 数字 以及 普通对象

遍历对象

<div id="app">
  <template v-for="(value, key, index) in info">
    <div>{{ value }} - {{ key }} - {{ index }}</div>
  </template>
</div>

<script>
  Vue.createApp({
    data() {
      return {
        info: {
          name: 'Klaus',
          age: 23
        }
      }
    }
  }).mount('#app')
</script>
复制代码

遍历可迭代对象

<template v-for="(value, index) in 'Klaus'">
  <div>{{ value }} - {{ index }}</div>
</template>
复制代码

遍历数字

<template v-for="(value, index) in 20">
  <div>{{ value }} - {{ index }}</div>
</template>
复制代码

template

如果我们需要在多个元素上添加相同的指令,例如

<h1 v-if="showInfo">Klaus</h1>
<h1 v-if="showInfo">24</h1>

<!-- 渲染后的结果为 -->
<h1>Klaus</h1>
<h1>24</h1>
复制代码

此时在每个元素上添加相同指令必然是十分繁琐的,所以我们会使用一个div元素进行包裹

目的是为了将其中的那些元素,作为div的子元素进行统一管理

<div v-if="showInfo">
  <h1>Klaus</h1>
  <h1>24</h1>
</div>

<!-- 渲染后的结果为 -->
<div>
  <h1>Klaus</h1>
  <h1>24</h1>
</div>
复制代码

但是这么做,依旧存在一个很大的弊端,也就是我们额外引入了一个div元素

很多情况下,在渲染的时候,我们并不希望渲染这个额外的div元素

此时我们可以使用template元素

template元素可以当做不可⻅的包裹元素,一般和指令结合使用,在最终的渲染结构中,template并不会被最终渲染出来

<template v-if="showInfo">
    <h1>Klaus</h1>
    <h1>24</h1>
</template>

<!-- 渲染后的结果为 -->
<h1>Klaus</h1>
<h1>24</h1>
复制代码

key

在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性

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

Vnode

VNode的全称是Virtual Node,也就是虚拟节点,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode

VNode的本质是一个JavaScript的对象

image.png

Vue在解析模板的时候,会将template在内存中解析成一个对应的JS对象,并使用children属性 将多个js对象联系起来,而这个JS对象 就是vNode(虚拟节点)

和真实DOM一样,vnode和vnode之间也会形成对应的树结构,而对应的树结构,而这个树结构就被称之为VDOM,也就是虚拟DOM

本质上,虚拟DOM和真实DOM是一一对应的

image.png

使用虚拟DOM的好处

  1. 虚拟DOM本质上就是一个JS对象,所以可以将其渲染成任何形式的内容,进而实现vue的跨平台

    例如: 在浏览器中,可以将vDOM渲染成真实DOM。在移动设备上,可以将vDOM渲染成对应的原生控件

  2. 和浏览器原生DOM对象不一样的是,vDOM是一个轻量级对象,且vDOM可以保存在内存中,进行新旧dom的diff算法

    所以使用vDOM进行中间渲染环节,可以有效的减少浏览器的重绘和回流

插入F的案例

需求

假设存在列表,对应其中的元素为a, b, c, d, e

现在想在b后边插入一个值为f的li节点

此时vue就会根据是否存在对应的唯一的key而做出不同的操作

在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表,因为在整个列表中其实存在很多元素是相同的,可能只是位置发生了改变,重新删除并创建整个元素看起来是没有必要的

当没有key的时候

类型
原始节点a b c d e
改动后新节点a b f c d e

vue在没有key的时候会依次进行遍历

  1. a节点和b节点是没有任何的改变的,所以vue会对其进行复用

  2. 比对到c节点的时候,因为没有key,所以vue并不知道c节点仅仅只是移动了位置

    所以此时vue会将c节点的内容修改为f

  3. d和e节点执行和c节点类似的操作

  4. 新建一个节点,将节点的值设置为e

image.png

当存在key的时候

  1. 从头开始进行遍历,复用可以复用的元素

image.png

  • a和b是一致的,vue会复用a节点和b节点
  • c和f因为key不一致,所以就会break跳出循环
  1. 从后向前进行遍历,复用可以复用的元素

image.png

  1. 如果旧节点遍历完毕,但是依然有新的节点,那么就新增节点

image.png

  1. 如果新的节点遍历完毕,但是依然有旧的节点,那么就移除旧节点

image.png

  1. 如果中间还有很多未知的或者乱序的节点,vue会使用最长递增子序列等方法,尽可能的移动节点以找到最长的可以复用的子序列进行复用

image.png

所以Vue在进行diff算法的时候,会尽量利用我们的key来进行优化操作。在进行插入或者重置顺序的时候,为元素添加唯一的key可以让diff算法更加的高效

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改