Vue3 进阶

Vue 进阶

前言

Vue3 入门文章地址:Vue3 入门

任务一 创建 Vite + Vue3 单页应用

Vue 3 是一个流行的 JavaScript 前端框架,用于构建单页应用程序(SPA)

下面是一些创建 Vue 3 单页应用程序的方式:

  • Vue CLI:Vue CLI 是一个命令行界面工具,用于创建和管理 Vue 应用程序。它可以自动生成一个基于 webpack 的项目模板,提供了一些内置的插件和特性,例如 Babel、ESLint、TypeScript 等。使用 Vue CLI 可以方便地创建 Vue 单页应用程序。
  • 手动设置:你可以手动设置一个 Vue 3 单页应用程序。这需要你手动创建 webpack 配置文件,并安装和配置必要的插件和库,如 vue-loader、babel-loader 等。这种方式更加灵活,但需要更多的配置和知识。
  • 使用 Vite:Vite 是一个现代化的构建工具,用于构建 Vue 应用程序。它使用现代化的技术和原生 ES 模块作为基础,提供了一种快速、轻量级的开发体验。你可以使用 Vite 创建一个 Vue 3 单页应用程序,只需运行几个命令即可。

在本任务中,我们将使用 Vite 来创建 Vue 3 单页应用程序,因为它能够大幅简化开发流程。不过,如果您需要更加灵活的控制和定制,手动设置也是一个不错的选择。

单页应用

单页应用(Single Page Application,SPA)是一种 Web 应用程序的架构模式,它使用动态加载的 HTML、CSS 和 JavaScript,以及 AJAX 和 WebSockets 等技术实现无刷新页面的单页应用。

核心思想

单页应用的核心思想是:将所有的页面都加载到一个单一的 HTML 页面中,通过 JavaScript 操作 DOM 实现页面的动态变化。

数据交互过程

当用户与应用程序交互时,JavaScript 会通过 AJAX 或 WebSockets 等技术请求后端 API 获取数据,然后在前端通过 Vue、React 等前端框架对数据进行处理,最终更新视图,从而实现页面的刷新和动态变化。

优点

单页应用的优点是:

  • 可以提高页面的加载速度和用户体验,因为只需要加载一次 HTML、CSS 和 JavaScript 等资源,之后就可以在前端通过 JavaScript 动态更新页面内容,而不需要重新加载整个页面。
  • 此外,单页应用还可以提高开发效率,因为前端可以采用组件化开发方式,将页面拆分为多个组件,每个组件可以独立开发和测试,最终再将这些组件拼接成完整的页面。

缺点

但是,单页应用也有一些缺点

  • 首先,由于 SPA 的内容是通过 JavaScript 动态加载的,对于 SEO 来说并不友好。
  • 其次,SPA 需要处理前后端分离的问题,需要前端开发人员和后端开发人员共同协作完成。此外,单页应用需要处理浏览器历史记录和 URL 路由等问题,需要使用一些第三方库或框架来处理这些问题。

多页应用

相比之下,多页应用(MPA)的优点是:对 SEO 友好,因为每个页面都有自己的 URL 地址和内容,便于搜索引擎进行抓取和索引。此外,多页应用还可以采用传统的服务器渲染方式,在后端渲染页面,以提高页面的加载速度和 SEO 效果。

但是,多页应用的缺点是:页面切换需要重新加载整个页面,页面刷新和交互体验相对较差。此外,多页应用需要在前端和后端进行模板渲染,代码复杂度相对较高,开发效率相对较低。

因此,选择单页应用(SPA)还是多页应用(MPA)取决于项目的具体需求和情况。如果项目注重用户体验和开发效率,且对 SEO 不是特别敏感,可以选择 SPA。

为什么 SPA 对 SEO 不友好?

在这句话中,SEO 指的是搜索引擎优化(Search Engine Optimization),即通过优化网站以提高其在搜索引擎中的排名和可见性。

"对于 SEO 来说并不友好"指的是:单页面应用(SPA)对于传统的搜索引擎优化方法不太友好。

原因:

SPA 是一种通过 JavaScript 动态加载内容的网页应用,它使用 AJAX 和前端路由等技术实现页面的无刷新加载和内容的动态更新。

由于 SPA 的内容大部分是在客户端通过 JavaScript 生成和加载的,而传统的搜索引擎爬虫通常只会抓取和索引【静态 HTML 页面】,因此无法直接获取和理解 SPA 中的内容。

这导致了 SPA 在搜索引擎中的可见性和排名受到一定影响。因为搜索引擎无法像普通的 HTML 页面那样直接抓取和索引【SPA 的内容】,所以对于那些依赖搜索引擎流量的网站来说,SPA 可能无法获得足够的有机流量。

解决方法:

然而,随着搜索引擎的发展,一些搜索引擎(如Google)已经可以执行 JavaScript 并抓取 SPA 的内容。同时,也出现了一些技术和方法来解决 SPA 的 SEO 问题,例如使用预渲染技术、动态生成静态页面、使用服务器端渲染(SSR)等。这些方法可以帮助 SPA 在搜索引擎中获得更好的可见性和排名,提高其对 SEO 的友好程度。

Vue 3 对单页应用开发的支持

Vue.js 是一个用于构建现代 web 应用的渐进式框架,它对开发单页应用提供了很多支持。我们习惯把 Vue.js 2.x 和 Vue.js 3.x 版本分别简称为 Vue2 和 Vue 3。

以下是一些 Vue 3 的特性,它们可以帮助开发者更容易地创建和维护单页应用

  • 使用了响应式数据绑定和组件化的思想,让开发者可以快速构建高效的用户界面。
  • 组合式 API:这是 Vue 3 的一个新特性,它允许开发者使用函数式的方式来组织和复用组件的逻辑,而不是依赖于选项式 API 的 data、methods、computed 等属性。这样可以让组件的代码更加清晰和模块化,也可以避免命名冲突和数据依赖的问题。
  • 优化的虚拟 DOM:虚拟 DOM 是 Vue 的核心特性之一,它可以让开发者使用声明式的语法来渲染界面,而不需要直接操作 DOM。Vue 3 对虚拟 DOM 进行了优化,使其更加高效和灵活。例如,Vue 3 引入了静态标记(hoisting)、片段(fragments)、模块化运行时(tree-shaking)等技术,来减少不必要的渲染和内存占用。
  • 支持 TypeScript:TypeScript 是一种在 JavaScript 基础上增加了类型检查和其他特性的编程语言,它可以提高代码的可读性和可维护性,也可以避免一些常见的错误。Vue 3 完全支持 TypeScript,不仅在源码层面使用了 TypeScript,还提供了完善的类型声明文件,让开发者可以在编辑器中享受到智能提示和错误检测的功能。
  • 更多的内置组件和指令:Vue 3 提供了一些新的内置组件和指令,来增强 SPA 的交互和功能。例如,<teleport> 组件可以让开发者将子组件渲染到任意位置,<suspense> 组件可以让开发者处理异步组件的加载状态。

总之,Vue3+vite 是一种非常适合开发单页应用的技术栈,它可以让开发者享受到最新的前端技术和最佳的开发体验。

安装 Node.js

创建 vite+Vue3 项目需要安装 16.0 或更高版本的 Node.js。您可以在命令行中运行 node -v 命令来检查您是否已安装了 node 环境。如果没有安装或版本不对的话,可以按照下面的步骤进行安装。

一、下载 Node.js

下载地址:https://nodejs.org/zh-cn/download/

下载长期维护版:

image

二、安装 Node.js

以 Windows 系统为例,下载 Node.js 安装包后,双击一路安装即可。安装过程,除了 Node.js 外,还会一并安装 npm 包管理器。启动 Node.js 和 npm 的命令分别是:

node   
npm 
三、测试安装是否成功

安装完成后,注意测试安装是否成功,测试命令:

node -v
npm -v

如果显示出 Node.js 和 npm 的版本号,表明安装成功。否则,需要检查环境变量的配置。

创建 Vite+Vue3 单页应用

Vue CLI 和 Vite 都是用于创建 Vue 项目的构建工具。

Vue CLI 是一个基于 Webpack 的构建工具,它提供了一个完整的项目脚手架,包括开发服务器、热重载、代码分割、ESLint 等等。

Vite 是一个基于 ES 模块的构建工具,它使用浏览器原生的 ES 模块加载器来提供快速的开发体验。Vite 提供了零配置的开发环境,无需安装和配置 webpack 等复杂的工具,只需一个 vite.config.js 文件即可启动一个本地服务器,因此它可以更快地启动和重载。Vite 支持 typescript、css 预处理器、jsx 等常用的功能,并且在生产环境中也可以更快地构建。

一、创建项目

使用 npmpnpm 命令均可创建 Vite+Vue3 单页应用:

npm init vue@latest
或
pnpm create vue@latest

这一命令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。

建议采用 pnpm create vue@latest 进行创建。如果没有安装 pnpm 的话,可以先安装 pnpm,安装命令为:

npm install pnpm -g

这里使用 pnpm create vue@latest 命令创建 Vue3 项目的过程中,您将会看到一些如 TypeScript 和测试支持之类的可选功能提示:

可按需要进行选择安装,这里我们选择 TypeScriptVue RouterPinia,其余的功能可以留待以后需要时再进行安装。

✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes -- y
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes -- y
✔ Add Pinia for state management? … No / Yes -- y
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / YesScaffolding project in ./<your-project-name>...

如果我们在上面第一步 Project name 处输入项目名称 RealWorld-frontend 的话,项目创建成功后,会显示如下命令序列,提示您如何进行后续操作。

cd realworld-frontend
pnpm install
pnpm dev

项目结构如下:

├── public/                  // 公共资源目录
│   └── favicon.ico          // 网站图标
├── src/                     // 项目源码目录
│   ├── assets/              // 静态资源目录(如图片、字体等)
│   ├── components/          // 组件目录
│   ├── router/              // 路由目录
│   ├── stores/              // 状态管理目录
│   ├── views/               // 视图目录
│   ├── App.vue              // 根组件
│   └── main.ts              // 项目入口文件
├── .gitignore               // Git 忽略文件列表
├── env.d.ts。               // 为用户自定义环境变量提供 TypeScript 智能提示
├── index.html           		 // 入口 HTML 文件
├── package.json             // 项目配置文件
├── README.md                // 项目说明文件
├── tsconfig.json            // TypeScript 配置文件
├── tsconfig.node.json       // 为 Node.js 环境提供单独的TypeScript编译选项
└── vite.config.js					 // Vite的配置文件,用于配置开发环境和生产环境的各种选项
二、安装依赖

按命令序列的前两条命令进行操作:

cd realworld-frontend	 # 进入项目文件夹
pnpm install  # 安装依赖(推荐)

依赖安装完成后,项目文件夹 RealWorld-frontend 中将多出一个子文件夹 node_modules:

├── node_modules/            // 第三方依赖包目录
├── public/                  
├── src/                     
│   ...

node_modules 文件夹里面就是按照 package.json 的指示下载的各种依赖。

运行单页应用

一、启动项目服务端

项目服务端先启动起来,才能对浏览器端提供网页服务。启动命令为:pnpm devnpm run dev

启动成功后,屏幕显示:

  VITE v4.2.1  ready in 416 ms➜  Local:   http://localhost:5173/➜  Network: use --host to expose➜  press h to show help

表明项目服务端已经运行在 5173 端口,等待浏览器的访问。

二、从浏览器访问应用

按住 Ctrl 键,并用鼠标单击链接 http://localhost:5173/,将在浏览器中打开应用的首页,内容如下图所示。

image

任务二 了解组合式 API

当使用 Vue 2 的 Options API 进行大型项目开发时,可能会遇到代码复杂度高、难以维护的问题。为了解决这个问题,Vue 3 提供了一种新的组件编写方式:组合式 APIsetup() 函数

  • 组合式 API 允许按照逻辑组织代码,而不是按照生命周期函数或选项来组织代码,从而提高了代码的可读性和可维护性。
  • setup() 函数在组件创建和挂载之前运行,用于设置组件的响应式数据、计算属性、方法和监听器等。在 setup() 函数中,可以使用 Vue 3 的组合式 API 如 ref、reactive、computed、watchEffect 等来实现组件的逻辑,从而更好地组织项目代码。

使用 setup 函数

Vue 3 中的 setup() 函数是使用组合式 API 编写组件的入口函数,它会在组件实例创建之前被调用。

setup() 函数内部,可以通过使用 Vue 3 提供的一系列组合式 API 来定义组件的响应式数据、计算属性、方法等等,并将它们返回给组件的模板中使用。setup() 函数返回的对象会被用作组件实例的数据对象,可以在模板中直接访问。

以下是一个简单的例子,演示了如何使用 Vue 3 的 setup() 函数reactive() 来创建一个响应式的计数器:

<template><div><p>当前计数:{{ state.count }}</p><button @click="increment">+ 1</button></div>
</template><script>
import { reactive } from 'vue'export default {setup() {const state = reactive({count: 0})const increment = () => {state.count++}return {state,increment}}
}
</script>

在上述代码中,

  1. 我们首先引入了 Vue 3 提供的 reactive 函数,用于创建响应式对象。
  2. 在 setup() 函数中,我们使用 reactive 函数创建了一个名为 state 的响应式对象,包含了一个 count 属性。
  3. 接着,我们定义了一个名为 increment 的方法,用于将 state 对象的 count 属性 +1。
  4. 最后,我们将 state 和 increment 作为对象返回,供模板中使用。

由于使用了 reactive 函数,state 对象是响应式的,因此它的值会自动更新到模板中。当state.count 属性更新时,模板中的数据也会更新。

同样的响应式也可以通过 ref() 实现:

<template><div><p>当前计数:{{ count }}</p><button @click="increment">+1</button></div>
</template>
<script>
import { ref } from 'vue'export default {setup() {const count = ref(0)const increment = () => {count.value++}return {count,increment}}
}
</script>

由于 setup() 函数的执行时机早于 Vue 2 中的 created 钩子函数,可以在其中进行一些更早的初始化操作,提高组件的性能。

总之,setup() 函数是 Vue 3 中使用组合式 API 编写组件的重要入口函数,通过它可以更加自由地组织组件的逻辑,使得代码更加清晰易懂。

使用 script setup

  • <script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,用于简化组件的编写和组织。
  • 当同时使用 SFC 与组合式 API 时该语法是默认推荐。它允许在一个 <script> 标签内使用 Composition API,而不需要定义 setup() 函数或返回语句
  • 它还提供了一些额外的功能,如自动导入和导出组件属性、响应式声明、类型推断等。

使用 <script setup> 可以让组件的代码更加清晰和高效。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和自定义事件。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

下面是一个使用 <script setup> 的示例组件:

<template><div><h1>{{ title }}</h1><button @click="increment">Count: {{ count }}</button></div>
</template><script setup>
import { ref } from 'vue'// 自动导出为组件属性
const title = 'Hello Vue 3'// 响应式声明
const count = ref(0)// 普通函数
function increment() {count.value++
}
</script>

<script setup> 语法糖改写目标 1 的代码:

<template><div><p>当前计数:{{ count }}</p><button @click="increment">+1</button></div>
</template><script setup>
import { ref } from 'vue'const count = ref(0)const increment = () => {count.value++
}
</script>

可见,在 <script setup>setup() 函数不用再写了,定义的变量和函数会自动暴露给模板,并且不需要再用 return 语句将它们导出。这样可以更简洁地编写 Vue 组件。

引入 TypeScript

在创建 Vue 3 + Vite 项目时,如果已经选择并安装了 “Add TypeScript”,那么就可以采用 TypeScript 编写组件,目标 2 中的代码用 TypeScript 可以改写为:

<template><div><p>当前计数:{{ count }}</p><button @click="increment">+1</button></div>
</template><script setup lang="ts">
import { ref } from 'vue'const count = ref<number>(0)const increment = () => {count.value++
}
</script>
  • <script setup> 中的脚本可以使用 TypeScript 或 JavaScript 编写。这里我们写成 <script setup lang="ts">,其中 lang="ts" 是告诉 Vue 3,该组件的脚本使用 TypeScript 编写。使用 TypeScript 可以提供更好的类型检查和代码提示,以及更好的代码可维护性和可读性。根据在 lang 属性中指定的语言类型,Vue 3 会相应地对组件进行编译处理。如果没有指定 lang 属性,则默认使用 JavaScript 编写脚本。
  • 在这里,ref<number>(0) 表示将数字类型的值 0 转换为响应式对象<number> 是 TypeScript 中的类型注解,用于指定 count 的类型为数字。这样在开发过程中,如果不小心给 count 赋予非数字类型的值,TypeScript 编译器就会提示错误。

响应式

所谓响应式,就是当我们修改数据后,可以自动做某些事情;对应到组件的渲染,就是修改数据后,能自动触发组件的重新渲染。这其实是一种状态驱动,让用户界面随着数据变化而自动更新的编程范式。

响应式的核心思想是将数据和视图绑定在一起,当数据发生变化时,视图也会相应地变化,而不需要手动操作 DOM 元素。

响应式的优点是可以简化前端开发的复杂度,提高用户体验和性能,避免不必要的重绘和重排。响应式的实现方式有多种,比如使用观察者模式、发布订阅模式、虚拟 DOM 等。响应式的代表框架有 React、Vue.js、Angular 等。

Vue 3 的响应式原理

Vue 3 的响应式原理是:基于 ES6 的 Proxy 和 Reflect 特性实现的,它可以对对象的各种操作进行拦截和处理,从而实现数据和视图的双向绑定。这个方法的本质是劫持了数据对象的读写。当我们访问数据时,会触发 getter 执行依赖收集;修改数据时,会触发 setter 派发通知。

Vue 3 的响应式原理主要包括以下几个步骤:

  1. 创建一个响应式对象,使用 Proxy 对目标对象进行代理,拦截它的 get 和 set 操作,同时使用 Reflect 对操作进行反射,保证原对象的行为不受影响。

  2. 在 get 操作中,收集依赖,即将当前的渲染函数或者副作用函数添加到目标对象的依赖集合中,这样当目标对象发生变化时,就可以通知这些函数进行更新。

  3. 在 set 操作中,触发更新,即遍历目标对象的依赖集合,调用其中的函数,让它们重新执行,从而更新视图或者产生其他效果。

  4. 重复以上步骤,实现数据和视图的动态响应。

Vue 3 的响应式原理相比于 Vue 2 的 Object.defineProperty 方式,有以下优势:

  • 可以拦截更多的操作,如 delete、has、ownKeys 等,提高了灵活性和兼容性。
  • 可以对数组和嵌套对象进行响应式处理,无需额外的处理逻辑。
  • 可以避免原对象被污染,保持了数据的纯净性。
  • 可以提高性能,减少了不必要的依赖收集和更新触发。

Vue 3 的响应式 API

Vue 3 的响应式 api 是一组新的功能,用于创建和管理响应式数据。

它们可以让我们在组件中更灵活地组织和复用逻辑,也可以与其他 Vue 特性(如计算属性、侦听器、生命周期钩子等)配合使用。

Vue 3 的响应式 api 主要包括以下几个部分:

  1. ref:用于创建一个响应式的值,可以是基本类型或对象类型。ref 返回一个包含 .value 属性的对象,通过这个属性可以访问或修改原始值。ref 也可以用于绑定模板中的 DOM 元素,通过 .value 获取元素的引用。
  2. reactive:用于创建一个响应式的对象,可以是普通对象、数组或类实例。reactive 返回一个代理对象,通过这个对象可以访问或修改原始对象的属性。reactive 不会改变原始对象的结构和身份,只是在访问和修改时触发响应式效果。
  3. computed:用于创建一个响应式的计算属性,可以根据其他响应式数据(如ref或reactive)动态地计算出一个值。computed 返回一个包含 .value 属性的对象,通过这个属性可以访问计算出的值。computed 也可以接受一个包含 get 和 set 函数的对象,实现可写的计算属性。
  4. watch:用于监听一个或多个响应式数据(如ref或reactive)的变化,并执行相应的回调函数。watch 可以接受一个源数据或一个返回源数据的函数作为第一个参数,以及一个回调函数作为第二个参数。watch 也可以接受一个包含多个源数据和回调函数的数组作为参数,实现同时监听多个数据。
  5. watchEffect:用于监听一个副作用函数中使用到的所有响应式数据的变化,并重新执行该函数。watchEffect 接受一个副作用函数作为参数,该函数会在首次调用时立即执行,并在后续任何依赖数据变化时重新执行。
  6. toRefs:用于将一个响应式对象(如reactive)转换为一个普通对象,该对象的每个属性都是一个 ref,指向原始对象的对应属性。toRefs 可以保持原始对象的响应性,同时避免解构时丢失响应性。
  7. toRaw:用于将一个响应式对象(如reactive)转换为原始对象,取消其响应性。toRaw 可以在需要直接操作原始对象而不触发响应式效果时使用。

任务三 用 reactive 设置响应式数据

reactive 函数的原理及注意事项

在 Vue 3 中,我们可以使用 Composition API 中的 reactive 函数来构建响应式对象。

这个函数接收一个普通对象作为参数,并返回一个响应式代理对象。这个代理对象包含了原始对象的所有属性,并且这些属性都是响应式的。当我们访问代理对象的属性时,实际上是在访问原始对象的属性,因此会触发 getter 执行依赖收集。当我们修改代理对象的属性时,实际上是在修改原始对象的属性,因此会触发 setter 派发通知。这种方式比 Vue 2.x 中使用的 Object.defineProperty() 实现响应式更加高效和灵活。

  • 在使用 reactive 时,需要注意以下几点:

  • 只有在组件的 setup 函数中才能使用 reactive 函数。

  • 只有通过 reactive 函数创建的对象才是响应式数据对象,直接修改普通 JavaScript 对象的属性不会触发视图更新。

  • reactive 函数只能处理对象类型数据,如果需要处理其他类型数据,可以使用 ref 函数。

  • reactive 返回的是一个 Proxy 对象,而不是原始对象。

  • reactive 返回的 Proxy 对象可以直接访问原始对象的属性和方法。

  • reactive 返回的 Proxy 对象可以直接修改原始对象的属性和方法。

  • reactive 返回的 Proxy 对象可以直接监听原始对象的变化。

  • 在模板中使用响应式对象的属性时,需要使用 两个花括号 语法来绑定数据,例如

    {{ state.list }}
    
  • 响应式对象的属性需要在对象创建时就定义好,不能在后面动态添加或删除属性。如果需要动态添加或删除属性,可以使用 reactive 函数创建一个新的响应式对象。

  • 在开发时,建议使用 toRefs 函数将响应式对象转换成普通对象的引用。这可以使代码更加清晰易懂,同时也能避免一些潜在的问题。

reactive 的使用举例

一、reactive 作用于普通的 JavaScript 对象

在 Vue 3 中,reactive 函数可以将一个普通的 JavaScript 对象转换成一个响应式数据对象,使得对象的属性可以被监听到变化并且自动更新视图。下面是 reactive 作用于对象的示例代码及注意事项:

<template><button @click="changeName">修改name</button><br><button @click="changeAge">修改age</button><br><button @click="changeGender">修改gender</button><br>{{ state.person.name }} - {{ state.person.age }} - {{ state.person.gender }}
</template><script setup>
import { reactive } from 'vue'const state = reactive({person: {name: 'Tom',age: 18,gender: 'male'}
})// 修改响应式对象的name
function changeName() {state.person.name = "Linda";console.log(state.person.name) // 输出 Linda
}// 修改响应式对象的age
function changeAge() {state.person.age++console.log(state.person.age) // 输出 19
}// 修改响应式对象的gender
function changeGender() {state.person.gender = "female"console.log(state.person.gender) // 输出 female
}</script>

注意事项:

  • 对于一个普通的 JavaScript 对象,通过 reactive 函数可以将其转换成一个响应式数据对象,使得对象的属性可以被监听到变化并且自动更新视图。
  • 只有通过 reactive 函数创建的对象才是响应式数据对象,直接修改普通 JavaScript 对象的属性不会触发视图更新。
  • 响应式对象的属性需要在对象创建时就定义好,不能在后面动态添加或删除属性。如果需要动态添加或删除属性,可以使用 reactive 函数创建一个新的响应式对象。
  • 响应式对象的属性值如果是一个对象,需要使用 reactive 函数将其转换成响应式对象,否则其属性变化不会触发视图更新。
二、reactive 作用于数组

在 Vue 3 中,reactive 函数也可以将一个普通的 JavaScript 数组转换成一个响应式数组,使得数组元素的变化可以被监听到并且自动更新视图。下面是一个 reactive 作用于数组的示例代码:

<template><button @click="change">修改元素</button><br><button @click="add">添加元素</button><br><button @click="del">删除元素</button><br>{{ state.list }}
</template><script setup>
import { reactive } from 'vue'const state = reactive({list: ['apple', 'banana', 'orange']
});// 修改响应式数组的元素
function change() {state.list[0] = 'pear'console.log(state.list)  // 输出 Proxy(Array) {0: 'pear', 1: 'banana', 2: 'orange'}
}// 向响应式数组中添加元素
function add() {state.list.push('grape')console.log(state.list)  // 输出 Proxy(Array) {0: 'pear', 1: 'banana', 2: 'orange', 3: 'grape'}
}// 从响应式数组中删除元素
function del() {state.list.splice(1, 1)console.log(state.list)  // 输出 Proxy(Array) {0: 'pear', 1: 'orange', 2: 'grape'}
}</script>

注意事项:

  • 响应式数组的元素操作需要使用 JavaScript 数组的操作方法,如 push()、pop()、shift()、unshift()、splice() 等。
  • 对响应式数组的操作会自动触发视图更新,无需手动调用。
  • 如果直接修改响应式数组的长度,例如 state.list.length = 2,则会导致视图无法更新,需要使用数组的操作方法进行操作。
  • 对响应式数组进行操作时,需要保证操作前后引用的是同一个数组对象,否则会导致视图无法更新。例如,不要使用 state.list = [] 的方式清空数组,而应该使用 state.list.splice(0, state.list.length) 的方式清空数组。

任务四 用 ref 设置响应式数据

ref 的原理及注意事项

在 Vue 3 中,ref 是一个函数,用于创建一个响应式数据对象,它可以将一个普通的 JavaScript 值转换成一个响应式数据对象,并提供了对该数据对象的访问和修改方法,使得修改该对象的值可以自动触发视图更新。(包裹一个值,这个值可以是对象类型也可以是基本类型,最终都会被转化成一个响应式对象)

ref 是基于 reactive 实现的。具体来说,ref 函数接受一个普通值作为参数,内部会使用 reactive 创建一个响应式对象来包裹这个值。当我们通过 .value 访问 ref 对象时,实际上是访问这个内部的响应式对象的值。

举个例子,以下代码展示了 ref 的实现原理:

import { reactive } from 'vue'function ref(value) {const obj = reactive({ value })return {get value() {return obj.value},set value(newValue) {obj.value = newValue}}
}

上面的代码中,ref 函数接受一个值作为参数,并使用 reactive 函数创建了一个包含该值的响应式对象 obj,然后返回一个包含 get 和 set 方法的对象,这两个方法用来获取和设置 obj.value 属性的值。

当我们访问 ref 对象时,实际上是在访问这个对象的 value 属性,这个属性通过 get 方法返回 obj.value当我们修改 ref 对象的值时,实际上是在修改这个对象内部响应式对象的值,这个操作通过 set 方法实现

因此,可以说 ref 是基于 reactive 实现的,它们都是 Vue 3 中用于创建响应式数据对象的重要函数。

在 Vue 3 中既然有了 reactive,为何还要 ref 呢?

当我们只想让某个变量实现响应式的时候,采用 reactive 就会比较麻烦,因此 Vue 3 提供了 ref 方法进行简单值的监听,但并不是说 ref 只能传入简单值,它的底层是 reactive,所以 reactive 有的,ref 都有。

请牢牢记住:

  • ref 本质也是 reactive,ref(obj) 等价于 reactive({value: obj})
  • 在 Vue 组件的 <script>...</script> 标签中使用 ref 的值,必须通过 .value 获取。
  • 在 Vue 组件的 <template>...</template> 标签中直接使用 ref 的值,不用也不能通过 .value 获取。

ref 的使用举例

我们将前面用 reactive 实现的代码改为 ref 再实现一次:

<template><button @click="changeName">修改name</button><br><button @click="changeAge">修改age</button><br><button @click="changeGender">修改gender</button><br>{{ name }} - {{ age }} - {{ gender }}
</template><script setup>
import { ref } from 'vue';// 包裹基本类型
const name = ref('Tom');
const age = ref(18);
const gender = ref('male');// 修改ref对象的name
function changeName() {name.value = "Linda";console.log(name.value); // 输出 Linda
}// 修改ref对象的age
function changeAge() {age.value++;console.log(age.value); // 输出 19
}// 修改ref对象的gender
function changeGender() {gender.value = "female";console.log(gender.value); // 输出 female
}</script>

在上面的代码中,我们使用 ref 分别创建了三个响应式数据 name、age 和 gender,它们的初始值分别为 ‘Tom’、18 和 ‘male’。同时,我们也将修改数据的三个函数 changeName、changeAge 和 changeGender 进行了相应的修改,使用 ref 对象的 .value 属性来修改值。

通过这种方式,我们可以将原本使用 reactive 实现的代码转换为使用 ref 实现的代码,这样做可以使代码更加简洁和直观,同时也方便我们进行数据的管理和修改。

前面说过,ref 函数并不只限于定义简单值,它仍然可以作用于对象。现在我们用 ref(obj) 的方式改写刚才的代码:

<template><button @click="changeName">修改name</button><br><button @click="changeAge">修改age</button><br><button @click="changeGender">修改gender</button><br>{{ person.name }} - {{ person.age }} - {{ person.gender }}
</template><script setup>
import { ref } from 'vue'const person = ref({name: 'Tom',age: 18,gender: 'male'
});// 修改ref对象的name
function changeName() {person.value.name = "Linda"console.log(person.value.name) // 输出 Linda
}// 修改ref对象的age
function changeAge() {person.value.age++;console.log(person.value.age) // 输出 19
}// 修改ref对象的gender
function changeGender() {person.value.gender = "female"console.log(person.value.gender) // 输出 female
}</script>

在修改 person 对象的属性时,需要使用 person.value 来访问内部的对象。

任务五 toRef() 与 toRefs()

toRef() 的使用场景

1 问题引入

先来看看下面的代码有什么问题:

<template><div><p>Name: {{ user.name }}</p><p>Age: {{ user.age }}</p><p>Gender: {{ user.gender }}</p><button @click="changeInfo">Change Info</button></div>
</template><script setup lang="ts">interface User {name: string;age: number;gender: string;
}const user: User = {name: 'John',age: 25,gender: 'male',
}function changeInfo() {user.name = 'Mary'user.age = 30user.gender = 'female'
}</script>

这段代码存在的问题是:在 Vue3 中使用了非响应式的对象 user。由于 user 对象是普通的 JavaScript 对象,而不是 Vue 的响应式对象,所以当 changeInfo() 函数修改了 user 对象的属性时,界面不会更新

2 解决办法

解决方法是使用 Vue 的响应式对象来代替普通 JavaScript 对象,例如使用 reactive() 函数来创建一个响应式对象。可以将 user 对象改为以下代码:

<template><div><p>Name: {{ user.name }}</p><p>Age: {{ user.age }}</p><p>Gender: {{ user.gender }}</p><button @click="changeInfo">Change Info</button></div>
</template><script setup lang="ts">
import { reactive } from 'vue'
interface User {name: string;age: number;gender: string;
}const user = reactive({name: 'John',age: 25,gender: 'male',
})function changeInfo() {user.name = 'Mary'user.age = 30user.gender = 'female'
}</script>

这段代码确实实现了页面的同步更新,但模板里反复使用 user.前缀 显得有些冗余,如何减少模板中重复使用 user.前缀 的情况,从而提高了代码的可读性呢?

3 继续改进

toRef 是 Vue3 中的一个工具函数,用于将响应式对象上的一个属性转换为一个单独的 ref 对象。我们尝试用它来减少模板中 user. 前缀的重复使用,代码如下:

<template><div><p>Name: {{ name }}</p> // 少了前缀<p>Age: {{ age }}</p><p>Gender: {{ gender }}</p><button @click="changeInfo">Change Info</button></div>
</template><script setup lang="ts">
import { reactive, toRef } from 'vue'interface User {name: string;age: number;gender: string;
}const user: User = reactive({name: 'John',age: 25,gender: 'male',
});const name = toRef(user, 'name')
const age = toRef(user, 'age')
const gender = toRef(user, 'gender')function changeInfo() {name.value = 'Mary'age.value = 30gender.value = 'female'
}</script>

当我们使用 toRef() 将一个响应式对象 user 的属性转化为 ref 后,该 ref 会与源对象的属性保持同步。这样创建的 ref 对象与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

toRefs() 的使用场景

一、问题引入

在上面示例 3 的代码中,如果对象的属性很多,每个属性都要保持响应性,那么势必多次调用 toRef() 函数,有没有什么办法可以一次性将响应式对象上的所有属性都转换为 ref 对象,从而简化代码呢?

二、解决办法

使用 Vue3 的 toRefs() 函数可以一次性将响应式对象上的所有属性都转换为 ref 对象,并使用对象解构语法来进一步简化代码,代码如下:

<template><div><p>Name: {{ name }}</p><p>Age: {{ age }}</p><p>Gender: {{ gender }}</p><button @click="changeInfo">Change Info</button></div>
</template><script setup lang="ts">
import { reactive, toRefs } from 'vue'interface User {name: string;age: number;gender: string;
}const user: User = reactive({name: 'John',age: 25,gender: 'male',
})const stateRefs = toRefs(user)
const { name, age, gender } = stateRefsfunction changeInfo() {name.value = 'Mary'age.value = 30gender.value = 'female'
}</script>

这段代码中使用 toRefs(user) 将响应式对象 user 中的所有属性(即name、age和gender)转换成对应的 ref 对象,并返回一个由这些 ref 对象组成的对象,这些 ref 对象的变化也会影响原响应式对象的属性。

然后使用对象解构将它们分别赋值给 name、age 和 gender,这样,我们在模板中就可以直接访问这三个变量,而不用写成 stateRefs.name、stateRefs.age 和 stateRefs.gender 的形式。在 changeInfo 函数中,直接修改 name.value、age.value 和 gender.value 的值,这些值的变化会直接反映到原响应式对象 user 的属性上,从而触发页面的更新。

任务六 computed 与计算属性

基本概念

computed 函数,是一个响应式 API,类似于 Vue2.x 中的 computed 属性。使用 computed 函数可以让代码更加简洁和高效。

computed 函数的值就是计算属性。computed 函数可以使用响应式依赖来创建计算属性,计算属性会自动追踪响应式依赖,在其任何响应式依赖项更改时自动重新计算。

在模板中绑定计算属性,当计算属性的值发生变化时,会触发组件的重新渲染。在模板中绑定计算属性的另一个好处是,可以在模板中声明性地指定复杂的逻辑,而不必手动更新值或在代码中执行计算。

使用计算属性

计算属性的使用场景。

例如,考虑一个简单的 Vue 3 组件,该组件显示一个商品列表和这些商品的总数:

<template><div><div v-if="isLoading">Loading...</div><div v-else><ul><li v-for="item in goods" :key="item.id">{{ item.name }}</li></ul><p>商品总数: {{ totalGoods }}</p></div></div>
</template><script setup lang="ts">
import { computed, ref } from 'vue'interface Item {id: number;name: string;price: number;
}const goods = ref<Item[]>([]);const isLoading = computed(() => {return goods.value.length === 0
})const totalGoods = computed(() => {return goods.value.length
})const baseUrl = 'http://localhost:3000'const fetchGoods = async () => {try {const res = await fetch(baseUrl + '/goods')const data = await res.json()goods.value = data as Item[]} catch (e) {throw new Error('an error happened' + e)}
}fetchGoods()</script>

在这个例子中,totalGoods 计算属性函数负责计算 goods 数组中的商品总数。isLoading 计算属性基于 goods 数组的长度返回一个布尔值,指示组件是否处于加载状态。

请注意,这里使用计算属性 isLoading 来定义应用程序是否正在加载。通常,很多开发人员会定义 isLoading 变量,并在调用 fetchGoods 时将变量 isLoading 设置为 true,然后在 API 请求完成后再将其设置为 false。比如:

<script setup lang="ts">const isLoading = ref(true)
// ...
const fetchGoods = async () => {try {isLoading.value = true// 网络访问的代码 // ...isLoading.value = false} catch (e) {// ...}
}fetchGoods()</script>

相对于 Goods02.vue,Goods.vue 使用计算属性实现了 Loading 的显示逻辑与商品数据获取逻辑之间的解耦

th === 0
})

const totalGoods = computed(() => {
return goods.value.length
})

const baseUrl = ‘http://localhost:3000’

const fetchGoods = async () => {
try {
const res = await fetch(baseUrl + ‘/goods’)
const data = await res.json()
goods.value = data as Item[]
} catch (e) {
throw new Error(‘an error happened’ + e)
}
}

fetchGoods()

```

在这个例子中,totalGoods 计算属性函数负责计算 goods 数组中的商品总数。isLoading 计算属性基于 goods 数组的长度返回一个布尔值,指示组件是否处于加载状态。

请注意,这里使用计算属性 isLoading 来定义应用程序是否正在加载。通常,很多开发人员会定义 isLoading 变量,并在调用 fetchGoods 时将变量 isLoading 设置为 true,然后在 API 请求完成后再将其设置为 false。比如:

<script setup lang="ts">const isLoading = ref(true)
// ...
const fetchGoods = async () => {try {isLoading.value = true// 网络访问的代码 // ...isLoading.value = false} catch (e) {// ...}
}fetchGoods()</script>

相对于 Goods02.vue,Goods.vue 使用计算属性实现了 Loading 的显示逻辑与商品数据获取逻辑之间的解耦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NCV12711ADNR2G芯片PWM控制器中文资料规格书PDF数据手册引脚图图片价格功能

产品概述&#xff1a; NCV12711是一款固定频率、峰值电流模式PWM控制器&#xff0c;具有实施单端功率转换器拓扑结构所需的必要性能。这款器件工作电压范围4V至45V&#xff0c;无需辅助绕组&#xff0c;且位于热性能范围内。这款控制器包含一个可编程振荡器&#xff0c;能够在…

elasticsearch安装部署

elasticsearch部署 安装elasticsearch1.部署单点es1.1.创建网络1.2.加载镜像1.3.运行 2.部署kibana2.1.部署2.2.DevTools 3.安装IK分词器3.1.在线安装ik插件&#xff08;较慢&#xff09;3.2.离线安装ik插件&#xff08;推荐&#xff09;3.3 扩展词词典3.4 停用词词典 4.部署es…

Jmeter接口登录获取参数token报错问题解决方案

Jmeter接口登录时获取到的参数token一直在变的问题&#xff0c;导致运行时总是报错 解决方法如下&#xff1a; 1.新建一个GET的HTTP请求 2.添加正则表达式提取器 记得name"_token" value"(.?) 中间有一个空格&#xff0c;“_token”和value中间的空格&#xf…

Linux第81步_使用“互斥体”实现“互斥访问”共享资源

1、创建MyMutexLED目录 输入“cd /home/zgq/linux/Linux_Drivers/回车” 切换到“/home/zgq/linux/Linux_Drivers/”目录 输入“mkdir MyMutexLED回车”&#xff0c;创建“MyMutexLED”目录 输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹 2、…

机器学习-可解释性机器学习:支持向量机与fastshap的可视化模型解析

一、引言 支持向量机(Support Vector Machine, SVM)作为一种经典的监督学习方法&#xff0c;在分类和回归问题中表现出色。其优点之一是生成的模型具有较好的泛化能力和可解释性&#xff0c;能够清晰地展示特征对于分类的重要性。 fastshap是一种用于快速计算SHAP值&#xff08…

进程创建,程序加载运行,以及进程终止,什么是僵尸进程,什么是孤儿进程

进程控制 创建进程&#xff0c;撤销进程&#xff0c;实现进程转换&#xff08;必须一气呵成&#xff0c;使用原语&#xff09; 原语不被中断是因为有关中断指令 创建进程 撤销进程 进程创建fork fork&#xff08;&#xff09;函数会创建一个子进程&#xff0c;子进程会返…

Uibot6.0 (RPA财务机器人师资培训第2天 )采购付款——网银付款机器人案例实战

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff0…

网络编程day7

学生管理系统系统 #include <myhead.h> int do_add(sqlite3 *ppDb) {int add_numb0;char add_name[20]"";double add_score0;printf("请输入学号&#xff1a;");scanf("%d", &add_numb);printf("请输入姓名&#xff1a;");…

你要的个性化生信分析服务今天正式开启啦!定制你的专属解决方案!全程1v1答疑!

之前在 干货满满 | 给生信小白的入门小建议 | 掏心掏肺版 中有提到&#xff0c;如果小伙伴们真的想学好生信&#xff0c;那编程能力是必须要有的&#xff01;但是可能有些小伙伴们并没有那么多的时间从头开始学习编程&#xff0c;又或是希望有人指导或者协助完成生信分析工作&a…

vue-admin-template极简的 vue admin 管理后台的动态路由实现方法

项目源码地址&#xff1a;GitHub - PanJiaChen/vue-admin-template: a vue2.0 minimal admin template 注意&#xff1a;项目中的路由均写在 src\router\index.js 中&#xff0c;其中默认包含 constantRoutes 数组&#xff0c;这是固定路由&#xff0c;无论用户是什么角色&…

读懂2023微博财报,“大V”们选择留下

2024年2月27日&#xff0c;董宇辉清空了自己158万粉丝的微博账号&#xff0c;无独有偶&#xff0c;此前微博粉丝数超300万的知名刑法教授罗翔老师&#xff0c;也在清空了账号后退出微博&#xff0c;引发全网舆论。如今前者名下的“与辉同行”和“董宇辉”抖音账号分别坐拥1711.…

spring boot3登录开发-2(2短信验证码接口实现)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 上文衔接 内容简介 短信验证码接口实现 1.依赖导入 2.接口分析 3.实现思路 3.功能实现 创建发送短信…

23.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[]示…

十四届蓝桥杯 BC A.日期统计

思路&#xff1a; 循环2023的每一天&#xff0c;一共八位数&#xff1b;年份是确定的&#xff0c;只需要循环月份和天数&#xff0c;注意这里已知2023的2月份天数为28天。用b数组 int b[8]{2,0,2,3,month/10,month%10,d/10,d%10};//枚举2023的每一天来和已知数据的八位数字比较…

Django日志(二)

一、Handler Handler决定如何处理logger中的每条消息。它表示一个特定的日志行为,例如 将消息写入屏幕、文件或网络Socket handler对应的是个字典,每一个键都是一个handler的名字,每个值又一个字典,描述了如何配置对应的handler实例 2.1、内置Handler class(必需):处理…

计算机408网课评测+资料分享

408当然有比较好的网课推荐&#xff0c;比如王道的视频课 现在大部分人备战408基本都用王道的讲义&#xff0c;然后再搭配王道408的课程来听&#xff0c;可以学的很好。 其中408视频课中&#xff0c;我认为讲的比较好的是数据结构&#xff0c;和操作系统&#xff0c;计算机组…

聚焦两会:数字化再加速,VR全景助力制造业转型

近年来&#xff0c;随着信息技术、人工智能、VR虚拟现实等新兴技术的不断涌现&#xff0c;数字化正日益成为推动当今经济发展的新驱动力。在不久前的两会上&#xff0c;数字化经济和创新技术再度成为热门话题&#xff1a; 国务院总理李强作政府工作报告&#xff1a; 要深入推…

extract变量覆盖

一、靶场解题过程 题目描述&#xff1a;try your best to find the flag. 进入靶场后看到的是一张图片 查看网页源代码&#xff0c;发现一个source.txt的注释信息&#xff0c;可能路径上有这个文件&#xff0c;尝试访问一下 访问后出现一个新的页面 该页面有一些php的代…

电子科技大学链时代工作室招新题C语言部分---题号H

1. 题目 最有操作的一道题&#xff0c;有利于对贪心算法有个初步了解。 这道题的开篇向我们介绍了一个叫汉明距离的概念。 汉明距离指的就是两个相同长度的字符串的不同字符的个数。 例如&#xff0c;abc和acd&#xff0c;b与c不同&#xff0c;c与d不同&#xff0c;所以这两个…

多线程(JUC, ReentrantLock, 原子类, 线程池, 信号量 Semaphore, CountDownLatch)

JUC Java.util.concurrent 包, 存放了并发编程相关的组件, 目的是更好的支持高并发任务 (多线程只是实现并发编程的一种具体方式 …) ReentrantLock 可重入互斥锁, 和 synchronized 定位类似, 用来实现互斥效果, 保证线程安全. synchronized 对对象加锁, 保护临界资源Reentreat…