第7章 Vue.js

7.1 Vue概述

7.1.1 Vue简介

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。官网为:https://cn.vuejs.org/

Vue作者:尤雨溪

Vue的两个核心功能:

  • 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
  • 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM

7.1.2 Vue开发环境

1、Node.js

Node.js(https://node.org.cn)是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,可以使 JavaScript 运行在服务器端。使用 Node.js,可以方便地开发服务器端应用程序,如 Web 应用、API、后端服务,还可以通过 Node.js 构建命令行工具等。相比于传统的服务器端语言(如 PHP、Java、Python 等),Node.js 具有以下特点:

  • 单线程,但是采用了事件驱动、异步 I/O 模型,可以处理高并发请求。
  • 轻量级,使用 C++ 编写的 V8 引擎让 Node.js 的运行速度很快。
  • 模块化,Node.js 内置了大量模块,同时也可以通过第三方模块扩展功能。
  • 跨平台,可以在 Windows、Linux、Mac 等多种平台下运行。
  1. 安装方式见教材p195

  2. 检查是否安装完成

    node -v
    

    在这里插入图片描述

2、npm 包管理工具

NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于后端的Maven 。

安装node,自动安装npm包管理工具!

  1. 配置阿里镜像

    npm config set registry https://registry.npmmirror.com
    
  2. 确认配置已生效

    npm config get registry
    
  3. npm常用命令

    npm -v # 查看npm的版本
    
    npm install 包名 #可以简写为“npm i 包名”,用于为项目安装指定名称的包。如果加上-g选项,则会把包安装为全局包,否则只安装到本项目中。
    
    npm uninstall 包名 #用于卸载指定名称的包。
    
    npm update 包名 #用于更新指定名称的包。
    

3、Vue CLI

Vue CLI是一个Vue的官方命令行工具,它提供了构建、开发和部署Vue应用的脚手架,用于快速、便捷地创建和管理Vue项目。Vue CLI参考

  1. 安装Vue CLI,具体命令如下:

    npm install -g @vue/cli # 全局安装vue脚手架
    
  2. 查看安装是否成功

    vue -V
    

    在这里插入图片描述

7.2 Vue项目的创建和执行过程

7.2.1 Vue项目的创建

(1)打开用于存放项目的文件夹,在地址栏中输入cmd后按“Enter”键,在当前目录下打开命令提示符窗口,并通过如下命令创建一个名称为“chapter07”的项目。

vue create chapter07

(2)执行完上述命令后,需要选择项目创建的预设配置:选择Vue3,按”Enter“键

在这里插入图片描述

(3)命令行窗口会继续提示选择什么包管理工具:选择”Use NPM“,按”Enter“键

在这里插入图片描述

(4)完成创建

在这里插入图片描述

(5)通过“cd chapter07”命令进入到项目的目录,然后执行“npm run serve”命令启动chapter07项目的服务。

在这里插入图片描述

(6)chapter07项目启动成功后,可以通过本地服务器中的8080端口进行访问。在浏览器中访问http://localhost:8080/进入项目的欢迎页面。(Ctrl+C停止项目运行)

在这里插入图片描述

(7)使用IDEA工具打开chapter07项目,可以看到一个默认生成的项目目录结构。

在这里插入图片描述

chapter07/ # 项目根目录
├── node_modules/ # 项目的依赖库目录,通过npm install安装的所有依赖包都存放在这里
├── public/ # 静态资源目录,需要使用绝对路径访问
│ ├── index.html # 默认的主渲染页面文件,同时也是页面的入口文件
├── src/ # 源代码目录,我们编写代码的地方
│ ├── assets/ # 资源目录,存放图片、字体、样式等资源,需要使用相对路径访问
│ ├── components/ # 组件目录,存放可复用的Vue组件
│ │ ├── HelloWorld.vue # 示例组件,一个Vue单文件组件
│ ├── App.vue # 应用的根组件,所有其他组件都将作为其子组件
│ └── main.js # 应用入口文件,在这里创建Vue应用实例并挂载到DOM
├── package.json # 项目配置文件,定义了项目的基本信息、依赖和脚本命令
└── vue.config.js # Vue CLI的配置文件,用于自定义构建配置和开发服务器配置

配置参考 | Vue CLI

(8)打开IDEA终端工具运行Vue项目配置IDEA工具快捷运行Vue项目(可选)

在这里插入图片描述

7.2.2 Vue项目的执行过程

当执行“npm run serve”命令启动一个Vue项目时,项目会通过main.js文件将App.vue组件渲染到index.html文件指定的区域,从而在页面上显示内容。

  • App.vue:Vue项目是由各种组件组成的,App.vue是项目的根组件。在根组件中可以引入其他组件,从而显示其他组件的内容

    <template>
      <img alt="Vue logo" src="./assets/logo.png">
      <!-- 使用子组件 -->
      <HelloWorld msg="Welcome to Your Vue.js App"/>
    </template>
    
    <script>
    // 导入子组件
    import HelloWorld from './components/HelloWorld.vue'
    // 导出组件,Vue组件书写风格之一:选项式API(Options API)
    export default {
      name: 'App',
      components: {
        HelloWorld
      }
    }
    </script>
    <!--样式-->
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
  • main.js:main.js文件是项目的入口文件,该文件创建了Vue应用实例,Vue应用实例是Vue项目工作的基础

    // 导入createApp()函数,用于创建Vue应用实例
    import { createApp } from 'vue'
    // 导入App.vue,并保存为App变量
    import App from './App.vue'
    // 创建Vue应用实例,并挂载到index.html文件中的#app元素上
    createApp(App).mount('#app')
    
  • index.html:index.html文件是页面的入口文件,它负责加载和初始化项目所需的资源和组件,为用户提供一个与网站交互的页面。

    <!DOCTYPE html>
    <html lang="">
      <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">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
      <body>
        <noscript>
          <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    
    • 虽然在 index.html 中看不到 main.js 的引入,但在构建过程中,main.js 的内容会被打包并自动添加到 HTML 页面中

7.3 Vue开发基础

7.3.1 单文件组件

什么是VUE的组件?

  • 一个页面作为整体,是由多个部分组成的,每个部分在这里就可以理解为一个组件
  • 每个.vue文件就可以理解为一个组件,多个.vue文件可以构成一个整体页面
  • 组件化给我们带来的另一个好处就是组件的复用和维护非常的方便
    在这里插入图片描述

什么是.vue文件?

  • 传统的页面有.html文件.css文件和.js文件三个文件组成(多文件组件)

  • vue将这文件合并成一个.vue文件(Single-File Component,简称 SFC,单文件组件)

  • .vue文件对js/css/html统一封装,这是VUE中的概念 该文件由三个部分组成 <script> <template> <style>

    • template标签 代表组件的html部分代码 代替传统的.html文件
    • script标签 代表组件的js代码 代替传统的.js文件
    • style标签 代表组件的css样式代码 代替传统的.css文件
    <template>
      <!-- 组件的模板代码 -->
    </template>
    
    <script setup>
    // 组件的逻辑代码
    </script>
    
    <style scoped>
    /* 组件的样式代码 */
    </style>
    

7.3.2 数据绑定

1、定义数据

在一个Web应用中,页面中的数据通常是动态变化的,为了更方便地处理这些数据,Vue提供了数据绑定功能,能够将数据轻松地绑定到DOM中。绑定数据首先需要在

<template>
  {{ 数据名 }}
</template>
<script>
export default {
  setup(){
    return{
      数据名:数据值,
      // ……
    }
  }
}
</script>

setup()函数是Vue3中特有的,在该函数中可以定义数据和方法,并且要通过一个return关键字返回一个对象,用于将对象中的数据暴露给模板和组件实例;

<script>
export default {
  setup() {
    let count = 1
    let person =  {
      name: '张三',
      age: 18
    }
    let flag = true
    return {
      count,
      person,
      flag
    }
  }
}
</script>

为了让代码更简洁,Vue3中还提供了setup语法糖(Syntactic Sugar),它可以简化在<script>标签中定义数据的语法格式,具体格式如下(:

<!--setup语法糖-->
<script setup>
  const count = 1
  const person = {
    name: '张三',
    age: 18
  }
  const flag = true
</script>

2、获取数据

Vue提供的Mustache语法(又称为双大括号语法)用于在模板中放入占位符。当页面渲染时,{{ 数据名 }}会被替换为对应的数据值。

在Mustache语法中,还可以将表达式的值作为输出内容,表达式的值可以是字符串、数字等类型,示例代码如下。

<template>
  <div>
    <button @click="count++">Count is {{ count }}</button><br>
    {{ 'Hello Vue.js' }}<br>
    {{ count + 1 }}<br>
    {{ person.name }}<br>
    {{ flag ? 'a' : 'b' }}
  </div>
</template>

<script>
// 同上
</script>

7.3.3 ref()函数和reactive()函数

在Vue3中,数据默认是非响应式的,也就是说,将数据定义出来并在页面中显示后,如果后续修改了数据,页面中显示的数据不会同步更新。为此,Vue 3提供了两个函数ref()和reactive(),用于绑定响应式的数据。

1、ref()函数

ref()函数用于将数据转换为响应式数据。使用ref()函数定义响应式数据的语法格式如下。

<script setup>
  import {ref} from "vue"; // 引入ref()函数
  const count = ref(1) // 定义响应式数据
</script>

ref()函数处理的响应式数据在js编码修改的时候需要通过.value操作,而ref响应式数据在绑定到html上时不需要.value

<template>
  <div>
    <button @click="count++">Count is {{ count }}</button>
  </div>
</template>

<script setup>
  import {ref} from "vue";// 引入ref
  const count = ref(1)
  setTimeout(() => {
    count.value = 1000000
  }, 2000)
</script>

@click是Vue指令v-on的语法糖,v-on指令后文会讲解

修改代码:增加两个按钮,一个控制count加1,一个控制count减1

ESLint问题:①配置文件;②IDEA

Vue文件中的缩进问题,setting>Editor>code style>Vue template>Indent children of top-level tag: template,script,style

2、reactive()函数

reactive()函数用于创建一个响应式的对象或数组,具体语法格式如下。

<script setup>
  import {reactive} from "vue"; // 引入reactive
  // 定义响应式对象
  const person = reactive({
    name: '张三',
    age: 18
  })
</script>

函数中要操作reactive处理过的数据,需要通过 对象名.属性名的方式,在模板中使用时,也使用 对象名.属性名

<template>
  <div>
    姓名:{{ person.name }}<br>
    年龄:{{ person.age }}
  </div>
</template>

<script setup>
  import {reactive} from "vue"; // 引入reactive
  // 定义响应式对象
  const person = reactive({
    name: '张三',
    age: 18
  })
  // 2秒后修改数据
  setTimeout(() => {
    person.name = '王五'
    person.age = 20
  }, 2000)
</script>

3、ref()与reactive()

  • ref()可以定义响应式数据,包括对象,通常用于定义基本类型(如字符串、数字、布尔值)的响应式数据,而当用于对象时,实际上内部会调用reactive函数来使对象具有响应性。

  • 使用ref()函数来定义响应式对象时,修改对象也需要使用.value

    <script>
      import { ref } from 'vue'
      // 定义对象响应式
      const user = ref({
        name: '张三',
        age: 25,
        address: {
          city: '北京'
        }
      })
      // 访问和修改
      console.log(user.value.name)  // 获取
      user.value.name = '李四'      // 修改
      user.value.age = 26           // 修改
    </script>
    
  • reactive()函数 可以将一个普通对象转化为响应式对象,这样在数据变化时会自动更新界面,特别适用于处理复杂对象或者数据结构。

  • 综上所述,ref 适用与简单情形下的数据双向绑定,对于只有一个字符等基本类型数据或自定义组件等情况,建议可以使用 ref;而对于对象、函数等较为复杂的数据结构,以及需要递归监听的属性变化,建议使用 reactive。当然,在实际项目中根据需求灵活选择也是十分必要的。

7.4 Vue指令

在实际开发中,经常需要操作页面中的元素并与之进行交互,例如动态修改元素的文本内容、为元素绑定事件、控制元素的显示与隐藏等。为此,Vue提供了一系列指令,它们是以v-为前缀的特殊属性。通过这些指令,开发者能够使用更简洁的代码实现这些功能。

7.4.1 v-bind和v-model

1、v-bind

v-bind用于为元素属性动态地绑定属性值,v-bind的语法格式如下。

<标签名 v-bind:属性名="数据"></标签名>

上述语法格式还可以简写为如下形式。

<标签名 :属性名="数据"></标签名>

具体使用

<template>
  <div>
    <a
      v-bind:href='data.url'
      target="_self">
      <img
        style="width: 100px;background:#8c271e;"
        :src="data.logo"
        :title="data.name">
      <br>
      <input type="button"
             :value="`点击访问${data.name}`">
    </a>
  </div>
</template>

<script setup>
  const data = {
    name:'科成',
    url:"https://www.cduestc.cn/",
    logo:"https://www.cduestc.cn/image/main/logo.png"
  }
</script>

2、v-model

在处理表单信息时,经常需要将表单输入框中的内容同步给JavaScript中相应的变量,如果使用原生JavaScript代码实现,还需要手动选择输入框元素并添加事件监听器,代码会比较烦琐,而通过v-model可以非常方便地实现这一功能。

单项绑定和双向绑定

  • 单向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变不会同步更新到响应式数据
  • 双向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据
    • 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行
    • v-model专门用于双向绑定表单标签的value属性,语法为 v-model:value='',可以简写为 v-model=''
    • v-model还可以用于各种不同类型的输入,<textarea><select> 元素。

v-model用于实现表单元素的双向数据绑定,其语法格式如下。

<标签名 v-model="数据"></标签名>

具体使用

<template>
  <div>
    <input v-model="name"><br>
    欢迎你,{{ name }}
  </div>
</template>

<script setup>
  import {ref} from "vue";

  const name = ref('张三')
</script>

假设有一个表单,用于收集个人信息,代码应该如何修改?

<template>
  <div>
    姓名:<input v-model="person.name"><br>
    年龄:<input v-model="person.age"><br>
    性别:
    <select v-model="person.sex">
      <option value="男">男</option>
      <option value="女">女</option>
    </select> <br>
    <button @click="submit()">提交</button>
  </div>
</template>

<script setup>
  import {reactive} from "vue";

  const person = reactive({
    name: '',
    age: '',
    sex: ''
  })
  function submit() {
    // alert(`姓名:${person.name},年龄:${person.age},性别:${person.sex}`)
  }
</script>

7.4.2 v-on

我们可以使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。

语法格式如下:

<标签名 v-on:事件名="handler"></标签名>

可以简写为:

<标签名 @事件名="handler"></标签名>

handler的值可以是方法事件处理器,也可以是内联事件处理器,vue中的事件名=原生事件名去掉on 前缀 如:onclick --> click,事件参考:HTML DOM 事件对象 | 菜鸟教程

比如:

<!-- 方法事件处理器 -->
<button @click="submit">提交</button>
<!-- 内联事件处理器 -->
<button @click="count++">提交</button>

绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下:

  • .once:只触发一次事件。[重点]
  • .prevent:阻止默认事件。[重点]
  • .stop:阻止事件冒泡。
  • .capture:使用事件捕获模式而不是冒泡模式。
  • .self:只在事件发送者自身触发时才触发事件。

具体使用:

<template>
  <div>
    <h1>count的值是{{ count }}</h1>
    <!--方法事件处理器-->
    <button v-on:click="addCount()">addCount(方法事件处理器)</button>
    <!--内联事件处理器-->
    <button v-on:click="count++">count++(内联事件处理器)</button>
    <!--事件修饰符 once 只绑定事件一次-->
    <button @click.once="count++">addOnce(只触发一次)</button>
    <!--事件修饰符 prevent 组织组件的默认事件-->
    <a href="https://www.baidu.com" @click.prevent="count++">不会跳转百度</a>
    <!--原生js方式阻止组件默认行为-->
    <a href="https://www.baidu.com" @click.prevent="incrCount($event)">不会跳转百度</a>">
  </div>
</template>

<script setup>
  import {ref} from "vue";
  let count = ref(0)
  let addCount = () => {
    count.value++
  }
  let incrCount = (event) => {
    count.value++
    // 通过事件对象组织组件的默认行为
    event.preventDefault()
  }
</script>

事件绑定中的函数传参

  • 自动传递事件对象

    <template>
      <!-- 自动传递 $event 对象 -->
      <button @click="doThis">按钮1</button>
    
      <!-- 不传递 $event 对象 -->
      <button @click="doThis()">按钮2</button>
    
      <!-- 手动传递 $event 对象 -->
      <button @click="doThis($event)">按钮3</button>
    </template>
    <script setup>
      function doThis(event) {
      	console.log(event) 
        // 对于按钮1:MouseEvent 对象
        // 对于按钮2:undefined
        // 对于按钮3:MouseEvent 对象
    	}
    </script>
    
  • 传递自定义参数

    <template>
    	<!-- 可以传递自定义参数 -->
      <button @click="doThis('hello', 123)">按钮</button>
    
      <!-- 需要手动传递 $event 对象 -->
      <button @click="doThis('hello', 123, $event)">按钮</button>
    </template>
    
    <script setup>
      function doThis(arg1, arg2, event) {
        console.log(arg1, arg2, event) // 'hello', 123, MouseEvent
      }
    </script>
    

7.4.3 v-if和v-show

1、v-if

v-if用于根据条件决定是否添加或删除DOM元素来实现条件渲染。当条件为真时,元素会被添加到DOM中;当条件为假时,元素会从DOM中移除。

<标签名v-if="条件"></标签名>

也可以使用v-else-ifv-elsev-if 添加一个“else 区块”。注意一个 v-else-ifv-else 元素必须跟在一个 v-if 元素后面,否则它将不会被识别

<标签名v-if="条件A">元素A</标签名>
<标签名v-else-if="条件B">元素B</标签名>
<标签名v-else>元素C</标签名>

具体使用:定义一个输入框,根据输入的分数判定成绩等级

<template>
  <div>
    请输入您的分数:
    <input type="text" v-model="score"><br><br>
    您的等级为:
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
  </div>
</template>

<script setup>
  import {ref} from 'vue';
  const score = ref('50')
</script>

2、v-show

v-show也可以用于根据条件控制元素的显示与隐藏,但是它与v-if不同的是,v-show的原理是通过设置元素的样式属性display来控制它的显示与隐藏。当v-show的条件为假时,会为元素添加display:none样式,反之则不添加。

v-show的条件无论如何变化,元素都不会从DOM中移除,因此v-show更适合于频繁切换显示与隐藏的场景,因为它不需要频繁操作DOM,性能相对较高。

v-show的语法格式与v-if单独使用时相同。

<template>
  <div>
    <p v-if="flag">v-if控制的元素</p>
    <p v-show="flag">v-show控制的元素</p>
    <button @click="flag = !flag">显示/隐藏</button>
  </div>
</template>

<script setup>
  import {ref} from 'vue';
  const flag = ref(true)
</script>

注:

  • 打开浏览器控制台,查看元素,观察页面标签变化

    在这里插入图片描述

  • v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。

3、v-if vs v-show

  • v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。

  • v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

  • 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。

  • 总的来说,v-if 有更高的切换开销(元素的有无),而 v-show 有更高的初始渲染开销(CSS改变)。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

7.4.4 v-for

v-for是Vue中的列表渲染指令,用于基于一个数组、对象或数字来循环渲染一个列表。

使用v-for基于数组渲染列表的语法格式如下:

<标签名 v-for= "(item, [index]) in arr"></标签名>

使用v-for基于对象渲染列表的语法格式如下:

<标签名 v-for = "(value, [key], [index]) in obj"></标签名>

使用v-for基于数字渲染列表的语法格式如下。

<标签名 v-for = "(item, [index]) in num"></标签名>

具体使用:

<template>
  <div>
    基于数组的渲染列表:
    <!-- index表示索引,当然不是非得使用index这个单词 -->
    <!-- 语法规范弱的条件下,:key不写也可以,推荐写,可以提高渲染性能 -->
    <div v-for="(city, index) in cities" :key="index">
      索引{{ index }} -> {{ city }}
    </div>
    基于对象的渲染列表:
    <div v-for="(value, key) in person" :key="key">
      {{ key }} -> {{ value }}
    </div>
    基于数值的渲染列表:
    <div v-for="value in 5" :key="value">
      {{ value }}
    </div>

  </div>
</template>

<script setup>
  import {reactive} from "vue";

  const cities = reactive(["北京", "上海", "广州", "深圳"])
  const person = reactive({
    id: 1001,
    name: '张三',
    age: 18
  })
</script>

7.5 组件

7.5.1 组件的生命周期

在Vue中,组件的生命周期是指一个组件从被创建到被销毁的整个过程,这个过程包括组件的创建、挂载、更新、销毁四个主要阶段。在这些阶段中,Vue提供了一系列相应的生命周期钩子函数,它们会在对应的阶段自动执行。通过这些函数,开发者可以在某个特定的时机执行特定的逻辑。

在这里插入图片描述

常见钩子函数

  • onMounted() 注册一个回调函数(钩子函数),在组件挂载完成后执行。
  • onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
  • onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用。
  • onBeforeMount() 注册一个回调函数,在组件被挂载之前被调用。
  • onBeforeUpdate() 注册一个回调函数,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
  • onBeforeUnmount() 注册一个回调函数,在组件实例被卸载之前调用。

具体使用案例:

<template>
  <div>
    <span id="span1" v-text="message"></span> <br>
    <input type="text" v-model="message">
  </div>
</template>

<script setup>
  import {ref, onMounted, onBeforeUpdate, onUpdated,} from 'vue'

  let message = ref('hello')
  // 在组件被挂载到DOM前执行
  onMounted(() => {
    console.log('===========onMounted===========')
    const span1 = document.querySelector('#span1');
    console.log(span1.innerHTML)
  })
  // 在组件即将因为响应式状态变更而更新其DOM之前执行
  onBeforeUpdate(() => {
    console.log('===========onBeforeUpdate===========')
    const span1 = document.querySelector('#span1');
    console.log(span1.innerHTML)
  })
  // 在组件因为响应式状态变更而更新其DOM之后执行
  onUpdated(() => {
    console.log('===========onUpdated===========')
    const span1 = document.querySelector('#span1');
    console.log(span1.innerHTML)
  })
</script>

生命周期详解官方文档:组合式 API:生命周期钩子 | Vue.js

7.5.2 组件的注册

当在Vue项目中定义了一个新的组件后,要想在其他组件中引用这个组件,需要对这个组件进行注册。组件注册的方式有两种,分别是全局注册和局部注册。

1、全局注册

全局注册组件是指将一个组件注册到全局范围,使得该组件可以在当前项目的任何地方被引用。全局组件需要在main.js文件中通过Vue应用实例的component()方法进行注册,该方法的语法格式如下。

const app = createApp(App)
app.component('组件名称',需要引入的组件)

具体使用:

  • 需要进行全局注册的组件

    <template>
      <h1>{{ message }}, 我是通过全局注册的Vue组件</h1>
    </template>
    
    <script setup>
      import {ref} from 'vue'
      const message = ref('hello')
    </script>
    
  • 在main.js中进行全局注册

    // 导入createApp()函数,用于创建Vue应用实例
    import { createApp } from 'vue'
    import App from './components/App11Registry.vue'
    import GlobalComponent from './components/GlobalComponent.vue'
    const app = createApp(App)
    // 全局注册组件
    app.component("GlobalComponent",GlobalComponent)
    app.mount('#app')
    
    
  • 使用全局注册的组件

    <template>
      <div>
        <h1>{{ msg }}我是App11Registry组件</h1>
        <!--直接使用-->
        <GlobalComponent/>
      </div>
    </template>
    
    <script setup>
      import {ref} from "vue";
    
      const msg = ref('Hi~')
    </script>
    

2、局部注册

局部注册是指在某个组件中注册另一个组件,被局部注册的组件只能在当前组件中使用。例如,在组件A中注册了组件B,则组件B只能在组件A中使用。

在选项式API中的局部注册:

<script>
import PartComponent from './PartComponent.vue'
export default {
  components: {
    'PartComponent': PartComponent
  }
}
</script>

在组合式API中的局部注册(setup语法糖):

<template>
  <PartComponent/>
</template>
<script setup>
  import PartComponent from './PartComponent.vue'
</script>

导入的组件会被自动注册,无须手动进行注册,导入后可以直接在当前组件的模板中使用

7.5.3 组件的传递数据

在实际开发中,经常会遇到不同组件之间需要共享或传递一些数据。例如一个页面由导航栏组件和内容组件组成,当用户单击导航栏中的链接时,需要传递相应的数据到内容组件以展示相关的内容。

当一个组件中引入了其他组件时,当前组件被称为父组件,而引入的组件被称为子组件,父组件和子组件是一个相对的概念。父组件可以传递数据给子组件,子组件也可传递数据给父组件。

1、父组件向子组件传递数据

父组件向子组件传递数据可以通过props来实现。若要实现父组件向子组件传递数据,需要先在子组件中声明props,表示子组件可以从父组件中接收哪些数据。子组件在接收这些数据时,需要在其组件定义中显式声明这些属性,以便Vue能够正确地处理和验证这些传递过来的值。具体操作如下:

  1. 首先,在父组件中定义需要传递给子组件的值,接着,在父组件的模板中引入子组件,同时在引入子组件的标签中添加 props 属性并为其设置需要传递的值。

  2. 在 Vue3 中,父组件通过 props 传递给子组件的值是响应式的。也就是说,如果在父组件中的传递的值发生了改变,子组件中的值也会相应地更新。

  3. 在使用setup语法糖时,子组件声明props需要使用defineProps()函数来实现,

<script setup>
import {defineProps} from "vue";
defineProps(
    {属性1: 类型}, 
    {属性2: 类型},
    ……
)
</script>
<template>
  <子组件名称 :属性1="数据1" :属性2="数据2" ……/>
</template>
<script setup>
//导入子组件
//定义数据
</script>

具体使用:

  • 父组件代码:

    <template>
      <div>
        <h2>我是父组件</h2>
        <button @click="changeMessage">点击更新</button>
        <!--使用子组件,并传递数据-->
        <App12Son :message="message" :count="count"/>
      </div>
    </template>
    
    <script setup>
      import App12Son from "@/components/App12Son.vue";
      import {ref} from "vue";
    
      let message = ref('你好')
      let count = ref(40)
    
      function changeMessage() {
        message.value = '修改数据'
        count.value++
      }
    </script>
    
  • 子组件代码:

  • <template>
      <div>
        <h2>我是子组件</h2>
        <div>
          {{ message }}, {{ count }}
        </div>
      </div>
    </template>
    
    <script setup>
      import {defineProps} from "vue";
      // 声明父组件传递属性值
      defineProps({
        message: String,
        count: Number
      })
    </script>
    

2、子组件向父组件传递数据

在Vue中,子组件向父组件传递数据可以通过自定义事件来实现。在使用自定义事件时,需要在子组件中声明和触发自定义事件,在父组件中监听自定义事件。具体操作如下:

  1. 首先需要在子组件中使用defineEmits()函数声明该事件,语法格式如下。
<script setup>
  const emit = defineEmits(['自定义事件名称'])
</script>
  1. 自定义事件声明完成后,通过emit()函数触发该事件,并将数据传递给父组件,语法格式如下。
emit('自定义事件名称','需要传递的数据')
  1. 父组件想要获取到子组件传递的数据,可以通过v-on来监听子组件抛出的事件,并对接收到的数据进行处理,监听子组件抛出事件的语法格式如下。
<子组件名称 @自定义事件名称="事件处理器" />
  1. 在上述语法格式中,当触发该自定义事件时,父组件会接收到子组件中传递的数据,在事件处理器中可以对该数据进行处理,语法格式如下。
<script setup>
  const 方法名 = (数据) =>{
  //处理接收到数据
  }
</script>

具体使用:

  • 子组件代码:

    <template>
      <div>
        <h2>子组件</h2>
        <button @click="sendMsgToParentIncrease">数量+1发送给父组件</button>
        <button @click="sendMsgToParentDecrease">数量-1发送给父组件</button>
      </div>
    </template>
    
    <script setup>
      import {ref, defineEmits} from "vue";
    
      const count = ref(1)
      // 1.定义要发送给父组件的方法,可以1或者多个
      const emit = defineEmits(['increase', 'decrease'])
      function sendMsgToParentIncrease() {
        // 2.出发父组件对应的方法,调用defineEmits对应的属性
        emit('increase', count.value++)
      }
      function sendMsgToParentDecrease() {
        emit('decrease', count.value--)
      }
    </script>
    
  • 父组件代码:

    <template>
      <div>
        <h2>父组件,接收子组件的发送的数据:{{ pCount }}</h2>
        <!-- 声明@事件名应该等于子模块对应事件名!调用方法可以是当前自定义!-->
        <App13Son @increase="add" @decrease="sub"/>
      </div>
    </template>
    
    <script setup>
      import App13Son from "./App13Son.vue";
      import {ref} from "vue";
    
      let pCount = ref(0)
    
      //自定义接收,子组件传递数据方法! 参数为数据!
      function add(data) {
        console.log(data)
        pCount.value = data
      }
    
      function sub(data) {
        console.log(data)
        pCount.value = data
      }
    </script>
    

3、兄弟组件传参

思考:如何通过子传父,父传子,实现兄弟组件之间的数据传递?

7.6 Vue路由

在Vue项目中,主要通过单一的HTML页面集成多个组件来展示不同的功能。由于一个项目包含的组件众多,并且功能也各不相同,所以不会同时在一个页面上展示所有组件,而是希望通过单击链接或其他方式切换到对应功能的组件。为了解决这个问题,Vue提供了路由机制,它可以实现在不刷新页面的情况下切换不同的组件。

需求:

在这里插入图片描述

7.6.1 Vue Router

Vue Router | Vue.js 的官方路由

安装路由

npm install vue-router

准备页面组件

  • App.vue
  • Home.vue
  • List.vue

使用路由:

  1. 新建router.js文件,创建路由实例并定义路由规则

    创建路由实例是指初始化一个路由对象,该对象包含了所有关于路由的配置和逻辑。定义路由规则是指将URL映射到特定的组件上,当用户访问特定的URL时,路由会根据定义的路由规则来渲染对应的组件。在Vue中,使用createRouter()函数来创建路由实例。

    // 导入路由创建的相关方法
    import {createRouter, createWebHashHistory} from "vue-router";
    
    // 导入vue组件
    import Home from './components/App14BHome.vue'
    import List from './components/App14CList.vue'
    
    // 创建路由对象,声明路由规则
    const router = createRouter({
        //createWebHashHistory() 是 Vue.js 基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中
        history: createWebHashHistory(),
        routes: [
            {
                path: '/home',
                component: Home
            },
            {
                path: '/list',
                component: List
            }
        ]
    })
    // 导出路由对象
    export default router
    
  2. 在main.js文件中,导入并挂载路由实例

    import { createApp } from 'vue';
    import App from './components/App14ARouter.vue';
    import router from './router';
    const app = createApp(App);
    // 绑定路由对象
    app.use(router);
    app.mount('#app');
    
  3. 在App.vue中定义路由链接和路由视图

    为了在页面中将路由对应的组件显示出来,还需要在父组件中定义路由视图。路由视图使用标签定义,该标签会被渲染成当前路由对应的组件。另外,为了方便在不同组件之间切换,可以通过标签定义路由链接,该标签的to属性用于指定链接路径,与路由规则中path属性指定的URL对应。

    <template>
      <div>
        <h2>App页面</h2>
        <hr/>
        <!-- 路由的连接 -->
        <router-link to="/home" style="padding-right: 16px">Home</router-link>
        <router-link to="/list">List</router-link>
        <!-- 路由连接对应视图的展示位置 -->
        <router-view></router-view>
      </div>
    </template>
    

路由重定向

重定向的作用:将一个路由重定向到另一个路由上

需求:修改案例:访问/list和/showAll都定向到List.vue

  • router.js

    // 导入路由创建的相关方法
    import {createRouter, createWebHashHistory} from "vue-router";
    
    // 导入vue组件
    import Home from './components/App14BHome.vue'
    import List from './components/App14CList.vue'
    
    // 创建路由对象,声明路由规则
    const router = createRouter({
        // //createWebHashHistory() 是 Vue.js 基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中
        history: createWebHashHistory(),
        routes: [
            {
                path: '/',
                component: Home
            },
            {
                path: '/home',
                component: Home
            },
            {
                path: '/list',
                component: List
            },
            // 重定向
            {
                path: '/showAll',
                redirect: '/list'
            }
        ]
    })
    // 导出路由对象
    export default router
    
  • App.vue

    <template>
      <div>
        <h2>App页面</h2>
        <hr/>
        <!-- 路由的连接 -->
        <router-link to="/home" style="padding-right: 16px">Home</router-link>
        <router-link to="/list" style="padding-right: 16px">List</router-link>
        <!-- 增加一个重定向的路由连接 -->
        <router-link to="/showAll">showAll</router-link>
        <!-- 路由连接对应视图的展示位置 -->
        <router-view></router-view>
      </div>
    </template>
    

7.6.2 路由传参

在实际开发中,经常需要在页面跳转时传递一些数据,例如从列表跳转到详情页时,需要传递选中项的ID以获取该项的详细内容,这时可以通过路由传参来实现。

Vue Router提供了两种传参方式,分别是通过params传递动态路径参数和通过query传递查询参数

1、通过params传递动态路径参数

通过params传递动态路径参数时,首先需要在路由规则配置文件(router.js)中指定这些参数。在路由规则中可以使用“:参数名”的方式指定动态路径参数,比如:

{path: '/showDetail/:param1/:param2',component: showDetail}

在路由规则中指定了动态路径参数后,可以在组件中使用<router-link>标签定义路由链接时指定这些参数的具体值,比如:

<router-link to="/showDetail/value1/value2">链接名称</router-link>

指定了动态路径参数的具体值后,可以在对应组件中获取这些参数的值。在获取参数值时需要用到**useRoute()**函数,该函数用于获取包含当前路由信息的对象,通过该对象的params值可以获取当前路由中指定的动态路径参数。

<script setup>
  import {useRoute} from "vue-router";
  const route = useRoute()
  console.log(route.params.param1)
	console.log(route.params.param2)
</script>

2、通过query传递查询参数

通过query传递查询参数时,路由规则配置文件中不需要预先指定这些参数,只需要定义问号“?”前面的路径即可,示例代码如下。

{path: '/showDetail',component: showDetail}

在组件中使用标签定义路由链接时,需要在URL路径后面附加查询参数,示例代码如下。

<router-link to="/showDetail?param1=value1&param2=value2">链接名称</router-link>

在对应组件中,可以通过useRoute()函数获取包含当前路由信息的对象,并通过该对象的query值获取URL路径中的查询参数。获取查询参数的示例代码如下。

<script setup>
  import {useRoute} from "vue-router";
  const route = useRoute()
  route.query.param1
  route.query.param2
</script>

具体使用:

  • App.vue组件:

    <template>
      <div>
        <h1>路由传参</h1>
        <router-link to="/detail1/1/张三">获取动态路径参数</router-link> &nbsp;
        <router-link to="/detail2?id=2&name=李四">获取查询参数</router-link>
        <hr>
        <router-view></router-view>
      </div>
    </template>
    
  • 两个路由组件:

    <template>
      <h2>动态路径参数</h2>
      <p>学号:{{ userId }} 姓名:{{ userName }}</p>
    </template>
    <script setup>
      import {useRoute} from "vue-router";
      import {ref} from "vue";
    
      const userId = ref(0)
      const userName = ref("")
      const route = useRoute()
      userId.value = route.params.id
      userName.value = route.params.name
    </script>
    
    <template>
      <h2>查询参数</h2>
      <p>学号:{{ id }} 姓名:{{ name }}</p>
    </template>
    <script setup>
      import {useRoute} from "vue-router";
      import {ref} from "vue";
    
      const id = ref(0)
      const name = ref("")
      const route = useRoute()
      id.value = route.query.id
      name.value = route.query.name
    </script>
    
  • router.js路由配置文件:

    // 导入路由创建的相关方法
    import {createRouter, createWebHashHistory} from "vue-router";
    
    // 导入vue组件
    import DetailB from './components/App15BDetail.vue'
    import DetailC from './components/App15CDetail.vue'
    
    // 创建路由对象,声明路由规则
    const router = createRouter({
        // //createWebHashHistory() 是 Vue.js 基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中
        history: createWebHashHistory(),
        routes: [
            {
                path: '/detail1/:id/:name',
                component: DetailB
            },
            {
                path: '/detail2',
                component: DetailC
            }
        ]
    })
    // 导出路由对象
    export default router
    

7.6.3 编程式路由

在Vue中,路由的跳转通常有两种主要方式,分别为声明式路由和编程式路由。其中,使用标签定义路由链接的方式属于声明式路由,前面已经介绍过。然而在某些情况下,可能需要通过JavaScript代码动态地控制路由的跳转,例如单击某个按钮时进行路由跳转,对此可以通过编程式路由实现。

编程式路由是先通过useRouter()函数获取全局路由实例,然后通过调用该实例的push()方法实现路由跳转。push()方法可以接收一个字符串路径作为参数,用于跳转到路由规则中与该路径对应的路由组件。

<template>
  <button @click="toHome">去首页</button>
  <router-view></router-view>
</template>
<script setup>
  import {useRouter} from "vue-router"
  //获取全局路由实例
  const router = useRouter()
  //实现路由跳转
  const toHome = () => {
    router.push('/home')
  }
</script>

编程式路由跳转时,如果需要传递参数,可以在push()方法中传递一个描述路由信息的对象作为参数,该对象可以包含路由的路径(path)、路由名称(name)、查询参数(query)和动态路径参数(params)等属性。其中,路由名称(name)是指在定义路由规则时为路由指定的一个唯一标识符(router.js),具体示例如下。

{path: '/pathA', name: 'componentA', component: ComponentA},

当指定路由名称后,可以在编程式路由中通过路由名称引用对应的路由。编程式路由传参的示例如下。

//传递动态路径参数(方式一)
router.push({ name: 'componentA', params: {id: 1, name: '张三'} })
//传递动态路径参数(方式二)
router.push({ path: '/pathA/1/张三'})
//传递查询参数
router.push({ path: '/pathA', query: {id : 1, name: '张三'} })

具体使用:

  • 修改App.vue代码:

    <template>
      <div>
        <h1>路由传参</h1>
        <button @click="toPage">编程式路由跳转</button>
        <hr>
        <router-view></router-view>
      </div>
    </template>
    
    <script setup>
      import {useRouter} from "vue-router";
    
      const router = useRouter()
      const toPage = () => {
        router.push({
          path: '/detail2',
          query: {
            id: 4,
            name: '赵六'
          }
        })
      }
    </script>
    

更多推荐