Skip to content

Vue3开发基础语法

预备知识

Vue3+TS系统学习三 - Vue3开发基础语法(一)

methods 中的 this

  • 这里涉及到箭头函数使用 this 的查找规则,它会在自己的上层作用域中查找 this

  • 最终刚好找到的是 script 作用域中的 this,所以就是 window

    前端面试之彻底搞懂this指向

js
const App = {
  template: "#my-app",
  data() {
    return {
      counter: 0
    }
  },
  methods: {
    increment: () => {
      // this.counter++;
      console.log(this); // window
    },
    decrement() {
      this.counter--;
      console.log(this);
    }
  }
}

事实上 Vue 的源码当中就是对 methods 中所有函数进行了遍历,并且通过 bind 绑定了 this

image-20220622144947794

VSCode 代码片段

有些代码片段是需要经常写的,我们在 VSCode 中可以生成一个代码片段,方便我们快速生成

image-20220622150245149image-20220622150340913image-20220622150818885

具体步骤如下:

  1. 复制自己需要生成代码片段的代码
  2. https://snippet-generator.app/ 在该网站中生成代码片段
  3. 在 VSCode 中配置代码片段

Vue3 基础模板语法

React 开发模式

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

Vue 也支持 jsx 的开发模式

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

插值语法

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

html
<!-- 1.mustache的基本使用 -->
<h2>{{ message }}</h2>
<!-- 2.是一个表达式 -->
<h2>{{ counter * 10 }}</h2>
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
<!-- 3.也可以调用函数 -->
<h2>{{ getReverseMessage() }}</h2>
<!-- 4.三元运算符 -->
<h2>{{ isShow ? "哈哈哈": "" }}</h2>

基础指令

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

  • 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
  • 该指令可以用于性能优化
html
<div v-once>
  <h2>{{ counter }}</h2>
  <h2>{{ message }}</h2>
</div>

v-text:用于更新元素的 textContent

v-html:默认情况下,如果我们展示的内容本身是 html 的,那么 vue 并不会对其进行特殊的解析,如果希望这个内容被 Vue 可以解析出来,可以使用这个

v-pre:用于跳过元素和它的子元素的编译过程,显示原始的 Mustache 标签,跳过不需要编译的节点,加快编译的速度

v-cloak:保持在元素上直到关联组件实例结束编译

  • 和如下 CSS 规则一起使用
html
<style>
  [v-cloak] {
    display: none;
  }
</style>

v-bind 绑定属性

某些属性希望动态来绑定

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

v-bind:绑定一个或多个属性值,或者向另一个组件传递 props 值

  • 缩写: :

  • 修饰符:

    .camel:将 kebab-case attribute 名转换为 camelCase

html
<template id="my-app">
  <!-- 1.v-bind的基本使用 -->
  <img v-bind:src="imgUrl" alt="">
  <a v-bind:href="link">百度一下</a>

  <!-- 2.v-bind提供一个语法糖 : -->
  <img :src="imgUrl" alt="">
</template>

绑定 class 的两种方式

  • 对象语法

    html
    <template id="my-app">
      <div :class="{ active: isActive, title: true }">哈哈哈</div>
      <!-- 默认的class和动态的class结合 -->
      <div class="abc cba" :class="classObj">呵呵呵</div>
      <!-- 将返回的对象放到一个methods(computed)方法中 -->
      <div class="abc cba" :class="getClassObj()">喵喵喵</div>   
    </template>
  • 数组语法

    html
    <div :class="['abc', title, isActive ? 'active': '']">哈哈哈</div>
    <div :class="['abc', title, { active: isActive }]">呵呵呵</div>

绑定 style 的两种方式

  • 对象语法

    html
    <div :style="{ color: finalColor, 'font-size': '30px' }">哈哈哈</div>
    <div :style="{ color: finalColor, fontSize: '30px' }">呵呵呵</div>
  • 数组语法

    html
    <div :style="[style1Obj, style2Obj]">哈哈哈</div>

动态绑定属性

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

  • 前端我们不论绑定 src、href、class、style 属性名称都是固定的
  • 如果 属性名称不是固定 的,我们可以使用 :[属性名]="值" 的格式来定义
html
<div :[name]="value">哈哈哈</div>

绑定一个对象

如果我们希望将一个对象的所有属性,绑定到元素上的所有属性

html
<template id="my-app">
  <div v-bind="info">哈哈哈哈</div>
  <div :="info">哈哈哈哈</div>
</template>

<script>
  const App = {
    data() {
      return {
        info: {
          name: "why",
          age: 18,
          height: 1.88
        }
      }
    }
  }
</script>

v-on 绑定事件

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

  • 这个时候,我们就必须监听用户发生的事件,比如:点击、拖拽、键盘事件等

v-on 监听事件:

  • 缩写:@
  • 修饰符:
    • .stop - 调用 event.stopPropagation()
    • .prevent - 调用 event.preventDefault()
    • .capture - 添加事件侦听器时使用 capture 模式
    • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调
    • .{keyAlias} - 仅当事件是从特定键触发时才触发回调
    • .once - 只触发一次回调
    • .left - 只当点击鼠标左键时触发
    • .right - 只当点击鼠标右键时触发
    • .middle - 只当点击鼠标中键时触发
    • .passive - { passive: true } 模式添加侦听器
html
<button v-on:click="btn1Click">按钮1</button>
 <!-- 语法糖 -->
<button @click="btn1Click">按钮1</button>
<!-- 绑定一个表达式: inline statement -->
<button @click="counter++">{{counter}}</button>
<!-- 绑定一个对象 -->
<div class="area" v-on="{ click: btn1Click, mousemove: mouseMove }">
<div class="area" @="{click: btn1Click, mousemove: mouseMove}"></div></div>

v-on 参数传递

  • 如果该方法不需要额外参数,那么方法后的 () 可以不添加

    如果方法本身有一个参数,那么默认将原生事件 event 参数传递进去

  • 如果同时传入某个参数,同时需要 event 时,可以通过 $event 传入事件

html
<!-- 默认传入event对象, 可以在方法中获取 -->
<button @click="btn1Click">按钮1</button>
<!-- $event可以获取到事件发生时的事件对象 -->
<button @click="btn2Click($event, 'coderwhy', 18)">按钮2</button>

v-on 修饰符

html
<div @click="divClick">
  <button @click.stop="btnClick">按钮</button>
</div>
<input type="text" @keyup.enter="enterKeyup">

Vue3 基础模板语法2

条件渲染

Vue 提供了下面的指令来进行条件判断:

  • v-if
  • v-eles
  • v-else-if
  • v-show
html
<template id="my-app">
  <input type="text" v-model="score">
  <h2 v-if="score > 90">优秀</h2>
  <h2 v-else-if="score > 60">良好</h2>
  <h2 v-else>不及格</h2>
</template>

v-if 的渲染原理:

  • v-if 是惰性的
  • 当条件为 false 时,其判断的内容完全不会被渲染活着呗销毁掉
  • 当条件为 true 时,才会真正渲染条件块中的内容

template元素

  • 如果我们希望切换的是多个元素,此时我们渲染 div,但是我们不希望 div 这个元素被渲染

    这个时候,我们可以选择使用 template

  • template 元素可以当做不可见的包裹元素,并且在 v-if 上使用,但是最终 template 不会被渲染出来

    有点类似小程序中的 block

v-show 与 v-if 区别

  • 用法区别

    v-show 不支持 template

    v-show 不可以和 v-else 一起使用

  • 本质区别

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

    v-if 当条件为 false,其对应的原生压根不会被渲染到 DOM 中

开发中如何进行选择呢?

  • 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show
  • 如果不会频繁的发生切换,那么使用v-if

列表渲染

v-for 基本格式:"item in 数组"

  • 数组通常是来自 data 或者 prop,也可以是其他方式
  • item 是我们给每项元素起的一个别名,这个别名可以自定来定义
  • 数组也可以用 of 替代 in 作为分隔符

v-for 也支持遍历对象,并且支持有三个参数:

  • 一个参数:"value in object"
  • 二个参数:"(value, key) in object"
  • 三个参数:"(value, key, index) in object"

v-for 同时也支持数字的遍历

  • 每一个 item 都是一个数字
html
<template id="my-app">
  <h2>电影列表</h2>
  <ul>
    <!-- 遍历数组 -->
    <li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
  </ul>
  <h2>个人信息</h2>
  <ul>
    <!-- 遍历对象 -->
    <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
  </ul>
  <h2>遍历数字</h2>
  <ul>
    <li v-for="(num, index) in 10">{{num}}-{{index}}</li>
  </ul>
</template>

数组更新检测

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

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

上面的方法会直接修改原来的数组,但是某些方法不会替换原来的数组,而是生成新的数组,比如:filter()concat()slice()

key 和 diff 算法

列表渲染-维护状态

在使用 v-for 进行列表渲染时,我们通常会给元素或者组件绑定一个 key 属性,下面先来看一下官方解释

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

官方解释对于初学者来说并不好理解,比如下面问题:

  • 什么是新旧 nodes,什么是 VNode?
  • 没有 key 的时候,如何尝试修改和复用的?
  • 有 key 的时候,如何基于 key 重新排列的?

VNode 和 虚拟 DOM

  • 可以先理解 HTML 元素创建出来的 VNode
  • VNode 的全称是 Virtual Node,也就是虚拟节点
  • 事实上,无论是组件还是元素,它们最终在 Vue 中表示出来的都是一个个 VNode
  • VNode 本质是一个 JavaScript 的对象

image-20220627105406469

  • 如果我们不只是一个简单的 div,而是有一大堆的元素,那么它们应该形成一个 VNode Tree

image-20220627105652606

案例

我们可以确定的是,这次更新对于 ul 和 button 是不需要进行更新,需要更新的是我们li的列表:

  • 在 Vue 中,对于相同父元素的子元素节点并不会重新渲染整个列表
  • 因为对于列表中 a、b、c 它们都是没有变化的
  • 在操作真实 DOM 的时候,我们只需要在中间插入一个 f 的 li 即可
html
<template id="my-app">
  <ul>
    <li v-for="item in letters" :key="item">{{item}}</li>
  </ul>
  <button @click="insertF">插入F元素</button>
</template>

<script>
  const App = {
    template: '#my-app',
    data() {
      return {
        letters: ['a', 'b', 'c', 'd']
      }
    },
    methods: {
      insertF() {
        this.letters.splice(2, 0, 'f')
      }
    }
  }

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

Vue 中对于列表更新究竟如何操作:

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

Vue 对于没有 key 的操作

Vue 源码对于 key 的判断

image-20220627142640666

没有 key 的操作

image-20220627150432621

可以发现上面的 diff 算法效率并不高

  • c 和 d 来说它们事实上并不需要有任何的改动
  • 但是因为我们的 c 被 f 使用了,后续所有内容那个都需要一次进行改动,并且最后进行新增

image-20220627151229743

Vue 对于有 key 的操作

image-20220627153426548

第一步

image-20220627160321997

第一步的操作是从头开始进行遍历:

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

image-20220627160604975

第二步

image-20220627160743698

第二步的操作是从尾部开始进行遍历:

image-20220627160652671

第三步

image-20220627164356377

第三步是如果旧节点遍历完毕,但是依然有新的节点,那么就新增节点:

image-20220627160858839

第四步

image-20220627164409032

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

image-20220627164253383

第五步

image-20220627170020440

第五步是最特色的情况,中间还有很多未知的或者乱序的节点:

image-20220627164319073

Vue 在进行 diff 算法的时候,会尽量利用我们的 key 来进行优化操作:

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

常备不懈,才能有备无患