Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码

文章目录

      • 一、简介
      • 二、Vue2和Vue3区别
      • 三、Vue知识点学习
        • 一、Vue基础-几个基本属性介绍和简单案例
          • 1、template属性
          • 2、data属性
          • 3、methods属性
          • 4、计数器案例
        • 二、Vue基础-模板语法
          • 1、模板语法
          • 2、基本指令
          • 3、v-bind和v-on
          • 4、条件渲染
          • 5、列表渲染
          • 6、VNode
        • 三、Vue3的Options-API
          • 1、计算属性computed
          • 2、侦听器watch
        • 四、知识点综合小案例
      • 四、TypeScript知识点
      • 五、项目实战
      • 六、项目打包和自动化部署
      • 七、沿途学习代码地址及案例地址
        • 1、沿途学习代码地址
        • 2、项目案例地址
      • 八、知识拓展
        • 1、ES6数组与对象的解构赋值详解
          • 数组的解构赋值
          • 对象的解构赋值
          • 字符串的解构赋值
        • 2、JavaScript的 ...(展开运算符)
        • 3、export 'defineEmit' (imported as 'defineEmit') was not found in 'vue'
      • 九、其他知识学习
        • 1、Webpack学习
        • 2、数据可视化-echarts
        • 3、Vue2学习
        • 4、JavaScript面向对象和设计模式
        • 5、微前端学习

一、简介

Vue3+TypeScript从入门到进阶(一)——Vue3简介及介绍——附沿途学习案例及项目实战代码

二、Vue2和Vue3区别

Vue3+TypeScript从入门到进阶(二)——Vue2和Vue3的区别——附沿途学习案例及项目实战代码

三、Vue知识点学习

一、Vue基础-几个基本属性介绍和简单案例

1、template属性

在使用createApp的时候,我们传入了一个对象,接下来我们详细解析一下之前传入的属性分别代表什么含义。

**template属性:**表示的是Vue需要帮助我们渲染的模板信息:

  • 目前我们看到它里面有很多的HTML标签,这些标签会替换掉我们挂载到的元素(比如id为app的div)的innerHTML;

  • 模板中有一些奇怪的语法,比如 {{}},比如 @click,这些都是模板特有的语法,我们会在后面讲到;

但是这个模板的写法有点过于别扭了,并且IDE很有可能没有任何提示,阻碍我们编程的效率。

Vue提供了两种方式:

  • 方式一:使用script标签,并且标记它的类型为 x-template;

  • 方式二:使用任意标签(通常使用template标签,因为不会被浏览器渲染),设置id;

    template元素是一种用于保存客户端内容的机制,该内容再加载页面时不会被呈现,但随后可以在运行时使用JavaScript实例化;

方式一:使用script标签

// template写法一
<body>
  
  <div id="app">哈哈哈哈啊</div>

  <script type="x-template" id="why">
    <div>
      <h2>{{message}}</h2>
      <h2>{{counter}}</h2>
      <button @click='increment'>+1</button>
      <button @click='decrement'>-1</button>
    </div>
  </script>

  <script src="../js/vue.js"></script>
  <script>
    document.querySelector("#why")
    Vue.createApp({
      template: '#why',
      data: function() {
        return {
          message: "Hello World",
          counter: 100
        }
      },
      // 定义各种各样的方法
      methods: {
        increment() {
          console.log("点击了+1");
          this.counter++;
        },
        decrement() {
          console.log("点击了-1");
          this.counter--;
        }
      }
    }).mount('#app');
  </script>
</body>

方式二:使用template

// template写法二
<body>
  
  <div id="app"></div>

  <template id="why">
    <div>
      <h2>{{message}}</h2>
      <h2>{{counter}}</h2>
      <button @click='increment'>+1</button>
      <button @click='decrement'>-1</button>
      <button @click="btnClick">按钮</button>
    </div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    document.querySelector("#why")
    Vue.createApp({
      template: '#why',
      data: function() {
        return {
          message: "Hello World",
          counter: 100
        }
      },
      // 定义各种各样的方法
      methods: {
        increment() {
          console.log("点击了+1");
          this.counter++;
        },
        decrement() {
          console.log("点击了-1");
          this.counter--;
        },
        btnClick: () => {
          // this === window? 不可以
          // 写成一个箭头函数时, 这个this就是window
          // 在箭头函数中是不绑定this, 但是函数中如果使用了this
          console.log(this);
        },
        btn2Click: function() {
          // this === window? 不可以
          // 写成一个箭头函数时, 这个this就是window
          // 在箭头函数中是不绑定this, 但是函数中如果使用了this
          console.log(this);
        }
      }
    }).mount('#app');

  </script>
</body>

这个时候,在createApp的对象中,我们需要传入的template以 # 开头:

  • 如果字符串是以 # 开始,那么它将被用作 querySelector,并且使用匹配元素的 innerHTML 作为模板字符串;
2、data属性

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

  • 在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数);

  • 在Vue3.x的时候,必须传入一个函数,否则就会直接在浏览器中报错;

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

  • 所以我们在template中通过 {{counter}} 访问counter,可以从对象中获取到数据;

  • 所以我们修改counter的值时,template中的 {{counter}}也会发生改变;

具体这种响应式的原理,我们后面会有专门的篇幅来讲解。

3、methods属性

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

  • 这些方法可以被绑定到 template 模板中;

  • 在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性;

对于有经验的同学,在这里我提一个问题,官方文档有这么一段描述:

  • 问题一:为什么不能使用箭头函数(官方文档有给出解释)?

  • 问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第1张图片

当然,这里还可以定义很多其他的属性,我们会在后续进行讲解:

  • 比如props、computed、watch、emits、setup等等;

  • 也包括很多的生命周期函数;

不用着急,我们会一个个学习它们的。

4、计数器案例

如果我们希望实现一个计数器的案例:

  • 点击+1,那么内容会显示数字+1;

  • 点击-1,那么内容会显示数字-1;

我们可以选择很多种方式来实现:

  • 在这里我们就对比原生和Vue的实现方式的不同。

计数器原生实现

<body>
  <h2 class="counter">0</h2>
  <button class="increment">+1</button>
  <button class="decrement">-1</button>

  <script>
    // 1.获取所有的元素
    const counterEl = document.querySelector(".counter");
    const incrementEl = document.querySelector(".increment");
    const decrementEl = document.querySelector(".decrement");

    // 2.定义变量
    let counter = 100;
    counterEl.innerHTML = counter;

    // 3.监听按钮的点击
    incrementEl.addEventListener("click", () => {
      counter += 1;
      counterEl.innerHTML = counter;
    });
    decrementEl.addEventListener("click", () => {
      counter -= 1;
      counterEl.innerHTML = counter;
    });
  </script>
</body>

计数器Vue实现

// 计数器案例-Vue
<body>
  
  <div id="app">哈哈哈哈啊</div>

  <script src="../js/vue.js"></script>
  <script>
    Vue.createApp({
      template: `
        

{{message}}

{{counter}}

`
, data: function() { return { message: "Hello World", counter: 100 } }, // 定义各种各样的方法 methods: { increment() { console.log("点击了+1"); this.counter++; }, decrement() { console.log("点击了-1"); this.counter--; } } }).mount('#app'); </script> </body>

二、Vue基础-模板语法

methods方法绑定this

问题回顾:

  • 问题一:为什么不能使用箭头函数(官方文档有给出解释)?

  • 问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第2张图片

问题一:不能使用箭头函数?

我们在methods中要使用data返回对象中的数据:

  • 那么这个this是必须有值的,并且应该可以通过this获取到data返回对象中的数据。

那么我们这个this能不能是window呢?

  • 不可以是window,因为window中我们无法获取到data返回对象中的数据;

  • 但是如果我们使用箭头函数,那么这个this就会是window了;

为什么是window呢?

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

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

this到底是如何查找和绑定的呢?

  • 在我的公众号有另外一篇文章,专门详细的讲解了this的绑定规则;

  • https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA;

  • 认真学习之后你绝对对this的绑定一清二楚;

问题二:this到底指向什么?

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第3张图片

VSCode代码片段

我们在前面练习Vue的过程中,有些代码片段是需要经常写的,我们再VSCode中我们可以生成一个代码片段,方便我们快速生成。

VSCode中的代码片段有固定的格式,所以我们一般会借助于一个在线工具来完成。

具体的步骤如下:

  • 第一步,复制自己需要生成代码片段的代码;

  • 第二步,https://snippet-generator.app/在该网站中生成代码片段;

  • 第三步,在VSCode中配置代码片段;

代码片段过程

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第4张图片

1、模板语法

React的开发模式:

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

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

Vue也支持jsx的开发模式(后续有时间也会讲到):

  • 但是大多数情况下,使用基于HTML的模板语法;

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

  • 在底层的实现中,Vue将模板编译成虚拟DOM渲染函数,这个我会在后续给大家讲到;

所以,对于学习Vue来说,学习模板语法是非常重要的。

Mustache双大括号语法

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

  • 并且我们前端提到过,data返回的对象是有添加到Vue的响应式系统中;

  • 当data中的数据发生改变时,对应的内容也会发生更新。

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

另外这种用法是错误的:

<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>
2、基本指令

v-once指令

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

  • 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过;

  • 该指令可以用于性能优化;

    <h2 v-once>{{counter}}</h2>
    <button @click="increment">+1</button>
    

如果是子节点,也是只会渲染一次:

<div v-once>
  <h2>{{counter}}</h2>
  <h2>{{message}}</h2>
</div>
<button @click="increment">+1</button>

v-text指令

用于更新元素的 textContent:

<template id="my-app">
  <h2 v-text="message"></h2>
  <!--等价于-->
  <h2>{{message}}</h2>
</template>

v-html

默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。

  • 如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第5张图片

v-pre

v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:

  • 跳过不需要编译的节点,加快编译的速度;
<template id="my-app">
  <h2 v-pre>{{message}}</h2>
</template>

v-cloak

这个指令保持在元素上直到关联组件实例结束编译。

  • 和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-cloak>{{message}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
<div> 不会显示,直到编译结束。
3、v-bind和v-on

v-bind的绑定属性

前端讲的一系列指令,主要是将值插入到模板内容中。

但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定

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

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

绑定属性我们使用v-bind:

  • 缩写::

  • 预期:any (with argument) | Object (without argument)

  • 参数:attrOrProp (optional)

  • 修饰符

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

  • 用法:动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

绑定基本属性

v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍);

在开发中,有哪些属性需要动态进行绑定呢?

  • 还是有很多的,比如图片的链接src、网站的链接href、动态绑定一些类、样式等等
<div id="app"></div>

<!-- vue2 template模板中只能有一个根元素 -->
<!-- vue3 是允许template中有多个根元素 -->
<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="">
  <img src="imgUrl" alt="">
</template>

v-bind有一个对应的语法糖,也就是简写方式。

在开发中,我们通常会使用语法糖的形式,因为这样更加简洁。

绑定class介绍

在开发中,有时候我们的元素class也是动态的,比如:

  • 当数据为某个状态时,字体显示红色。

  • 当数据另一个状态时,字体显示黑色。

绑定class有两种方式:

  • 对象语法

  • 数组语法

绑定class – 对象语法

**对象语法:**我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class。

<template id="my-app">
  <div :class="className">哈哈哈哈</div>
  <!-- 对象语法: {'active': boolean} -->
  <div :class="{'active': isActive}">呵呵呵呵</div>
  <button @click="toggle">切换</button>

  <!-- 也可以有多个键值对 -->
  <div :class="{active: isActive, title: true}">呵呵呵呵</div>

  <!-- 默认的class和动态的class结合 -->
  <div class="abc cba" :class="{active: isActive, title: true}">
    呵呵呵呵
  </div>

  <!-- 将对象放到一个单独的属性中 -->
  <div class="abc cba" :class="classObj">呵呵呵呵</div>

  <!-- 将返回的对象放到一个methods(computed)方法中 -->
  <div class="abc cba" :class="getClassObj()">呵呵呵呵</div>      
</template>

绑定class – 数组语法

**数组语法:**我们可以把一个数组传给 :class,以应用一个 class 列表;

<template id="my-app">
  <div :class="['abc', title]">哈哈哈哈</div>
  <div :class="['abc', title, isActive ? 'active': '']">哈哈哈哈</div>
  <div :class="['abc', title, {active: isActive}]">哈哈哈哈</div>
</template>

绑定style介绍

我们可以利用v-bind:style来绑定一些CSS内联样式:

  • 这次因为某些样式我们需要根据数据动态来决定;

  • 比如某段文字的颜色,大小等等;

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

绑定class有两种方式:

  • 对象语法

  • 数组语法

绑定style演练

对象语法:

<template id="my-app">
  <!-- :style="{cssPropertyName: cssPropertyValue}" -->
  <div :style="{color: finalColor, 'font-size': '30px'}">哈哈哈哈</div>
  <div :style="{color: finalColor, fontSize: '30px'}">哈哈哈哈</div>
  <div :style="{color: finalColor, fontSize: finalFontSize + 'px'}">哈哈哈哈</div>

  <!-- 绑定一个data中的属性值, 并且是一个对象 -->
  <div :style="finalStyleObj">呵呵呵呵</div>
  <!-- 调用一个方法 -->
  <div :style="getFinalStyleObj()">呵呵呵呵</div>
</template>

数组语法:

  • :style 的数组语法可以将多个样式对象应用到同一个元素上;
<template id="my-app">
  <div :style="[style1Obj, style2Obj]">哈哈哈</div>
  <img :src="" alt="">
  <a :href=""></a>
  <div :class></div>
</template>

动态绑定属性

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

  • 前端我们无论绑定src、href、class、style,属性名称都是固定的;

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

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

<template id="my-app">
  <div :[name]="value">哈哈哈</div>
</template>

绑定一个对象

如果我们希望将一个对象的所有属性,绑定到元素上的所有属性,应该怎么做呢?

  • 非常简单,我们可以直接使用 v-bind 绑定一个 对象;

案例:info对象会被拆解成div的各个属性

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

<script src="../js/vue.js"></script>
<script>
  const App = {
    template: '#my-app',
    data() {
      return {
        info: {
          name: "why",
          age: 18,
          height: 1.88
        }
      }
    }
  }

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

v-on绑定事件

前面我们绑定了元素的内容和属性,在前端开发中另外一个非常重要的特性就是交互。

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

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

  • 在Vue中如何监听事件呢?使用v-on指令。

接下来我们来看一下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 } 模式添加侦听器

  • 用法:绑定事件监听

v-on的基本使用

我们可以使用v-on来监听一下点击的事件:

<!-- 完整写法: v-on:监听的事件="methods中方法" -->
<button v-on:click="btn1Click">按钮1</button>
<div class="area" v-on:mousemove="mouseMove">div</div>

<!-- 绑定一个表达式: inline statement -->
<button @click="counter++">{{counter}}</button>

v-on:click可以写成@click,是它的语法糖写法:

<!-- 语法糖 -->
<button @click="btn1Click">按钮1</button>

当然,我们也可以绑定其他的事件:

<!--绑定鼠标移动事件-->
<div @mouseover="mouseMove">div的区域</div>

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

<!-- 绑定一个对象 -->
<div class="area" v-on="{click: btn1Click, mousemove: mouseMove}"></div>
<div class="area" @="{click: btn1Click, mousemove: mouseMove}"></div>

v-on参数传递

当通过methods中定义方法,以供@click调用时,需要注意参数问题

情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。

  • 但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去

情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。

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

<script>
  const App = {
    template: '#my-app',
    data() {
      return {
        message: "Hello World"
      }
    },
    methods: {
      btn1Click(event) {
        console.log(event);
      },
      btn2Click(event, name, age) {
        console.log(name, age, event);
      }
    }
  }

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

v-on的修饰符

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

修饰符

.stop - 调用 event.stopPropagation()。

.prevent - 调用 event.preventDefault()。

.capture - 添加事件侦听器时使用 capture 模式。

.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。

.{keyAlias} - 仅当事件是从特定键触发时才触发回调。

.once - 只触发一次回调。

.left - 只当点击鼠标左键时触发。

.right - 只当点击鼠标右键时触发。

.middle - 只当点击鼠标中键时触发。

.passive - { passive: true } 模式添加侦听器

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

  <template id="my-app">
    <div @click="divClick">
      <button @click.stop="btnClick">按钮</button>
    </div>
    <input type="text" @keyup.enter="enterKeyup">
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      },
      methods: {
        divClick() {
          console.log("divClick");
        },
        btnClick() {
          console.log('btnClick');
        },
        enterKeyup(event) {
          console.log("keyup", event.target.value);
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
4、条件渲染

在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了。

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

  • v-if

  • v-else

  • v-else-if

  • v-show

下面我们来对它们进行学习。

v-if、v-else、v-else-if

v-if、v-else、v-else-if用于根据条件来渲染某一块的内容:

  • 这些内容只有在条件为true时,才会被渲染出来;

  • 这三个指令与JavaScript的条件语句if、else、else if类似;

<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元素

因为v-if是一个指令,所以必须将其添加到一个元素上:

  • 但是如果我们希望切换的是多个元素呢?

  • 此时我们渲染div,但是我们并不希望div这种元素被渲染;

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

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

  • 有点类似于小程序中的block
<template id="my-app">
  <template v-if="isShowHa">
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
  </template>

  <template v-else>
    <h2>呵呵呵呵</h2>
    <h2>呵呵呵呵</h2>
    <h2>呵呵呵呵</h2>
  </template>
</template>

v-show

v-show和v-if的用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件:

<template id="my-app">
  <h2 v-show="isShow">哈哈哈哈</h2>
</template>

v-show和v-if的区别

首先,在用法上的区别:

  • v-show是不支持template;

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

其次,本质的区别:

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

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

开发中如何进行选择呢?

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

  • 如果不会频繁的发生切换,那么使用v-if;

5、列表渲染

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

  • 这个时候我们可以使用v-for来完成;

  • v-for类似于JavaScript的for循环,可以用于遍历一组数据;

v-for基本使用

v-for的基本格式是 “item in 数组”:

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

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

我们知道,在遍历一个数组的时候会经常需要拿到数组的索引:

  • 如果我们需要索引,可以使用格式: “(item, index) in 数组”;

  • 注意上面的顺序:数组元素项item是在前面的,索引项index是在后面的;

<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>

v-for支持的类型

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

  • 一个参数: “value in object”;

  • 二个参数: “(value, key) in object”;

  • 三个参数: “(value, key, index) in object”;

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

  • 每一个item都是一个数字;
<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元素

类似于v-if,你可以使用 template 元素来循环渲染一段包含多个元素的内容:

  • 我们使用template来对多个元素进行包裹,而不是使用div来完成;
<template id="my-app">
  <ul>
    <template v-for="(value, key) in info">
      <li>{{key}}</li>
      <li>{{value}}</li>
      <li class="divider"></li>
    </template>
  </ul>
</template>

数组更新检测

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

  • push()

  • pop()

  • shift()

  • unshift()

  • splice()

  • sort()

  • reverse()

替换数组的方法

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

v-for中的key是什么作用?

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

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

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

  • 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;

  • 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;

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

  • 什么是新旧nodes,什么是VNode?

  • 没有key的时候,如何尝试修改和复用的?

  • 有key的时候,如何基于key重新排列的?

认识VNode

我们先来解释一下VNode的概念:

  • 因为目前我们还没有比较完整的学习组件的概念,所以目前我们先理解HTML元素创建出来的VNode;

  • VNode的全称是Virtual Node,也就是虚拟节点;

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

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第6张图片

虚拟DOM

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第7张图片

插入F的案例

我们先来看一个案例:这个案例是当我点击按钮时会在中间插入一个f;

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

  <template id="my-app">
    <ul>
      <li v-for="item in letters" :key="item">{{item}}</li>
    </ul>
    <button @click="insertF">插入F元素</button>
  </template>

  <script src="../js/vue.js"></script>
  <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>
</body>

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

  • 在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表;

  • 因为对于列表中 a、b、c、d它们都是没有变化的;

  • 在操作真实DOM的时候,我们只需要在中间插入一个f的li即可;

那么Vue中对于列表的更新究竟是如何操作的呢?

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

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

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

Vue源码对于key的判断

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第8张图片

没有key的操作(源码)

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第9张图片

没有key的过程如下

我们会发现上面的diff算法效率并不高:

  • c和d来说它们事实上并不需要有任何的改动;

  • 但是因为我们的c被f所使用了,所有后续所有的内容都要一次进行改动,并且最后进行新增;

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第10张图片

有key执行操作(源码)

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第11张图片

有key的diff算法如下

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

  • a和b是一致的会继续进行比较;

  • c和f因为key不一致,所以就会break跳出循环;

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第12张图片

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第13张图片

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第14张图片

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第15张图片

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

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第16张图片

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

  • 在没有key的时候我们的效率是非常低效的;

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

三、Vue3的Options-API

1、计算属性computed

复杂data的处理方式

我们知道,在模板中可以直接通过插值语法显示一些data中的数据

但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;

  • 比如我们需要对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示;

  • 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算;

  • 在模板中放入太多的逻辑会让模板过重和难以维护;

  • 并且如果多个地方都使用到,那么会有大量重复的代码;

我们有没有什么方法可以将逻辑抽离出去呢?

  • 可以,其中一种方式就是将逻辑抽取到一个method中,放到methods的options中;

  • 但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用;

  • 另外一种方式就是使用计算属性computed;

认识计算属性computed

什么是计算属性呢?

  • 官方并没有给出直接的概念解释;

  • 而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性

  • 计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例;

计算属性的用法:

  • 选项:computed

  • 类型:{ [key: string]: Function | { get: Function, set: Function } }

那接下来我们通过案例来理解一下这个计算属性。

案例实现思路

我们来看三个案例:

案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示;

案例二:我们有一个分数:score

  • 当score大于60的时候,在界面上显示及格;

  • 当score小于60的时候,在界面上显示不及格;

案例三:我们有一个变量message,记录一段文字:比如Hello World

  • 某些情况下我们是直接显示这段文字;

  • 某些情况下我们需要对这段文字进行反转;

我们可以有三种实现思路:

  • 思路一:在模板语法中直接使用表达式;

  • 思路二:使用method对逻辑进行抽取;

  • 思路三:使用计算属性computed;

思路一的实现:模板语法

  • 缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);

  • 缺点二:当有多次一样的逻辑时,存在重复的代码;

  • 缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存;

// 01_三个案例的实现-插值语法
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{firstName + " " + lastName}}</h2>
    <h2>{{score >= 60 ? '及格': '不及格'}}</h2>
    <h2>{{message.split(" ").reverse().join(" ")}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant",
          score: 80,
          message: "Hello World"
        }
      }
    }

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

思路二的实现:method实现

  • 缺点一:我们事实上先显示的是一个结果,但是都变成了一种方法的调用;

  • 缺点二:多次使用方法的时候,没有缓存,也需要多次计算;

// 02_三个案例的实现-methods
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{getFullName()}}</h2>
    <h2>{{getResult()}}</h2>
    <h2>{{getReverseMessage()}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant",
          score: 80,
          message: "Hello World"
        }
      },
      methods: {
        getFullName() {
          return this.firstName + " " + this.lastName;
        },
        getResult() {
          return this.score >= 60 ? "及格": "不及格";
        },
        getReverseMessage() {
          return this.message.split(" ").reverse().join(" ");
        }
      }
    }

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

思路三的实现:computed实现

p注意:计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;

  • 我们会发现无论是直观上,还是效果上计算属性都是更好的选择;

  • 并且计算属性是有缓存的;

// 03_三个案例的实现-computed
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{fullName}}</h2>
    <h2>{{result}}</h2>
    <h2>{{reverseMessage}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant",
          score: 80,
          message: "Hello World"
        }
      },
      computed: {
        // 定义了一个计算属性叫fullname
        fullName() {
          return this.firstName + " " + this.lastName;
        },
        result() {
          return this.score >= 60 ? "及格": "不及格";
        },
        reverseMessage() {
          return this.message.split(" ").reverse().join(" ");
        }
      }
    }

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

计算属性 vs methods

在上面的实现思路中,我们会发现计算属性和methods的实现看起来是差别是不大的,而且我们多次提到计算属性有缓存的。

接下来我们来看一下同一个计算多次使用,计算属性和methods的差异:

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第17张图片

计算属性的缓存

这是什么原因呢?

  • 这是因为计算属性会基于它们的依赖关系进行缓存;

  • 在数据不发生变化时,计算属性是不需要重新计算的;

  • 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算;

计算属性的setter和getter

计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数

但是,如果我们确实想设置计算属性的值呢?

  • 这个时候我们也可以给计算属性设置一个setter的方法;
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <button @click="changeFullName">修改fullName</button>
    <h2>{{fullName}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant"
        }
      },
      computed: {

        // fullName 的 getter方法
        fullName() {
          return this.firstName + " " + this.lastName;
        },
        
        // fullName的getter和setter方法
        fullName: {
          get: function() {
            return this.firstName + " " + this.lastName;
          },
          set: function(newValue) {
            console.log(newValue);
            const names = newValue.split(" ");
            this.firstName = names[0];
            this.lastName = names[1];
          }
        }
      },
      methods: {
        changeFullName() {
          this.fullName = "Coder Why";
        }
      }
    }

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

源码如何对setter和getter处理呢?

你可能觉得很奇怪,Vue内部是如何对我们传入的是一个getter,还是说是一个包含setter和getter的对象进行处理的呢?

  • 事实上非常的简单,Vue源码内部只是做了一个逻辑判断而已;

Vue3+TypeScript从入门到进阶(三)——Vue3基础知识点(上)——附沿途学习案例及项目实战代码_第18张图片

2、侦听器watch

认识侦听器watch

什么是侦听器呢?

  • 开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;

  • 当数据变化时,template会自动进行更新来显示最新的数据;

  • 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了;

侦听器的用法如下:

  • **选项:**watch

  • 类型:{ [key: string]: string | Function | Object | Array}

侦听器案例

举个例子:

  • 比如现在我们希望用户在input中输入一个问题;

  • 每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案;

  • 那么,我们就需要实时的去获取最新的数据变化;

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

  <template id="my-app">
    您的问题: <input type="text" v-model="question">
    <!-- <button @click="queryAnswer">查找答案</button> -->
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          // 侦听question的变化时, 去进行一些逻辑的处理(JavaScript, 网络请求)
          question: "Hello World",
          anwser: ""
        }
      },
      watch: {
        // question侦听的data中的属性的名称
        // newValue变化后的新值
        // oldValue变化前的旧值
        question: function(newValue, oldValue) {
          console.log("新值: ", newValue, "旧值", oldValue);
          this.queryAnswer();
        }
      },
      methods: {
        queryAnswer() {
          console.log(`你的问题${this.question}的答案是哈哈哈哈哈`);
          this.anwser = "";
        }
      }
    }

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

侦听器watch的配置选项

我们先来看一个例子:

  • 当我们点击按钮的时候会修改info.name的值;

  • 这个时候我们使用watch来侦听info,可以侦听到吗?答案是不可以。

这是因为默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:

  • 这个时候我们可以使用一个选项deep进行更深层的侦听;

  • 注意前面我们说过watch里面侦听的属性对应的也可以是一个Object;

还有另外一个属性,是希望一开始的就会立即执行一次

  • 这个时候我们使用immediate选项;

  • 这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;

watch: {
  // 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
  // 深度侦听/立即执行(一定会执行一次)
  info: {
    handler: function(newInfo, oldInfo) {
      console.log("newValue:", newInfo.nba.name, "oldValue:", oldInfo.nba.name);
    },
    deep: true, // 深度侦听
    // immediate: true // 立即执行
  }
},

侦听器watch的其他方式(一)

  watch: {
    // 方法名
    b: 'someMethod',
    // 你可以传入回调数组,它们会被逐一调用
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],

侦听器watch的其他方式(二)

另外一个是Vue3文档中没有提到的,但是Vue2文档中有提到的是侦听对象的属性

"info.name": function(newName, oldName) {
  console.log(newName, oldName);
}

还有另外一种方式就是使用 $watch 的API:

我们可以在created的生命周期(后续会讲到)中,使用 this.$watchs 来侦听;

  • 第一个参数是要侦听的源;

  • 第二个参数是侦听的回调函数callback;

  • 第三个参数是额外的其他选项,比如deep、immediate;

created() {
  const unwatch = this.$watch("info", function(newInfo, oldInfo) {
    console.log(newInfo, oldInfo);
  }, {
    deep: true,
    immediate: true
  })
  // unwatch()
}

四、知识点综合小案例

现在我们来做一个相对综合一点的练习:书籍购物车

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67LNDw8u-1653814410134)(C:\WUYUXIN\Code\Vue3\typora\Vue3+TypeScript从入门到进阶(三)—附沿途学习案例.assets\image-20220302165245679.png)]

案例说明:

  • 1.在界面上以表格的形式,显示一些书籍的数据;

  • 2.在底部显示书籍的总价格;

  • 3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);

  • 4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <template v-if="books.length > 0">
      <table>
        <thead>
          <th>序号</th>
          <th>书籍名称</th>
          <th>出版日期</th>
          <th>价格</th>
          <th>购买数量</th>
          <th>操作</th>
        </thead>
        <tbody>
          <tr v-for="(book, index) in books">
            <td>{{index + 1}}</td>
            <td>{{book.name}}</td>
            <td>{{book.date}}</td>
            <td>{{formatPrice(book.price)}}</td>
            <td>
              <button :disabled="book.count <= 1" @click="decrement(index)">-</button>
              <span class="counter">{{book.count}}</span>
              <button @click="increment(index)">+</button>
            </td>
            <td>
              <button @click="removeBook(index)">移除</button>
            </td>
          </tr>
        </tbody>
      </table>
      <h2>总价格: {{formatPrice(totalPrice)}}</h2>
    </template>
    <template v-else>
      <h2>购物车为空~</h2>
    </template>
  </template>

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

</body>
</html>
// index.js
Vue.createApp({
  template: "#my-app",
  data() {
    return {
      books: [
        {
          id: 1,
          name: '《算法导论》',
          date: '2006-9',
          price: 85.00,
          count: 1
        },
        {
          id: 2,
          name: '《UNIX编程艺术》',
          date: '2006-2',
          price: 59.00,
          count: 1
        },
        {
          id: 3,
          name: '《编程珠玑》',
          date: '2008-10',
          price: 39.00,
          count: 1
        },
        {
          id: 4,
          name: '《代码大全》',
          date: '2006-3',
          price: 128.00,
          count: 1
        },
      ]
    }
  },
  computed: {
    // vue2: filter/map/reduce
    totalPrice() {
      let finalPrice = 0;
      for (let book of this.books) {
        finalPrice += book.count * book.price;
      }
      return finalPrice;
    },
    // Vue3不支持过滤器了, 推荐两种做法: 使用计算属性/使用全局的方法
    filterBooks() {
      return this.books.map(item => {
        const newItem = Object.assign({}, item);
        newItem.price = "¥" + item.price;
        return newItem;
      })
    }
  },
  methods: {
    increment(index) {
      // 通过索引值获取到对象
      this.books[index].count++
    },
    decrement(index) {
      this.books[index].count--
    },
    removeBook(index) {
      this.books.splice(index, 1);
    },
    formatPrice(price) {
      return "¥" + price;
    }
  }
}).mount("#app");
// style.css
table {
  border: 1px solid #e9e9e9;
  border-collapse: collapse;
  border-spacing: 0;
}

th, td {
  padding: 8px 16px;
  border: 1px solid #e9e9e9;
  text-align: left;
}

th {
  background-color: #f7f7f7;
  color: #5c6b77;
  font-weight: 600;
}

.counter {
  margin: 0 5px;
}

Vue3+TypeScript从入门到进阶(四)——Vue3基础知识点(中)——附沿途学习案例及项目实战代码

Vue3+TypeScript从入门到进阶(五)——Vue3基础知识点(下)——附沿途学习案例及项目实战代码

四、TypeScript知识点

Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码

五、项目实战

Vue3+TypeScript从入门到进阶(七)——项目实战——附沿途学习案例及项目实战代码

六、项目打包和自动化部署

Vue3+TypeScript从入门到进阶(八)——项目打包和自动化部署——附沿途学习案例及项目实战代码

七、沿途学习代码地址及案例地址

1、沿途学习代码地址

https://gitee.com/wu_yuxin/vue3-learning.git

2、项目案例地址

https://gitee.com/wu_yuxin/vue3-ts-cms.git

在这里插入图片描述
在这里插入图片描述

八、知识拓展

1、ES6数组与对象的解构赋值详解

数组的解构赋值

基本用法

ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构(Destructuring)

// 以前为变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
// ES6允许写成这样
var [a,b,c] = [1,2,3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

下面是一些使用嵌套数组进行解构的例子:

let [foo,[[bar],baz]] = [1,[[2],3]];
foo // 1
bar // 2
baz // 3
let [,,third] = ["foo","bar","baz"];
third // "baz"
let [head,...tail] = [1,2,3,4];
head // 1
tail // [2,3,4]
let [x,y,...z] = ['a'];
x // "a"
y // undefined
z // []

默认值

解构赋值允许制定默认值

var [foo = true] = [];
foo // true
[x,y='b'] = ['a'];
// x='a', y='b'

注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。

所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

var [x=1] = [undefined];
x //1
var [x=1] = [null];
x // null

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值:

function f(){
 console.log('aaa');
}
let [x=f()] = [1];

上面的代码中,因为x能取到值,所以函数f()根本不会执行。上面的代码其实等价于下面的代码:

let x;
if([1][0] === undefined){
 x = f();
}else{
 x = [1][0];
}

默认值可以引用解构赋值的其他变量,但该变量必须已经声明:

let [x=1,y=x] = [];
// x=1; y=1
let [x=1,y=x] = [2];
// x=2; y=2
let [x=1,y=x] = [1,2];
// x=1; y=2
let [x=y,y=1] = []; // ReferenceError

上面最后一个表达式,因为x用到默认值是y时,y还没有声明。

对象的解构赋值

1、最简单的案例

看下面的案例

 let person = {
     name: 'yhb',
     age: 20
 }
 /*
 注意:下面虽然看起来是创建了一个对象,对象中有两个属性 name 和 age
 但是:其实是声明了两个变量
 name:等于对象person 中的name属性的值
 age:等于对象person 中的 age属性的值
 */
 let { name, age } = person
 console.log(name,age)

如上面注释中所说,声明了变量 name和age,然后分别从对象person中寻找与变量同名的属性,并将属性的值赋值给变量

所以,这里的关键,就是首先要知道对象中都有哪些属性,然后再使用字面量的方式声明与其同名的变量

2、属性不存在怎么办
如果不小心声明了一个对象中不存在的属性怎么办?

或者,实际情况下,可能是我们就是想再声明一个变量,但是这个变量也不需要从对象中获取值,这个时候,此变量的值就是 undefined

let person = {
    name: 'yhb',
    age: 20
}   
let { name, age,address } = person
console.log(name,age,address)

此时,可以给变量加入一个默认值

let { name, age,address='北京' } = person

3、属性太受欢迎怎么办

当前声明了 name 和 age 变量,其值就是person对象中name和age属性的值,如果还有其他变量也想获取这两个属性的值怎么办?

 let { name, age, address = '北京' } = person
 console.log(name, age, address)
 let { name, age } = person
 console.log(name, age)

上面的方法肯定不行,会提示定义了重复的变量 name 和 age

那怎么办呢?

难道只能放弃结构赋值,使用老旧的方式吗?

let l_name=person.name
let l_age=person.age
console.log(l_name,l_age)

其实不然!

let {name:l_name,age:l_age}=person
console.log(l_name,l_age)

说明:

声明变量 l_name 并从对象person中获取name属性的值赋予此变量
声明变量 l_age, 并从对象person中获取age属性的值赋予此变量
这里的重点是下面这行代码

let {name:l_name,age:l_age}=person

按照创建对象字面量的逻辑,name 为键,l_name 为值。但注意,这里是声明变量,并不是创建对象字面量,所以争取的解读应该是

声明变量 l_name,并从person 对象中找到与 name 同名的属性,然后将此属性的值赋值给变量 l_name

所以,我们最后输出的是变量 l_name和l_age

console.log(l_name,l_age)

当然这种状态下,也是可以给变量赋予默认值的

let { name:l_name, age:l_age, address:l_address='北京' }=person

4、嵌套对象如何解构赋值

let person = {
 name: 'yhb',
 age: 20,
 address: {
     province: '河北省',
     city: '保定'
 }
}
// 从对象 person 中找到 address 属性,并将值赋给变量 address
let {address}=person
// 从对象 address 中找到 province 属性,并将值赋给变量 province
let {province}=address
console.log(province)

上面代码一层层的进行结构赋值,也可以简写为如下形式

let {address:{province}}=person  

从peson 对象中找到 address 属性,取出其值赋值给冒号前面的变量 address,然后再将 变量address 的值赋值给 冒号 后面的变量 {province},相当于下面的写法

let {province}=address
字符串的解构赋值

1、字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

2、JavaScript的 …(展开运算符)

三个连续的点具有两个含义:展开运算符(spread operator)和剩余运算符(rest operator)。

展开运算符

展开运算符允许迭代器在接收器内部分别展开或扩展。迭代器和接收器可以是任何可以循环的对象,例如数组、对象、集合、映射等。你可以把一个容器的每个部分分别放入另一个容器。

const newArray = ['first', ...anotherArray];

剩余参数

剩余参数语法允许我们将无限数量的参数表示为数组。命名参数的位置可以在剩余参数之前。

const func = (first, second, ...rest) => {};

用例

定义是非常有用的,但是很难仅从定义中理解概念。我认为用日常用例会加强对定义的理解。

复制数组

当我们需要修改一个数组,但又不想改变原始数组(其他人可能会使用它)时,就必须复制它。

const fruits = ['apple', 'orange', 'banana'];
const fruitsCopied = [...fruits]; // ['apple', 'orange', 'banana']

console.log(fruits === fruitsCopied); // false

// 老方法
fruits.map(fruit => fruit);

它正在选择数组中的每个元素,并将每个元素放在新的数组结构中。我们也可以使用 map 操作符实现数组的复制并进行身份映射。

唯一数组

如果我们想从数组中筛选出重复的元素,那么最简单的解决方案是什么?

Set 对象仅存储唯一的元素,并且可以用数组填充。它也是可迭代的,因此我们可以将其展开到新的数组中,并且得到的数组中的值是唯一的。

const fruits = ['apple', 'orange', 'banana', 'banana'];
const uniqueFruits = [...new Set(fruits)]; // ['apple', 'orange', 'banana']

// old way
fruits.filter((fruit, index, arr) => arr.indexOf(fruit) === index);

串联数组
可以用 concat 方法连接两个独立的数组,但是为什么不再次使用展开运算符呢?

const fruits = ['apple', 'orange', 'banana'];
const vegetables = ['carrot'];
const fruitsAndVegetables = [...fruits, ...vegetables]; // ['apple', 'orange', 'banana', 'carrot']
const fruitsAndVegetables = ['carrot', ...fruits]; // ['carrot', 'apple', 'orange', 'banana']

// 老方法
const fruitsAndVegetables = fruits.concat(vegetables);
fruits.unshift('carrot');

将参数作为数组进行传递

当传递参数时,展开运算符能够使我们的代码更具可读性。在 ES6 之前,我们必须将该函数应用于 arguments。现在我们可以将参数展开到函数中,从而使代码更简洁。

const mixer = (x, y, z) => console.log(x, y, z);
const fruits = ['apple', 'orange', 'banana'];

mixer(...fruits); // 'apple', 'orange', 'banana'

// 老方法
mixer.apply(null, fruits);

数组切片

使用 slice 方法切片更加直接,但是如果需要的话,展开运算符也可以做到。但是必须一个个地去命名其余的元素,所以从大数组中进行切片的话,这不是个好方法。

const fruits = ['apple', 'orange', 'banana'];
const [apple, ...remainingFruits] = fruits; // ['orange', 'banana']

// 老方法
const remainingFruits = fruits.slice(1);

将参数转换为数组
Javascript 中的参数是类似数组的对象。你可以用索引来访问它,但是不能调用像 map、filter 这样的数组方法。参数是一个可迭代的对象,那么我们做些什么呢?在它们前面放三个点,然后作为数组去访问!

const mixer = (...args) => console.log(args);
mixer('apple'); // ['apple']

将 NodeList 转换为数组
参数就像从 querySelectorAll 函数返回的 NodeList 一样。它们的行为也有点像数组,只是没有对应的方法。

[...document.querySelectorAll('div')];

// 老方法
Array.prototype.slice.call(document.querySelectorAll('div'));

复制对象
最后,我们介绍对象操作。复制的工作方式与数组相同。在以前它可以通过 Object.assign 和一个空的对象常量来实现。

const todo = { name: 'Clean the dishes' };
const todoCopied = { ...todo }; // { name: 'Clean the dishes' }
console.log(todo === todoCopied); // false

// 老方法
Object.assign({}, todo);

合并对象
合并的唯一区别是具有相同键的属性将被覆盖。最右边的属性具有最高优先级。

const todo = { name: 'Clean the dishes' };
const state = { completed: false };
const nextTodo = { name: 'Ironing' };
const merged = { ...todo, ...state, ...nextTodo }; // { name: 'Ironing', completed: false }

// 老方法
Object.assign({}, todo, state, nextTodo);

需要注意的是,合并仅在层次结构的第一级上创建副本。层次结构中的更深层次将是相同的引用。

将字符串拆分为字符
最后是字符串。你可以用展开运算符把字符串拆分为字符。当然,如果你用空字符串调用 split 方法也是一样的。

const country = 'USA';
console.log([...country]); // ['U', 'S', 'A']

// 老方法
country.split('');

3、export ‘defineEmit’ (imported as ‘defineEmit’) was not found in ‘vue’

在这里插入图片描述
在这里插入图片描述
在学习vue3的顶层编写方式时的父子组件通信的时候,我们会看到一些比较老(2020、2021年初)的博客里面会有使用defineEmit的,但是如果我们用比较新版本的Vue3的话,就会报错。原因是,新版本的Vue3将defineEmit改成了defineEmits了

九、其他知识学习

1、Webpack学习

Webpack从入门到进阶(一)—附沿路学习案例代码

Webpack从入门到进阶(二)—附沿路学习案例代码

Webpack从入门到进阶(三)—附沿路学习案例代码

2、数据可视化-echarts

数据可视化-echarts入门、常见图表案例、超详细配置解析及项目案例

3、Vue2学习

Vue项目开发-仿蘑菇街电商APP

Vue 知识点汇总(上)–附案例代码及项目地址

Vue 知识点汇总(下)–附案例代码及项目地址

4、JavaScript面向对象和设计模式

JavaScript面向对象编程浅析

JavaScript设计模式浅析

5、微前端学习

SingleSpa及qiankun入门、源码分析及案例

你可能感兴趣的:(Web前端,笔记,Vue,typescript,学习,javascript)