第1章 前端工程化开篇
1.1 什么是前端工程化
**前端工程化是使用 软件工程的方法来 单独解决 前端的开发流程中 模块化、组件化、规范化、自动化**的问题,以提高效率和降低成本。
1.2 前端工程化实现技术栈
前端工程化实现的技术栈有很多,我们采用ES6+Nodejs +npm+Vite+VUE3+Router+Pinia+Axios+Element-plus组合来实现。
ECMAScript6 VUE3中大量使用ES6语法;
Nodejs 前端项目运行环境;
npm 依赖下载工具;
Vite 前端项目构建工具;
Vue3 优秀的渐进式前端框架;
Router 通过路由实现页面切换;
Pinia 通过状态管理实现组件数据传递;
Axios ajax异步请求封装技术实现前后端数据交互;
Element Plus 可以提供丰富的快速构建网页的组件仓库;
第2章 ECMA6Script
2.1 ES6的介绍
ECMAScript 6,简称ES6,是JavaScript 语言的一次重大更新。它于2015 年发布,是原来的ECMAScript标准的第六个版本。ES6带来了大量的新特性,包括箭头函数、模板字符串、let和const关键字、解构、默认参数值、模块系统等等,大大提升了JavaScript的开发体验。由于VUE3中大量使用了ES6的语法,所以ES6成为了学习VUE3的门槛之一。ES6对JavaScript的改进在以下几个方面:
更加简洁:ES6引入了一些新的语法,如箭头函数、模板字符串等,使代码更加简洁易懂;
更强大的功能:ES6引入了一些新的API、解构语法和迭代器等功能,从而使得JavaScript更加强大;
更好的适用性:ES6引入的模块化功能为JavaScript代码的组织和管理提供了更好的方式,不仅提高了程序的可维护性,还让JavaScript更方便地应用于大型的应用程序;
总的来说,ES6在提高JavaScript的核心语言特性和功能方面取得了很大的进展。由于ES6已经成为了JavaScript的标准,它的大多数新特性都已被现在浏览器所支持,因此现在可以放心地使用ES6来开发前端应用程序。
历史版本:
标准版本
发布时间
新特性
ES1
1997年
第一版 ECMAScript
ES2
1998年
引入setter和getter函数,增加了try/catch,switch语句允许字符串
ES3
1999年
引入了正则表达式和更好的字符串处理
ES4
取消
取消,部分特性被ES3.1和ES5继承
ES5
2009年
Object.defineProperty,JSON,严格模式,数组新增方法等
ES5.1
2011年
对ES5做了一些勘误和例行修订
ES6
2015年
箭头函数、模板字符串、解构、let和const关键字、类、模块系统等
ES2016
2016年
数组.includes,指数操作符(**),Array.prototype.fill等
ES2017
2017年
异步函数async/await,Object.values/Object.entries,字符串填充
ES2018
2018年
正则表达式命名捕获组,几个有用的对象方法,异步迭代器等
ES2019
2019年
Array.prototype.{flat,flatMap},Object.fromEntries等
ES2020
2020年
BigInt、动态导入、可选链操作符、空位合并操作符
ES2021
2021年
String.prototype.replaceAll,逻辑赋值运算符,Promise.any等
… …
2.2 es6的变量和模板字符串
ES6 新增了let和const,用来声明变量,使用的细节上也存在诸多差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <script > console .log (a); let a = "apple" ; console .log (b); var b = "banana" ; </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 <script > const PI = 3.1415926 ; PI =3.14 const TEAM = ['刘德华' ,'张学友' ,'郭富城' ]; TEAM .push ('黎明' ); TEAM =[] console .log (TEAM ) </script >
模板字符串(template string)是增强版的字符串,用飘号,也叫反引号(`)标识 。
1、字符串中可以出现换行符;
2、可以使用 ${xxx} 形式输出变量和拼接变量;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script > let ulStr = '<ul>' + '<li>JAVA</li>' + '<li>html</li>' + '<li>VUE</li>' + '</ul>' console .log (ulStr) let ulStr2 = ` <ul> <li>JAVA</li> <li>html</li> <li>VUE</li> </ul>` console .log (ulStr2) let name ='张小明' let infoStr =name+'被评为本年级优秀学员' console .log (infoStr) let infoStr2 =`${name} 被评为本年级优秀学员` console .log (infoStr2) </script >
2.3 es6的解构表达式
ES6 的解构赋值是一种方便的语法,可以快速将数组或对象中的值拆分并赋值给变量。解构赋值的语法使用花括号 {} 表示对象,方括号 [] 表示数组。通过解构赋值,函数更方便进行参数接受等!
数组解构赋值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 let [a,b,c] = [1 ,2 ,3 ];console .log (a);console .log (b);console .log (c);console .log ("----------------" )let [d,[f],g] = [1 ,[2 ],3 ];console .log (d);console .log (f);console .log (g);console .log ("----------------" )let [h,i] = [1 ,2 ,3 ];console .log (h);console .log (i);console .log ("----------------" )let [j,,k] = [1 ,2 ,3 ];console .log (j);console .log (k);console .log ("----------------" )let [l,m = 2 ] = [1 ];console .log (l);console .log (m);console .log ("----------------" )let [n,...o] = [1 ,2 ,3 ,4 ,5 ];console .log (n); console .log (o); console .log ("----------------" )let [aa,bb,cc,dd] = 'abcdf' ;console .log (aa);console .log (bb);console .log (cc);console .log (dd);console .log ("----------------" )
对象解构赋值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let {name,age} = {name :"aaa" ,age :18 }console .log (name);console .log (age);console .log ("----------------" )let {name :ne,age :ae} = {name :"aaa" ,age :18 };console .log (ne);console .log (ae);console .log ("----------------" )let obj = {p :['hello' ,{y :'world' }]};let {p :[ff,{y :yy}]} = obj;let obj1 = {p : ['hello' , {y : 'world' }] };let {p : [x, { }] } = obj1;let {a :aaa, b :bbb, ...rest} = {a : 10 , b : 20 , c : 30 , d : 40 };
函数参数解构赋值 :
1 2 3 4 function add ([x, y] ) { return x + y; } add ([1 , 2 ]);
2.4 es6的箭头函数
ES6 允许使用“箭头” 函数。语法类似Java中的Lambda表达式。
2.4.1 声明
1 2 3 4 5 6 7 8 9 10 11 12 13 <script > function fun ( ){} let fun1 = function ( ){} let fun2 = ( )=>{} let fun3 = x =>{} let fun4 = x => console .log (x) let fun5 = x => x + 1 </script >
2.4.2 参数默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script > let sum = (a,b )=>{ return a + b } let result = sum (1 ,1 ) console .log (result); let result1 = sum (1 ) console .log (result1); let sum1 = (a,b=1 )=>{ return a + b } let result2 = sum1 (1 ,2 ) console .log (result2); let result3 = sum1 (1 ) console .log (result3); </script >
2.4.3 三个点扩展运算符
用法一:作为可变参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script > let fun = function (a,...args ){ console .log (a); console .log (args) } let fun1 = (a,...args ) =>{ console .log (args) } fun (1 ,2 ,3 ) fun1 (1 ,2 ,3 ,4 ) </script >
用法二:复制数组和对象的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script > let arr =[1 ,2 ,3 ] let arr2=[4 ,5 ,6 ] let arr3=[...arr,...arr2] console .log (arr3) let p1={name :"张三" } let p2={age :10 } let p3={gender :"boy" } let person ={...p1,...p2,...p3} console .log (person) </script >
2.5 链判断
如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。
比如,读取emp.dept.name这个属性,安全的写法是写成下面这样:
1 2 3 4 5 6 7 8 9 let emp = null const empName = (emp && emp.dept && emp.dept .name || 'default' ) console .log (empName)
这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?. ,简化上面的写法:
1 2 3 4 5 6 7 8 9 10 let emp = { name :"张三" , dept :{ name :"教学部" } } const empName = emp?.dept ?.name || 'default'
2.6 es6的模块化处理
2.6.1模块化介绍
模块化是一种组织和管理前端代码的方式,将代码拆分成小的模块单元,使得代码更易于维护、扩展和复用。它包括了定义、导出、导入以及管理模块的方法和规范。前端模块化的主要优势如下:
提高代码可维护性:通过将代码拆分为小的模块单元,使得代码结构更为清晰,可读性更高,便于开发者阅读和维护;
提高代码可复用性:通过将重复使用的代码变成可复用的模块,减少代码重复率,降低开发成本;
提高代码可扩展性:通过模块化来实现代码的松耦合,便于更改和替换模块,从而方便地扩展功能;
目前,前端模块化有多种规范和实现,包括 CommonJS、AMD 和 ES6 模块化。ES6 模块化是 JavaScript 语言的模块标准,使用 import 和 export 关键字来实现模块的导入和导出。现在,大部分浏览器都已经原生支持 ES6 模块化,因此它成为了最为广泛使用的前端模块化标准.。
ES6模块化的几种暴露和导入方式:
分别导出;
统一导出;
默认导出;
ES6中无论以何种方式导出,导出的都是一个对象,导出的内容都可以理解为是向这个对象中添加属性或者方法!!!
2.6.2 分别导出
1 2 3 4 5 6 7 8 export const PI = 3.14 export function sum (a, b ) { return a + b; }
1 2 3 4 5 6 7 8 9 10 import * as m1 from './module.js' console .log (m1.PI )let result =m1.sum (10 ,20 )console .log (result)
index.html作为程序启动的入口 ,导入 app.js :
1 2 <script src ="./app.js" type ="module" />
2.6.3 统一导出
1 2 3 4 5 6 7 8 9 10 11 12 13 const PI = 3.14 function sum (a, b ) { return a + b; } export { PI , sum }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import {PI ,sum } from './module.js' import {PI as pi,sum as add} from './module.js' console .log (PI )console .log (pi)let result1 =sum (10 ,20 )console .log (result1)let result2 =add (10 ,20 )console .log (result2)
2.6.4 默认导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export const PI = 3.14 function sum (a, b ) { return a + b; } export default sum
app.js 的default和其他导入写法混用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import * as m1 from './module.js' import {default as add} from './module.js' import add2 from './module.js' let result =m1.default (10 ,20 )console .log (result)let result2 =add (10 ,20 )console .log (result2)let result3 =add2 (10 ,20 )console .log (result3)import {PI } from './module.js' console .log (PI )
第3章 前端工程化环境搭建
3.1 Nodejs的介绍与安装
3.1.1 什么是Nodejs
Node.js 是一个基于 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 等多种平台下运行;
Node.js 的核心是其管理事件和异步 I/O 的能力。Node.js 的异步 I/O 使其能够处理大量并发请求,并且能够避免在等待 I/O 资源时造成的阻塞。此外,Node.js 还拥有高性能网络库和文件系统库,可用于搭建 WebSocket 服务器、上传文件等。在 Node.js 中,我们可以使用 JavaScript 来编写服务器端程序,这也使得前端开发人员可以利用自己已经熟悉的技能来开发服务器端程序,同时也让 JavaScript 成为一种全栈语言。Node.js 受到了广泛的应用,包括了大型企业级应用、云计算、物联网、游戏开发等领域。常用的 Node.js 框架包括 Express、Koa、Egg.js 等,它们能够显著提高开发效率和代码质量。
3.1.2 如何安装Nodejs
打开官网https://nodejs.org/en下载对应操作系统的 LTS 版本。
双击安装包进行安装,安装过程中遵循默认选项即可(或者参照https://www.runoob.com/nodejs/nodejs-install-setup.html )。安装完成后,可以在命令行终端输入 node -v 和 npm -v 查看 Node.js 和 npm 的版本号。
定义一个app.js文件,文件内定义如下代码。cmd到该文件所在目录,然后在dos上通过node app.js命令即可运行。
3.2 npm 配置和使用
3.2.1 npm介绍
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于后端的Maven的部分功能 。
3.2.2 npm 安装和配置
1、安装 :安装Nodejs,自动安装npm包管理工具!
2、配置依赖下载使用阿里镜像:
npm 安装依赖包时默认使用的是官方源,由于国内网络环境的原因,有时会出现下载速度过慢的情况。为了解决这个问题,可以配置使用阿里镜像来加速 npm 的下载速度,打开命令行终端,执行以下命令,配置使用阿里镜像:
1 npm config set registry https://registry.npmmirror.com
验证配置,查看当前 registry 的配置:如果输出结果为 https://registry.npmmirror.com,说明配置已成功生效。
1 2 npm config get registry npm config list
1 npm config set registry https :
3、配置全局依赖下载后存储位置:
4、升级npm版本:
cmd 输入npm -v 查看版本,如果node中自带的npm版本过低!则需要升级至9.6.6!
1 npm install -g npm@9.6.6
3.2.3 npm 常用命令
1、项目初始化:
npm init
进入一个vscode创建好的项目中,执行 npm init 命令后,npm 会引导您在命令行界面上回答一些问题,例如项目名称、版本号、作者、许可证等信息,并最终生成一个package.json 文件。package.json信息会包含项目基本信息!类似maven的pom.xml。
npm init -y
执行,-y yes的意思,所有信息使用当前文件夹的默认值!不用挨个填写!
2、安装依赖 (查看所有依赖地址 https://www.npmjs.com ):
npm install 包名 或者 npm install 包名@版本号
npm install -g 包名
安装全局依赖包(安装到d:/GlobalNodeModules)则可以在任何项目中使用它,而无需在每个项目中独立安装该包。
npm install
3、升级依赖:
4、卸载依赖:
5、查看依赖:
6、运行命令:
npm run 命令是在执行 npm 脚本时使用的命令。npm 脚本是一组在 package.json 文件中定义的可执行命令。npm 脚本可用于启动应用程序,运行测试,生成文档等,还可以自定义命令以及配置需要运行的脚本。
在 package.json 文件中,scripts 字段是一个对象,其中包含一组键值对,键是要运行的脚本的名称,值是要执行的命令。例如,以下是一个简单的 package.json 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package.json 是每个 npm 项目的基础配置文件,它定义了项目的依赖关系、版本号、入口脚本、包的元数据等信息。 一个基本的 package.json 文件包含以下字段: name: 包名 version: 版本号,遵循语义化版本控制(Semantic Versioning) description: 包的描述 main: 入口点脚本文件,通常是启动时加载的文件 scripts: 定义运行脚本的脚本命令 dependencies: 生产环境依赖 devDependencies: 开发环境依赖 repository: 代码仓库地址 keywords: 关键词数组,有助于 npm 搜索 author: 作者信息 license: 许可证 { "name" : "example-package" , "version" : "1.0.0" , "description" : "A sample npm package" , "main" : "index.js" , "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" } , maven / 依赖 / scope / compile 全程需要 | runtime 写代码时候无法使用, 运行才会有 mysql | provided main test 打包以后不使用 [ [ servlet] ] | test junit "dependencies" : (compile) { "express" : "4.17.1" , #准确版本 "express" : "^4.17.1" #大版本必须4 锁住大版本 } , "devDependencies" : (provided) { "nodemon" : "^2.0.7" } , "repository" : { "type" : "git" , "url" : "git+https://github.com/username/example-package.git" } , "keywords" : [ "example" , "npm" ] , "author" : "Your Name" , "license" : "ISC" } ### 环境依赖配置 dependencies 生产环境依赖 (compile) devDependencies 开发环境依赖 (provided) 当你执行 npm install(没有额外参数时)时,dependencies 会被自动安装。 当你执行 npm install --production 或者设置环境变量为 NODE_ENV=production 时,devDependencies 会被忽略,只会安装 dependencies 中的依赖。 ### name name 是指软件包名。 命名规则: 名称必须小于或等于 214 个字符。这包括范围软件包的范围。 作用域软件包的名称可以以点或下划线开头。如果没有作用域,则不允许这样做。 新软件包名称中不能有大写字母。 名称最终会成为 URL、命令行参数和文件夹名称的一部分。因此,名称中不能包含任何非 URL 安全字符。 名称可选择以作用域作为前缀,例如 @myorg/mypackage。 提示: 不要使用与 Node 核心模块相同的名称。 不要在名称中使用 "js " 或 “node”。因为你编写的是 package.json 文件,所以我们假定它是 js,你可以使用 "引擎 " 字段指定引擎。(见下文)。 该名称可能会作为参数传递给 require(),因此应该简短,但也要有合理的描述性。 你可能需要检查一下 npm 注册表,看看是否已经有使用该名称的软件,以免过于依赖它。https: 这是发布包的注意事项,如果你的项目不准备分享发布,则无所谓,建议和你项目相同。 ### version 指发布包版本号, x.x.x的形式。 版本必须能被 node-semver 解析,node-semver 作为依赖项与 npm 捆绑。(npm install semver 可自行使用)。 命名版本号建议遵循版本语义规范, : ### description 项目描述。 这是一个字符串。这有助于人们发现你的软件包,因为它会在 npm 搜索中列出。 ### keywords 项目关键字。 这是一个字符串数组。这有助于人们在 npm 搜索中发现你的软件包。 ### scripts 运行命令集合(字典) 脚本 "属性是一个字典,包含在软件包生命周期的不同时间运行的脚本命令。键是生命周期事件,值是在该时刻运行的命令。 package.json 文件的 " scripts "属性支持大量内置脚本、预设生命周期事件以及任意脚本。这些脚本都可以通过运行 npm run-script 或 npm run 来执行。名称匹配的前置和后置命令也会被执行(例如 premyscript、myscript、postmyscript)。使用 npm explore – npm run 可以运行来自依赖项的脚本。 例如: { " scripts": { " precompress": " { { executes BEFORE the `compress` script } } ", " compress": " { { run command to compress files } } ", " postcompress": " { { executes AFTER `compress` script } } " } } npm run compress 就会执行compress后面对应的命令!
scripts 对象包含 start、test 和 build 三个脚本。当您运行 npm run start 时,将运行 node index.js,并启动应用程序。同样,运行 npm run test 时,将运行 Jest 测试套件,而 npm run build 将运行 webpack 命令以生成最终的构建输出。
总之,npm run 命令为您提供了一种在 package.json 文件中定义和管理一组指令的方法,可以在项目中快速且灵活地运行各种操作。
第4章 Vue3框架
4.1 Vue3简介
4.1.1 Vue3介绍
Vue (发音为 /vjuː/,类似 view ) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。官网为:https://cn.vuejs.org/
Vue的两个核心功能:
声明式渲染 :Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系;
响应性 :Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM ;
VUE作者:尤雨溪
尤雨溪(Evan You),毕业于科尔盖特大学,前端框架Vue.js的作者、HTML5版Clear的打造人、独立开源开发者。曾就职于Google Creative Labs和Meteor Development Group。由于工作中大量接触开源的JavaScript项目,最后自己也走上了开源之路,现全职开发和维护Vue.js;
尤雨溪毕业于上海复旦附中,在美国完成大学学业,本科毕业于Colgate University,后在Parsons设计学院获得Design & Technology艺术硕士学位,任职于纽约Google Creative Lab;
尤雨溪大学专业并非是计算机专业,在大学期间他学习专业是室内艺术和艺术史,后来读了美术设计和技术的硕士,正是在读硕士期间,他偶然接触到了JavaScript,从此被这门编程语言深深吸引,开启了自己的前端生涯;
4.1.2 Vue3快速体验(非工程化方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Vue3初体验(非工程化方式)</title > </head > <body > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > <div id ="app" > <h1 > {{ message }}</h1 > <p v-text ="message" > </p > </div > <script > const app = Vue .createApp ({ setup ( ) { let message = "Hello Vue3" return { message } } }) app.mount ("#app" ) </script > </html >
更完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue3完整流程</title> </head> <body> <!-- 第一步:准备HTML模板 --> <div id="app"> <h1>{{ title }}</h1> <p>计数器: {{ count }}</p> <button @click="increment">点击+1</button> </div> <!-- 第二步:引入Vue --> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script> // 第三步:创建Vue应用 const app = Vue.createApp({ setup() { // 数据 let count = 0 let title = "我的Vue应用" // 方法 function increment() { count++ } // 返回给模板使用 return { count, title, increment } } }) // 第四步:挂载到DOM app.mount("#app") </script> </body> </html>
4.2 Vue3通过Vite实现工程化
4.2.1 Vite的介绍
在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。时过境迁,我们见证了诸如 webpack 、Rollup 和 Parcel 等工具的变迁,它们极大地改善了前端开发者的开发体验。
当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长;
包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感;
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。https://cn.vitejs.dev/guide/why.html。前端工程化的作用包括但不限于以下几个方面:
快速创建项目:使用脚手架可以快速搭建项目基本框架,避免从零开始搭建项目的重复劳动和繁琐操作,从而节省时间和精力;
统一的工程化规范:前端脚手架可以预设项目目录结构、代码规范、git提交规范等统一的工程化规范,让不同开发者在同一个项目上编写出风格一致的代码,提高协作效率和质量;
代码模板和组件库:前端脚手架可以包含一些常用的代码模板和组件库,使开发者在实现常见功能时不再重复造轮子,避免因为轮子质量不高带来的麻烦,能够更加专注于项目的业务逻辑;
自动化构建和部署:前端脚手架可以自动进行代码打包、压缩、合并、编译等常见的构建工作,可以通过集成自动化部署脚本,自动将代码部署到测试、生产环境等;
4.2.2 Vite创建Vue3工程化项目
1、Vite+Vue3项目的创建、启动、停止
1 使用命令行创建工程。
在磁盘的合适位置上,创建一个空目录用于存储多个前端项目;
用vscode打开该目录;
在vscode中打开命令行运行如下命令;
第一次使用Vite时会提示下载vite,输入y回车即可,下次使用Vite就不会出现了;
注意: 输入项目名称vue3-demo,选择Vue+JavaScript选项即可;
2 安装项目所需依赖:
cd进入刚刚创建的项目目录;
npm install命令安装基础依赖;
1 2 cd ./vue3-demo npm install
3 启动项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "name" : "vue3-demo" , "private" : true , "version" : "0.0.0" , "type" : "module" , "scripts" : { "dev" : "vite" , "build" : "vite build" , "preview" : "vite preview" } , "dependencies" : { "bootstrap" : "^5.2.3" , "sass" : "^1.62.1" , "vue" : "^3.2.47" } , "devDependencies" : { "@vitejs/plugin-vue" : "^4.1.0" , "vite" : "^4.3.2" } }
5 停止项目:
2、Vite+Vue3项目的目录结构
1.下面是 Vite 项目结构和入口的详细说明:
public/ 目录:用于存放一些公共资源,如 HTML 文件、图像、字体等,这些资源会被直接复制到构建出的目标目录中。
src/ 目录:存放项目的源代码,包括 JavaScript、CSS、Vue 组件、图像和字体等资源。在开发过程中,这些文件会被 Vite 实时编译和处理,并在浏览器中进行实时预览和调试。以下是src内部划分建议:
assets/ 目录:用于存放一些项目中用到的静态资源,如图片、字体、样式文件等。
components/ 目录:用于存放组件相关的文件。组件是代码复用的一种方式,用于抽象出一个可复用的 UI 部件,方便在不同的场景中进行重复使用。
layouts/ 目录:用于存放布局组件的文件。布局组件通常负责整个应用程序的整体布局,如头部、底部、导航菜单等。
pages/ 目录:用于存放页面级别的组件文件,通常是路由对应的组件文件。在这个目录下,可以创建对应的文件夹,用于存储不同的页面组件。
plugins/ 目录:用于存放 Vite 插件相关的文件,可以按需加载不同的插件来实现不同的功能,如自动化测试、代码压缩等。
router/ 目录:用于存放 Vue.js 的路由配置文件,负责管理视图和 URL 之间的映射关系,方便实现页面之间的跳转和数据传递。
store/ 目录:用于存放 Vuex 状态管理相关的文件,负责管理应用程序中的数据和状态,方便统一管理和共享数据,提高开发效率。
utils/ 目录:用于存放一些通用的工具函数,如日期处理函数、字符串操作函数等。
vite.config.js 文件:Vite 的配置文件,可以通过该文件配置项目的参数、插件、打包优化等。该文件可以使用 CommonJS 或 ES6 模块的语法进行配置。
package.json 文件:标准的 Node.js 项目配置文件,包含了项目的基本信息和依赖关系。其中可以通过 scripts 字段定义几个命令,如 dev、build、serve 等,用于启动开发、构建和启动本地服务器等操作。
Vite 项目的入口为 src/main.js 文件,这是 Vue.js 应用程序的启动文件,也是整个前端应用程序的入口文件。在该文件中,通常会引入 Vue.js 及其相关插件和组件,同时会创建 Vue 实例,挂载到 HTML 页面上指定的 DOM 元素中。
2.vite的运行界面:
在安装了 Vite 的项目中,可以在 npm scripts 中使用 vite 可执行文件,或者直接使用 npx vite 运行它。下面是通过脚手架创建的 Vite 项目中默认的 npm scripts:(package.json)。
1 2 3 4 5 6 7 { "scripts" : { "dev" : "vite" , "build" : "vite build" , "preview" : "vite preview" } }
运行设置端口号:(vite.config.js)。
1 2 3 4 5 6 7 export default defineConfig ({ plugins : [vue ()], server :{ port :3000 } })
3、Vite+Vue3项目组件(SFC入门)
什么是VUE的组件?
一个页面作为整体,是由多个部分组成的,每个部分在这里就可以理解为一个组件;
每个.vue文件就可以理解为一个组件,多个.vue文件可以构成一个整体页面;
组件化给我们带来的另一个好处就是组件的复用和维护非常的方便;
什么是.vue文件?
传统的页面有html文件css文件和js文件三个文件组成(多文件组件) ;
vue将这文件合并成一个vue文件(Single-File Component,简称 SFC,单文件组件); 整个项目叫SPA(single page application)
vue文件对js/css/html统一封装,这是VUE中的概念,该文件由三个部分组成 <script> <template> <style>;我们主要写的是<script> html部分Elementplus可以解决如果不会问ai嘛
template标签 代表组件的html部分代码,代替传统的html文件;
script标签 代表组件的js代码,代替传统的js文件;
style标签 代表组件的css样式代码,代替传统的css文件 ;
工程化vue项目如何组织这些组件?
index.html是项目的入口,其中 <div id ='app'></div>是用于挂载所有组建的元素;
index.html中的script标签引入了一个main.js文件,具体的挂载过程在main.js中执行;
main.js是vue工程中非常重要的文件,他决定这项目使用哪些依赖,导入的第一个组件;(控制布局)
App.vue是vue中的核心组件,所有的其他组件都要通过该组件进行导入,该组件通过路由可以控制页面的切换;
4、Vite+Vue3响应式入门和setup函数
1 删除App.vue中自带的内容
1 2 3 4 5 6 7 8 9 10 <script > </script > <template > </template > <style scoped > /** 存储的是css代码! <style scoped > 是 Vue.js 单文件组件中用于设置组件样式的一种方式。 它的含义是将样式局限在当前组件中,不对全局样式造成影响。 */ </style >
2 Vue3响应式数据入门:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <script type ="module" > import {ref} from 'vue' export default { setup ( ){ let counter = ref (1 ) function increase ( ){ counter.value ++ } function decrease ( ){ counter.value -- } return { counter, increase, decrease } } } </script > <template > <div > <button @click ="decrease()" > -</button > {{ counter }} <button @click ="increase()" > +</button > </div > </template > <style scoped > button { border : 1px solid red; } </style >
3 Vue3 setup函数和语法糖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!-- 组合式API编程模板--> <script type="module" setup> /* 通过setup关键字,可以省略 export default {setup(){ return{}}}这些冗余的语法结构 */ import {ref} from 'vue' // 定义响应式数据 let counter = ref(1) // 定义函数 function increase(){ counter.value++ //如果使用ref()声明响应式变量 js代码注意必须要加.value 指令或者插值表达式不用加 } function decrease(){ counter.value-- } </script> <template> <div> <button @click="decrease()">-</button> {{ counter }} <button @click="increase()">+</button> </div> </template> <style scoped> button{ border: 1px solid red; } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup> // 导入ref函数 响应式变量 import { ref } from 'vue' //let counter = 1;//不是响应式的 let counter = ref(1); </script> <template> <div> <button @click="counter--">-</button> =========={{ counter }}========== <button @click="counter++">+</button> </div> </template> <style scoped> button { border: 1px solid red; } </style>
5、Vite+Vue3关于样式的导入方式
全局引入main.js;针对所有的vue页面生效1 import './style/reset.css'
vue文件script代码引入;1 import './style/reset.css'
Vue文件style代码引入;[不推荐]1 @import './style/reset.css' ;
6、vscode中创建vue3SFC模版
vscode点击设置
搜索vue.json配置参数
添加vue3 SFC模板代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "Print to console" : { "prefix" : "sfc" , "body" : [ "<script setup>" , "" , "</script>" , "" , "<template>" , " <div>" , "" , " </div>" , "</template>" , "" , "<style scoped>" , "" , "</style>" , ] , "description" : "vue3的sfc模板" } }
使用vue3模板
4.3 Vue3视图渲染技术
4.3.1 模版语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
1、插值表达式和文本渲染
插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}:
插值表达式是将数据渲染到元素的指定位置的手段之一;
插值表达式不绝对依赖标签,其位置相对自由;
插值表达式中支持javascript的运算表达式;
插值表达式中也支持函数的调用;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script setup > let age = 18 let message = "测试插值表达式" let getAddress = ( )=>{ return "宏福苑小区" } </script > <template > {{ message }} <h1 > {{ message }}</h1 > <p > {{ message }}</p > <span style ="color: red;" > {{ message }}</span > <h1 > 你成年了吗:{{ age>=18?"已成年":"未成年"}}</h1 > <h1 > 你的居住地:{{ getAddress() }}</h1 > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script setup > let message = '永远不停止与这个世界对抗!' let age = 15 let getAddress = ( ) => { return '中国' } </script > <template > <div > <h2 > {{ message }}</h2 > <h3 v-text ="message" > </h3 > <h2 > {{ age >= 18 ? '成年啦!' : "未成年" }}</h2 > <h3 > {{ getAddress() }}</h3 > </div > </template > <style scoped > </style >
为了渲染双标中的文本,我们也可以选择使用v-text和v-html命令:
v-*** 这种写法的方式使用的是vue的命令;
v-***的命令必须依赖元素,并且要写在元素的开始标签中;
v-***指令支持ES6中的模板字符串;
插值表达式中支持javascript的运算表达式;
插值表达式中也支持函数的调用;
v-text可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本;
v-html可以将数据渲染成双标签中间的文本,识别html元素结构的文本;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script setup > let message = "测试文本渲染" let msg = "<font color='red'>我要红</font>" </script > <template > <h1 v-text ="message" > </h1 > <h1 v-html ="message" > </h1 > <p v-text ="message" > </p > <span style ="color: red;" v-text ="message" > </span > 8 <div v-text ="msg" > </div > <div v-html ="msg" > </div > </template >
2、Attribute属性渲染
想要渲染一个元素的 attribute,应该使用 v-bind指令。
由于插值表达式不能直接放在标签的属性中,要渲染元素的属性就应该使用v-bind;
v-bind可以用于渲染任何元素的属性,语法为 v-bind:属性名='数据名', 可以简写为 :属性名='数据名';
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script setup > let data = "测试属性渲染" let link = "http://www.yutao.com" </script > <template > <input v-bind:value ="data" > </input > <br > <input :value ="data" > </input > <br > <div :id ="data" > 我是div标签,我的id属性是通过Vue渲染的</div > <a :href ="link" > 点我有惊喜</a > <div :id > </div > <div v-bind:id > </div > </template >
3、事件的绑定
我们可以使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。
用法:v-on:click="handler" 或简写为 @click="handler";
vue中的事件名=原生事件名去掉on 前缀 如:onClick --> click;
handler的值可以是方法事件处理器,也可以是内联事件处理器;
绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下:
.once:只触发一次事件。[重点];
.prevent:阻止默认事件。[重点],例如:a标签添加阻止默认事件,就不触发href属性对应的地址了;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <script setup > import { ref } from 'vue' let count = ref (0 ) let add = ( ) => { count.value ++ } let sub = ( ) => { count.value -- } let changeData = ( ) => { console .log ("文本框中的内容发生了变化" ) } let blurF = ( ) => { console .log ("文本框中失去了焦点" ) } let stopDef = (event ) => { event.preventDefault () } </script > <template > <button v-on:click ="add" > ➕</button > {{ count }} <button @click ="sub" > ➖</button > <br > 给文本框绑定内容改变的事件:<input @change ="changeData" > </input > <br > 给文本框绑定失去焦点的事件:<input @blur ="blurF" > </input > <br > <button @click.once ="count++" > count只会被加一次</button > <br > <a href ="http://www.u.com" target ="_blank" @click.prevent ="" > 官网</a > <br > <a href ="http://www..com" target ="_blank" @click ="stopDef($event)" > 官网</a > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script setup > import { ref } from 'vue' let message = ref ('请输入姓名' )let changeData = (data ) => { message.value = data } let blurInput = (event ) => { message.value = event.target .value } </script > <template > <div > 姓名:{{ message }} <input :value ="message" @change ="changeData($event.target.value)" > 《输入框失去焦点时触发》 <input :value ="message" @blur ="blurInput" > {{ message }} <br > 只能触发一次 抽奖的场景可以使用 阻止链接跳转 </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 <template > <div style ="padding: 20px;" > <h2 > Vue3 事件处理详解</h2 > <div style ="margin: 20px 0; padding: 10px; border: 1px solid #ccc;" > <h3 > 文本框事件</h3 > <p > 输入的值:{{ inputValue }}</p > <label > change事件(失去焦点才触发):</label > <input @change ="handleChange" placeholder ="输入后点击别处" > <br > <br > <label > blur事件(失去焦点就触发):</label > <input @blur ="handleBlur" placeholder ="失去焦点时触发" > <br > <br > <label > input事件(实时触发):</label > <input @input ="handleInput" placeholder ="实时输入触发" > </div > <div style ="margin: 20px 0; padding: 10px; border: 1px solid #ccc;" > <h3 > 按钮事件</h3 > <p > 计数器:{{ count }}</p > <button @click ="increment" > 普通点击(每次+1)</button > <button @click.once ="count++" > 只执行一次(只会+1一次)</button > <div @click ="parentClick" style ="background: lightblue; padding: 20px;" > 父级容器(点击我) <button @click ="childClick" > 子级按钮(点击我)</button > <button @click.stop ="childClickStop" > 阻止冒泡的按钮</button > </div > </div > <div style ="margin: 20px 0; padding: 10px; border: 1px solid #ccc;" > <h3 > 链接事件</h3 > <p > <a href ="https://www.baidu.com" @click.prevent ="handleLink" > 阻止跳转的链接</a > </p > <p > <a href ="https://www.baidu.com" @click ="handleLinkWithDefault" > 正常跳转的链接</a > </p > <p > <a href ="https://www.baidu.com" @click ="stopDefault($event)" > JS阻止跳转</a > </p > </div > <div style ="margin: 20px 0; padding: 10px; border: 1px solid #ccc;" > <h3 > 键盘事件</h3 > <p > 按下的键:{{ pressedKey }}</p > <input @keyup.enter ="handleEnter" placeholder ="按回车键试试" > <input @keyup ="handleKeyup" placeholder ="按任意键试试" > </div > </div > </template > <script setup > import { ref } from 'vue' const inputValue = ref ('' )const count = ref (0 )const pressedKey = ref ('' )function handleChange (event ) { console .log ('change事件触发:' , event.target .value ) inputValue.value = event.target .value alert ('change事件:内容改变了!' ) } function handleBlur (event ) { console .log ('blur事件触发:' , event.target .value ) alert ('blur事件:失去焦点了!' ) } function handleInput (event ) { console .log ('input事件触发:' , event.target .value ) inputValue.value = event.target .value } function increment ( ) { count.value ++ console .log ('计数器:' , count.value ) } function parentClick ( ) { console .log ('父级被点击了' ) alert ('父级容器被点击' ) } function childClick ( ) { console .log ('子级被点击了' ) alert ('子级按钮被点击' ) } function childClickStop ( ) { console .log ('阻止冒泡的按钮被点击' ) alert ('这个按钮点击不会冒泡到父级' ) } function handleLink ( ) { console .log ('链接被点击,但被阻止了跳转' ) alert ('链接被点击,但不会跳转!' ) } function handleLinkWithDefault ( ) { console .log ('链接被点击,会正常跳转' ) alert ('链接被点击,即将跳转!' ) } function stopDefault (event ) { console .log ('使用JS阻止默认行为' ) event.preventDefault () alert ('使用JS阻止了跳转!' ) } function handleEnter ( ) { console .log ('按下了回车键' ) alert ('你按下了回车键!' ) } function handleKeyup (event ) { pressedKey.value = event.key console .log ('按下的键:' , event.key ) } </script > <style scoped > input , button { margin : 5px ; padding : 5px ; } button { background : #42b983 ; color : white; border : none; border-radius : 4px ; cursor : pointer; } button :hover { background : #369870 ; } </style >
4.3.2 响应式基础
此处的响应式是指 : 数据模型(自定义的变量、对象)发生变化时,自动更新DOM树内容,页面上显示的内容会进行同步变化。vue3的数据模型不是自动响应式的,需要我们做一些特殊的处理。
1、如何实现响应式
使用ref或reactive函数就可以将基本类型的数据(如字符串,数字等)和引用类型的数据(如对象)转换为一个响应式对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <script setup > import { ref,reactive } from 'vue' let count = ref (56 )let totalCount = reactive (566 ) let obj = ref ({ name :"蔡徐坤" , age :33 }) let obj2 = reactive ({ name :"吴亦凡" , age :38 }) let changeCount = ( )=>{ count.value = 58 } let changeTotalCount = ( )=>{ totalCount = 588 } let changeObj = ( )=>{ obj.value = { name :"李易峰" , age :35 } } let changeObj2 = ( )=>{ obj2 = { name :"PGOne" , age :32 } } let changeObjAttr = ( )=>{ obj.value .name = "蔡大使" } let changeObj2Attr = ( )=>{ obj2.name = "吴签" } </script > <template > <h2 > 班级总人数:{{ count }}</h2 > <h2 > 全校总人数:{{ totalCount }}</h2 > <h2 > 使用ref函数包装的对象:{{ obj }}</h2 > <h2 > 使用reactive函数包装的对象:{{ obj2 }}</h2 > <button @click ="changeCount" > 改变ref包装的基本类型</button > <br > <button @click ="changeTotalCount" > 改变reactive包装的基本类型</button > <br > <button @click ="changeObj" > 改变ref包装的对象</button > <br > <button @click ="changeObj2" > 改变reactive包装的对象</button > <br > <button @click ="changeObjAttr" > 改变ref包装的对象的属性值</button > <br > <button @click ="changeObj2Attr" > 改变reactive包装的对象的属性值</button > <br > </template >
2、ref与reactive的区别
ref函数可以包装基本类型(字符串、数字)和引用类型(数组、对象);reactive只能包装引用类型
使用ref包装的数据在JS中修改或获取的时候需要加.value,在DOM(template标签内)中不用添加.value;用reactive包装的数据在JS和DOM中都不需要添加.value
使用ref包装的对象,对象和对象的属性都是响应式的;使用reactive包装的对象,对象不是响应式的,对象的属性是响应式的
4.3.2 条件和列表渲染
1、条件渲染
v-if 条件渲染:
v-if='表达式' 只会在指令的表达式返回真值时才被渲染
也可以使用 v-else 为 v-if 添加一个“else 区块”。
一个 v-else 元素必须跟在一个 v-if 元素后面,否则它将不会被识别。
1 2 3 4 5 6 7 8 9 <script setup > let age = 16 </script > <template > <div v-if ="age >= 18" > 已成年</div > <div v-else > 小屁孩儿</div > </template >
v-show条件渲染扩展:
另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样;
不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性;
v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用;
1 2 3 4 5 6 7 8 9 10 11 <script setup > let age = 16 </script > <template > <div v-if ="age >= 18" > 已成年</div > <div v-else > 小屁孩儿</div > <div v-show ="age >= 18" > 成年人</div > <div v-show ="age < 18" > 儿童</div > </template >
v-if vs v-show :
v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建;
v-if 也是惰性 的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染;
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换;
总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适;
2、列表渲染
我们可以使用 v-for 指令基于一个数组来渲染一个列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script setup > let userArray = []let userArray2 = [ {id :1 ,name :"迪丽热巴" ,age :30 }, {id :2 ,name :"古力娜扎" ,age :28 }, {id :4 ,name :"马蓉" ,age :40 }, {id :5 ,name :"李小璐" ,age :33 }, {id :6 ,name :"杨颖" ,age :34 } ] </script > <template > <div v-if ="userArray2.length==0" > <h1 > 没有任何用户</h1 > </div > <div v-else > <h1 > 用户列表</h1 > <table border ="1" cellpadding ="20" cellspacing ="0" > <tr > <th > 序号</th > <th > 编号</th > <th > 姓名</th > <th > 年龄</th > <th colspan ="2" > 操作</th > </tr > <tr v-for ="(user,index) in userArray2" > <td > {{ index }}</td > <td v-text ="user.id" > </td > <td > {{ user.name }}</td > <td > {{ user.age }}</td > <td > <a href ="#" > 编辑</a > </td > <td > <a href ="#" > 删除</a > </td > </tr > </table > </div > </template >
4.3.3 双向绑定
单项绑定和双向绑定:
单向绑定:响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变不会同步更新到响应式数据;
双向绑定:响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据;
用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行;
v-model专门用于双向绑定表单标签的value属性,语法为 v-model:value='',可以简写为 v-model='';
v-model还可以用于各种不同类型的输入,<textarea>、<select> 元素;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script setup > import { ref } from 'vue' let data = ref ("测试双向的数据绑定" )let changeData = ( )=>{ data.value = "新值" } </script > <template > <h1 > {{ data }}</h1 > 测试单向数据绑定:<input v-bind:value ="data" > </input > <br > 测试双向数据绑定:<input v-model ="data" > </input > <br > <button @click ="changeData" > 点击修改JS中的数据</button > </template >
4.3.4 Vue生命周期
1、生命周期简介
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码!
常见钩子函数:
onMounted() 注册一个回调函数,在组件挂载完成后执行;
onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用;
onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用;
onBeforeMount() 注册一个钩子,在组件被挂载之前被调用;
onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用;
onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用;
2、生命周期案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script setup > import {ref,onUpdated,onMounted,onBeforeUpdate} from 'vue' let message =ref ('hello' ) onMounted (()=> { console .log ('-----------onMounted---------' ) let span1 =document .getElementById ("span1" ) console .log (span1.innerText ) }) onBeforeUpdate (()=> { console .log ('-----------onBeforeUpdate---------' ) console .log (message.value ) let span1 =document .getElementById ("span1" ) console .log (span1.innerText ) }) onUpdated (()=> { console .log ('-----------onUpdated---------' ) let span1 =document .getElementById ("span1" ) console .log (span1.innerText ) }) </script > <template > <div > <span id ="span1" v-text ="message" > </span > <br > <input type ="text" v-model ="message" > </div > </template > <style scoped > </style >
4.3.5 Vue组件基础
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。组件就是实现应用中局部功能代码和资源的集合!在实际应用中,组件常常被组织成层层嵌套的树状结构:
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。
传统方式编写应用:
组件方式编写应用:
4.3.6 Vue组件之间传递数据(了解)
1、父传子
Vue3 中父组件向子组件传值可以通过 props 进行,具体操作如下:
需要在子组件定义要接收的数据和参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 defineProps(['foo' ]) // 使用 <script setup>defineProps({ title: String , likes: Number }) String Number Boolean Array Object Date Function Symbol Error 获取数据: 方案1 :defineProps(['foo' ]) | defineProps({title: String }) 直接使用声明属性名即可 {{foo | title}} 方案2 :let pops = defineProps(['foo' ])|let pops = defineProps({title: String }) 直接使用声明属性名即可 {{pops.foo | pops.title}} 方案3 :let {foo} = defineProps(['foo' ])|let {title} = defineProps({title: String }) 直接使用声明属性名即可 {{foo | title}}
父组件使用子组件时进行赋值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 静态传参: 声明接收 defineProps( { greetingMessage: String } ) 参数传递 <MyComponent greeting-message ="hello" /> 理论上你也可以在向子组件传递 props 时使用 camelCase 形式,但实际上为了和 HTML attribute 对齐,通常会将其写为 kebab-case 形式! 动态参数: 相应地,还有使用 v-bind 或缩写 : 来进行动态绑定的 props <BlogPost :title ="post.title" /> <BlogPost :title ="post.title + ' by ' + post.author.name" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup > import Son from './components/Son.vue' import {ref,reactive,toRefs} from 'vue' let message = ref ('parent data!' ) let title = ref (42 ) function changeMessage ( ){ message.value = '修改数据!' title.value ++ } </script > <template > <div > <h2 > {{ message }}</h2 > <hr > <Son :message ="message" :title ="title" > </Son > <hr > <button @click ="changeMessage" > 点击更新</button > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script setup type ="module" > import {ref,isRef,defineProps} from 'vue' defineProps ({ message :String , title :Number }) </script > <template > <div > <div > {{ message }}</div > <div > {{ title }}</div > </div > </template > <style > </style >
2、子传父
Vue3 中子组件向父组件传值可以通过 defineEmits 进行,具体操作如下:
在子组件中定义事件 :使用 defineEmits 定义事件。
1 2 3 import {ref,defineEmits} from 'vue' let emites = defineEmits(['add' ,'sub' ]);
触发事件并传递参数 :在子组件适当的事件处理程序中调用 emit 函数以发射事件。
1 2 3 emites ('add' ,'add data!' +data.value) emites ('sub' ,'sub data!' +data.value)
在父组件中监听事件 :使用 v-on 或简写 @ 语法在父组件中监听子组件的事件。
1 2 <Son @add ="padd" @sub ="psub" > </Son >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script setup > import Son from './components/Son.vue' import {ref} from 'vue' let pdata = ref ('' ) const padd = (data ) => { console .log ('2222' ); pdata.value =data; } const psub = (data ) => { console .log ('11111' ); pdata.value = data; } </script > <template > <div > <Son @add ="padd" @sub ="psub" > </Son > <hr > {{ pdata }} </div > </template > <style > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script setup > import {ref,defineEmits} from 'vue' let emites = defineEmits (['add' ,'sub' ]); let data = ref (1 ); function sendMsgToParent ( ){ emites ('add' ,'add data!' +data.value ) emites ('sub' ,'sub data!' +data.value ) data.value ++; } </script > <template > <div > <button @click ="sendMsgToParent" > 发送消息给父组件</button > </div > </template >
3、兄弟传参
Navigator.vue: 发送数据到App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup type ="module" > import {defineEmits} from 'vue' const emits = defineEmits (['sendMenu' ]); function send (data ){ emits ('sendMenu' ,data); } </script > <template > <div > <ul > <li @click ="send('学员管理')" > 学员管理</li > <li @click ="send('图书管理')" > 图书管理</li > <li @click ="send('请假管理')" > 请假管理</li > <li @click ="send('考试管理')" > 考试管理</li > <li @click ="send('讲师管理')" > 讲师管理</li > </ul > </div > </template > <style > </style >
App.vue: 发送数据到Content.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <script setup > import Header from './components/Header.vue' import Navigator from './components/Navigator.vue' import Content from './components/Content.vue' import {ref} from "vue" var navigator_menu = ref ('ceshi' ); const receiver = (data ) =>{ navigator_menu.value = data; } </script > <template > <div > <hr > {{ navigator_menu }} <hr > <Header class ="header" > </Header > <Navigator @sendMenu ="receiver" class ="navigator" > </Navigator > <Content class ="content" :message ="navigator_menu" > </Content > </div > </template > <style scoped > .header { height : 80px ; border : 1px solid red; } .navigator { width : 15% ; height : 800px ; display : inline-block; border : 1px blue solid; float : left; } .content { width : 83% ; height : 800px ; display : inline-block; border : 1px goldenrod solid; float : right; } </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script setup type ="module" > defineProps ({ message :String }) </script > <template > <div > 展示的主要内容! <hr > {{ message }} </div > </template > <style > </style >
第5章 Vue3官方库
5.1 路由机制Router
5.1.1 路由简介
1 什么是路由?
定义:路由就是根据不同的 URL 地址展示不同的内容或页面;
通俗理解:路由就像是一个地图,我们要去不同的地方,需要通过不同的路线进行导航;
2 路由的作用:
单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验;
路由还可以实现页面的认证和权限控制,保护用户的隐私和安全;
路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面;
5.1.2 路由入门案例
1 案例需求分析:
2 创建项目和导入路由依赖:
1 2 3 npm create vite //创建项目cd 项目文件夹 //进入项目文件夹 npm install //安装项目需求依赖 npm install vue-router@4 --save //安装全局的vue-router 4版本, --save表示添加依赖到package.json,默认可省略。--save-dev表示增加开发依赖配置。
3 准备页面和组件 :
1 2 3 4 5 6 7 8 9 <script setup > </script > <template > <div > <h1 > Home页面</h1 > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 <script setup > </script > <template > <div > <h1 > List页面</h1 > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 <script setup > </script > <template > <div > <h1 > Add页面</h1 > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 <script setup > </script > <template > <div > <h1 > Update页面</h1 > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup > </script > <template > <div > <h1 > App页面</h1 > <hr /> <router-link to ="/" > home页</router-link > <br > <router-link to ="/list" > list页</router-link > <br > <router-link to ="/add" > add页</router-link > <br > <router-link to ="/update" > update页</router-link > <br > <hr /> <hr > 默认展示位置:<router-view > </router-view > <hr > Home视图展示:<router-view name ="homeView" > </router-view > <hr > List视图展示:<router-view name ="listView" > </router-view > <hr > Add视图展示:<router-view name ="addView" > </router-view > <hr > Update视图展示:<router-view name ="updateView" > </router-view > </div > </template > <style scoped > </style >
4 准备路由配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import {createRouter,createWebHashHistory} from 'vue-router' import Home from '../components/Home.vue' import List from '../components/List.vue' import Add from '../components/Add.vue' import Update from '../components/Update.vue' const router = createRouter ({ history : createWebHashHistory (), routes :[ { path :'/' , components :{ default :Home , homeView :Home } }, { path :'/list' , components :{ listView : List } }, { path :'/add' , components :{ addView :Add } }, { path :'/update' , components :{ updateView :Update } }, ] }) export default router;
5 main.js引入Router配置:
1 2 3 4 5 6 7 8 9 10 import { createApp } from 'vue' import './style.css' import App from './App.vue' import router from './routers/router.js' let app = createApp (App )app.use (router) app.mount ("#app" )
6 启动测试:
内容解释: https://router.vuejs.org/zh/guide/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 1 . RouterLink 用来渲染一个链接的组件,该链接在被点击时会触发导航。 < router- link to= "/list" > list页</ router- link> < br> to属性指的是触发路径跳转到对应组件 2 . RouterView RouterView 组件可以使 Vue Router 知道你想要在哪里渲染当前 URL 路径对应的路由组件。 它不一定要在 App .vue 中,你可以把它放在任何地方! 可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。 如果 router- view 没有设置名字,那么默认为default < router- view class = "view left-sidebar" name= "LeftSidebar" /> 3 . createRouter()函数 路由器实例是通过调用 createRouter() 函数创建的 const router = createRouter({ history: createMemoryHistory(), routes:[ path: "/对应地址" ,component: 默认routerView显示组件, path: "/对应路径" ,components:{命名视图名:显示组件,命名视图名:显示组件} ] }) 路径:动态路径设置,路径传惨 /users/ :username /users/ eduardo /users/ :username/posts/ :postId /users/ eduardo/posts/ 123 {{ $route .params.username }} 获取参数 4 . 注册路由器插件 createApp(App ) .use(router) .mount('#app') 注册路由作用: 全局注册 RouterView 和 RouterLink 组件。 添加全局 $router 和 $route 属性。 启用 useRouter() 和 useRoute() 组合式函数。 触发路由器解析初始路由。
5.1.3 路由重定向
重定向的作用:将一个路由重定向到另一个路由上。
修改案例:访问/list和/showAll都定向到List.vue.
router.js
1 2 3 4 5 { path :'/showAll' , redirect :'/list' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup > </script > <template > <div > <h1 > App页面</h1 > <hr /> <router-link to ="/" > home页</router-link > <br > <router-link to ="/list" > list页</router-link > <br > <router-link to ="/showAll" > showAll页</router-link > <br > <router-link to ="/add" > add页</router-link > <br > <router-link to ="/update" > update页</router-link > <br > <hr /> <hr > 默认展示位置:<router-view > </router-view > <hr > Home视图展示:<router-view name ="homeView" > </router-view > <hr > List视图展示:<router-view name ="listView" > </router-view > <hr > Add视图展示:<router-view name ="addView" > </router-view > <hr > Update视图展示:<router-view name ="updateView" > </router-view > </div > </template > <style scoped > </style >
5.1.4 编程式路由(useRouter)
声明式路由:
<router-link to="/list">list页</router-link> 这种路由,点击后只能切换/list对应组件,是固定的。
编程式路由:
通过useRouter,动态决定向那个组件切换的路由;
在 Vue 3 和 Vue Router 4 中,你可以使用 useRouter 来实现动态路由(编程式路由);
这里的 useRouter 方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作;
声明式
编程式
<router-link :to="...">
router.push(...)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 router.push ('/users/eduardo' ) router.push ({ path : '/users/eduardo' }) router.push ({ path : '/register' , query : { plan : 'private' } router.push (`/user/${username} ` ) router.push ({ path : `/user/${username} ` }) router.push ({ name : 'user' , params : { username } }) router.push ({ path : '/user' , params : { username } })
案例需求:通过普通按钮配合事件绑定实现路由页面跳转,不直接使用router-link标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script setup type ="module" > import {useRouter} from 'vue-router' import {ref} from 'vue' let router = useRouter () let showList = ( )=>{ router.push ({path :'/list' }) } </script > <template > <div > <h1 > App页面</h1 > <hr /> <router-link to ="/" > home页</router-link > <br > <router-link to ="/list" > list页</router-link > <br > <button @click ="showList()" > showList</button > <br > <hr /> <hr > 默认展示位置:<router-view > </router-view > <hr > Home视图展示:<router-view name ="homeView" > </router-view > <hr > List视图展示:<router-view name ="listView" > </router-view > </div > </template > <style scoped > </style >
5.1.5 路由传参(useRoute)
路径参数:
在路径中使用一个动态字段来实现,我们称之为 路径参数 :
例如: 查看数据详情 /showDetail/1 ,1就是要查看详情的id,可以动态添值。
键值对参数:
读取参数:
在 Vue 3 和 Vue Router 4 中,你可以使用 useRoute 这个函数从 Vue 的组合式 API 中获取路由对象;
useRoute 方法返回的是当前的 route 对象,你可以用它来获取关于当前路由的信息,如当前的路径、键值对参数等;
案例需求 : 切换到ShowDetail.vue组件时,向该组件通过路由传递参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <script setup type ="module" > import {useRouter} from 'vue-router' let router = useRouter () let showDetail = (id,language )=>{ router.push ({name :"showDetail" ,params :{id :id,language :language}}) } let showDetail2 = (id,language )=>{ router.push ({path :"/showDetail2" ,query :{id :id,language :language}}) } </script > <template > <div > <h1 > App页面</h1 > <hr /> <router-link to ="/showDetail/1/JAVA" > 路径传参JAVA</router-link > <button @click ="showDetail(1,'JAVA')" > 编程式路由路径传参显示JAVA</button > <hr /> <router-link v-bind:to ="{path:'/showDetail2',query:{id:1,language:'Java'}}" > 键值对传参JAVA</router-link > <button @click ="showDetail2(1,'JAVA')" > 编程式路由键值对传参JAVA</button > <hr > showDetail视图展示:<router-view name ="showDetailView" > </router-view > <hr > showDetail2视图展示:<router-view name ="showDetailView2" > </router-view > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import {createRouter,createWebHashHistory} from 'vue-router' import ShowDetail from '../components/ShowDetail.vue' import ShowDetail2 from '../components/ShowDetail2.vue' const router = createRouter ({ history : createWebHashHistory (), routes :[ { path :'/showDetail/:id/:language' , name :'showDetail' , components :{ showDetailView :ShowDetail } }, { path :'/showDetail2' , components :{ showDetailView2 :ShowDetail2 } }, ] }) export default router;
ShowDetail.vue 通过useRoute获取路径参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script setup type ="module" > import {useRoute} from 'vue-router' import { onMounted,ref } from 'vue' ; let route =useRoute () let languageId = ref (0 ) let languageName = ref ('' ) onMounted (()=> { languageId.value =route.params .id languageName.value =route.params .language console .log (languageId.value ) console .log (languageName.value ) }) </script > <template > <div > <h1 > ShowDetail页面</h1 > <h3 > 编号{{route.params.id}}:{{route.params.language}}是世界上最好的语言</h3 > <h3 > 编号{{languageId}}:{{languageName}}是世界上最好的语言</h3 > </div > </template > <style scoped > </style >
ShowDetail2.vue通过useRoute获取键值对参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script setup type ="module" > import {useRoute} from 'vue-router' import { onMounted,ref } from 'vue' ; let route =useRoute () let languageId = ref (0 ) let languageName = ref ('' ) onMounted (()=> { console .log (route.query ) console .log (languageId.value ) console .log (languageName.value ) languageId.value =route.query .id languageName.value =route.query .language }) </script > <template > <div > <h1 > ShowDetail2页面</h1 > <h3 > 编号{{route.query.id}}:{{route.query.language}}是世界上最好的语言</h3 > <h3 > 编号{{languageId}}:{{languageName}}是世界上最好的语言</h3 > </div > </template > <style scoped > </style >
5.1.6 路由守卫
在 Vue 3 中,路由守卫是用于在路由切换期间进行一些特定任务的回调函数。路由守卫可以用于许多任务,例如验证用户是否已登录、在路由切换前提供确认提示、请求数据等。Vue 3 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫:
全局前置守卫 :在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等;
全局后置守卫 :在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等;
守卫代码的位置 : 在router.js中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 router.beforeEach ( (to,from ,next ) => { console .log (to.path ,from .path ,next) if (to.path == '/index' ){ next () }else { next ('/index' ) } } ) router.afterEach ((to, from ) => { console .log (`Navigate from ${from .path} to ${to.path} ` ); });
登录案例,登录以后才可以进入home,否则必须进入login。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script setup > import {ref} from 'vue' import {useRouter} from 'vue-router' let username =ref ('' ) let password =ref ('' ) let router = useRouter (); let login = ( ) =>{ console .log (username.value ,password.value ) if (username.value == 'root' & password.value == '123456' ){ router.push ({path :'/home' ,query :{'username' :username.value }}) localStorage .setItem ('username' ,username.value ) }else { alert ('登录失败,账号或者密码错误!' ); } } </script > <template > <div > 账号: <input type ="text" v-model ="username" placeholder ="请输入账号!" > <br > 密码: <input type ="password" v-model ="password" placeholder ="请输入密码!" > <br > <button @click ="login()" > 登录</button > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script setup > import {ref} from 'vue' import {useRoute,useRouter} from 'vue-router' let route =useRoute () let router = useRouter () let username =window .localStorage .getItem ('username' ); let logout = ( )=>{ window .localStorage .removeItem ('username' ) router.push ("/login" ) } </script > <template > <div > <h1 > Home页面</h1 > <h3 > 欢迎{{username}}登录</h3 > <button @click ="logout" > 退出登录</button > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 <script setup type ="module" > </script > <template > <div > <router-view > </router-view > </div > </template > <style scoped > </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import {createRouter,createWebHashHistory} from 'vue-router' import Home from '../components/Home.vue' import Login from '../components/login.vue' const router = createRouter ({ history : createWebHashHistory (), routes :[ { path :'/home' , component :Home }, { path :'/' , redirect :"/home" }, { path :'/login' , component :Login }, ] }) router.beforeEach ((to,from ,next )=> { console .log (`从哪里来:${from .path} ,到哪里去:${to.path} ` ) if (to.path == '/login' ){ next () }else { let username =window .localStorage .getItem ('username' ); if (null != username){ next () }else { next ('/login' ) } } }) router.afterEach ((to,from )=> { console .log (`从哪里来:${from .path} ,到哪里去:${to.path} ` ) }) export default router;
5.2 状态管理Pinia
5.2.1 Pinia介绍
如何实现多个组件之间的数据传递?
方式1 组件传参 ;
方式2 路由传参 ;
方式3 通过pinia状态管理定义共享数据;
当我们有多个组件共享一个共同的状态(数据源)时,多个视图可能都依赖于同一份状态。来自不同视图的交互也可能需要更改同一份状态。虽然我们的手动状态管理解决方案(props,组件间通信,模块化)在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑:
更强的团队协作约定;
与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试;
模块热更新 (HMR);
服务端渲染支持;
Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。
官网:https://pinia.vuejs.org/zh
5.2.2 Pinia基本用法
1 准备Vite项目:
1 2 3 npm create vite npm install npm install vue-router@4 --save
2 安装Pinia:
3 定义pinia store对象 src/store/store.js (推荐这么命名不是强制):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import {defineStore } from 'pinia' export const definedPerson = defineStore ('personPinia' , { state :()=> { return { username :'张三' , age :0 , hobbies :['唱歌' ,'跳舞' ] } }, getters :{ getHobbiesCount ( ){ return this .hobbies .length }, getAge ( ){ return this .age } }, actions :{ doubleAge ( ){ this .age =this .age *2 } } } )
4 在main.js配置Pinia组件到Vue中 :
1 2 3 4 5 6 7 8 9 10 11 12 import { createApp } from 'vue' import App from './App.vue' import router from './routers/router.js' import { createPinia } from 'pinia' let pinia= createPinia ()let app =createApp (App )app.use (router) app.use (pinia) app.mount ('#app' )
5 Operate.vue 中操作Pinia数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <script setup type ="module" > import { definedPerson } from '../store/store' ; let person= definedPerson () </script > <template > <div > <h1 > operate视图,用户操作Pinia中的数据</h1 > 请输入姓名:<input type ="text" v-model ="person.username" > <br > 请输入年龄:<input type ="text" v-model ="person.age" > <br > 请增加爱好: <input type ="checkbox" value ="吃饭" v-model ="person.hobbies" > 吃饭 <input type ="checkbox" value ="睡觉" v-model ="person.hobbies" > 睡觉 <input type ="checkbox" value ="打豆豆" v-model ="person.hobbies" > 打豆豆 <br > <button @click ="person.doubleAge()" > 年龄加倍</button > <br > <button @click ="person.$reset()" > 恢复默认值</button > <br > <button @click ="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})" > 变身奥特曼</button > <br > 显示pinia中的person数据:{{person}} </div > </template > <style scoped > </style >
6 List.vue中展示Pinia数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script setup type ="module" > import { definedPerson } from '../store/store' ; let person= definedPerson () </script > <template > <div > <h1 > List页面,展示Pinia中的数据</h1 > 读取姓名:{{person.username}} <br > 读取年龄:{{person.age}} <br > 通过get年龄:{{person.getAge}} <br > 爱好数量:{{person.getHobbiesCount}} <br > 所有的爱好: <ul > <li v-for ='(hobby,index) in person.hobbies' :key ="index" v-text ="hobby" > </li > </ul > </div > </template > <style scoped > </style >
7 定义组件路由router.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import {createRouter,createWebHashHistory} from 'vue-router' import List from '../components/List.vue' import Operate from '../components/Operate.vue' const router = createRouter ({ history : createWebHashHistory (), routes :[ { path :'/operate' , component :Operate }, { path :'/list' , component :List }, ] }) export default router;
8 App.vue中通过路由切换组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup type ="module" > </script > <template > <div > <hr > <router-link to ="/operate" > 显示操作页</router-link > <br > <router-link to ="/list" > 显示展示页</router-link > <br > <hr > <router-view > </router-view > </div > </template > <style scoped > </style >
9 启动测试:
第6章 Axios
6.1 预讲知识-Promise
6.1.1 普通函数和回调函数
普通函数:正常调用的函数,一般函数执行完毕后才会继续执行下一行代码。
1 2 3 4 5 6 7 8 9 <script > let fun1 = ( ) =>{ console .log ("fun1 invoked" ) } fun1 () console .log ("other code processon" ) </script >
回调函数: 一些特殊的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了。
1 2 3 4 5 6 7 <script > setTimeout (function ( ){ console .log ("setTimeout invoked" ) },2000 ) console .log ("other code processon" ) </script >
6.1.2 Promise 简介
前端中的异步编程技术,类似Java中的多线程+线程结果回调!
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象;
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理;
Promise对象有以下两个特点:
Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变;
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果;
6.1.3 Promise 基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script > let promise =new Promise (function (resolve,reject ){ console .log ("promise do some code ... ..." ) 100s reject ("promise fail" ) }) console .log ('other code1111 invoked' ) promise.then ( function (value ){console .log (`promise中执行了resolve:${value} ` )}, function (error ){console .log (`promise中执行了reject:${error} ` )} ) console .log ('other code2222 invoked' ) </script >
6.1.4 Promise catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script > let promise =new Promise (function (resolve,reject ){ console .log ("promise do some code ... ..." ) throw new Error ("error message" ) }) console .log ('other code1111 invoked' ) promise.then ( function (resolveValue ){console .log (`promise中执行了resolve:${resolveValue} ` )} ).catch ( function (error ){console .log (error)} ) console .log ('other code2222 invoked' ) </script >
综合代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <script setup > console .log ("+++++++++++++++++111" ) let promis = new Promise ((resolve,reject )=> { setTimeout (() => { const userData = { id : 1 , name : 'Alice' , age : 30 , }; resolve (userData); }, 2000 ); }).then (data => { console .log ("resolve:" +data.name ) },failData => { console .log ("reject:" +failData.name ) }); console .log ("+++++++++++++++++222" ) let promis1 = new Promise ((resolve,reject )=> { setTimeout (() => { const userData1 = { id : 1 , name : 'heheh' , age : 30 , }; reject (userData1); }, 2000 ); }).then (data1 => { console .log ("resolve:" +data1.name ) }).catch (failData1 => { console .log ("reject:" +failData1) }); console .log ("+++++++++++++++++333" ) </script>
6.1.5 async和await的使用
async和await是ES6中用于处理异步操作的新特性。通常,异步操作会涉及到Promise对象,而async/await则是在Promise基础上提供了更加直观和易于使用的语法。
async 用于标识函数的:
async标识函数后,async函数的返回值会变成一个Promise对象;
如果函数内部返回的数据是一个非Promise对象,async函数的结果会返回一个成功状态 Promise对象;
如果函数内部返回的是一个Promise对象,则async函数返回的状态与结果由该对象决定;
如果函数内部抛出的是一个异常,则async函数返回的是一个失败的Promise对象;
async其实就是给我们提供了一个快捷声明回调函数的语法,有了它无需编写 new Promise(… …) 这样的代码了;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script > async function fun1 ( ){ let promise = Promise .reject ("something wrong" ) return promise } let promise =fun1 () promise.then ( function (value ){ console .log ("success:" +value) } ).catch ( function (value ){ console .log ("fail:" +value) } ) </script >
await:
await右侧的表达式一般为一个Promise对象,但是也可以是一个其他值;
如果表达式是Promise对象,await返回的是Promise成功的值;
如果表达式是其他值,则直接返回该值;
await会等右边的Promise对象执行结束,然后再获取结果,所在方法的后续代码也会等待await的执行;
await必须在async函数中,但是async函数中可以没有await;
如果await右边的Promise失败了,就会抛出异常,可以通过 try … catch捕获处理;
await其实就是给我们提供了一个快捷获得Promise对象成功状态的语法,无需编写promise.then(… …)这样的代码了;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script > async function fun1 ( ){ return 10 } async function fun2 ( ){ let res = 0 try { res = await fun1 () }catch (e){ console .log ("catch got:" +e) } console .log ("await got:" +res) } fun2 () </script >
综合案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 async function fetchUserData (userId ) { const response = new Promise ((resolve ) => { setTimeout (() => { resolve ({ id : userId, name : "John Doe" , age : 30 }); }, 2000 ); }); return response; } async function displayUser ( ) { console .log ("Fetching user data..." ); const user = await fetchUserData (1 ); console .log ("User Data:" , user); } displayUser ();
fetchUserData 函数 :
使用 async 修饰符定义一个异步函数 fetchUserData。
使用 await 关键字等待一个 Promise 的结果。在这个例子中,我们模拟了一个异步操作(网络请求),使用 setTimeout 来延迟响应。
一旦 Promise 被解决,我们返回用户数据。
displayUser 函数 :
这是主执行函数,同样使用 async 关键字。
在函数内部,我们调用 fetchUserData,并使用 await 等待它的结果。
一旦获取到用户数据,我们将其打印到控制台。
调用 displayUser :
最后,我们调用 displayUser 函数,开始整个过程。
6.2 Axios介绍
AJAX :
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML);
AJAX 不是新的编程语言,而是一种使用现有标准的新方法;
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容;
AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行;
XMLHttpRequest 只是实现 Ajax 的一种方式,本次我们使用Vue Axios方式实现;
什么是axios 官网介绍:https://axios-http.com/zh/docs/intro
Axios 是一个基于 Promise网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块,而在客户端 (浏览端) 则使用 XMLHttpRequests。它有如下特性:
6.3 Axios 入门案例
1 案例需求:请求后台获取随机土味情话。
1 https://api.uomg.com/api/rand.qinghua?format=json
1 { "code" : 1 , "content" : "我努力不是为了你而是因为你。" }
2 准备项目:
1 2 npm create vite npm install
3 安装Axios:
4 设计页面(App.Vue):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <script setup type ="module" > import axios from 'axios' import { onMounted,reactive } from 'vue' ; let jsonData =reactive ({code :1 ,content :'我努力不是为了你而是因为你' }) let getLoveMessage =( )=>{ axios ({ method :"post" , url :"https://api.uomg.com/api/rand.qinghua?format=json" , data :{ username :"123456" } }).then ( function (response ){ console .log (response) Object .assign (jsonData,response.data ) }).catch (function (error ){ console .log (error) }) } onMounted (()=> { getLoveMessage () }) </script > <template > <div > <h1 > 今日土味情话:{{jsonData.content}}</h1 > <button @click ="getLoveMessage" > 获取今日土味情话</button > </div > </template > <style scoped > </style >
5 启动测试:
异步响应的数据结构:
响应的数据是经过包装返回的!一个请求的响应包含以下信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { data: { } , status: 200 , statusText: 'OK', headers: { } , config: { } , request: { } }
1 2 3 4 5 6 7 then (function (response ) { console .log (response.data ); console .log (response.status ); console .log (response.statusText ); console .log (response.headers ); console .log (response.config ); });
6 通过async和await处理异步请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <script setup type ="module" > import axios from 'axios' import { onMounted,reactive } from 'vue' ; let jsonData =reactive ({code :1 ,content :'我努力不是为了你而是因为你' }) let getLoveWords = async ( )=>{ return await axios ({ method :"post" , url :"https://api.uomg.com/api/rand.qinghua?format=json" , data :{ username :"123456" } }) } let getLoveMessage = async ( )=>{ let {data} = await getLoveWords () Object .assign (jsonData,data) } onMounted (()=> { getLoveMessage () }) </script > <template > <div > <h1 > 今日土味情话:{{jsonData.content}}</h1 > <button @click ="getLoveMessage" > 获取今日土味情话</button > </div > </template > <style scoped > </style >
axios在发送异步请求时的可选配置:
详情见 https://axios-http.com/zh/docs/req_config
6.4 Axios get和post方法
配置添加语法:
1 2 3 4 5 6 7 8 9 10 11 axios.get (url[, config]) axios.get (url,{ 上面指定配置key :配置值, 上面指定配置key :配置值 }) axios.post (url[, data[, config]]) axios.post (url,{key :value 上面指定配置key :配置值, 上面指定配置key :配置值 })
测试axios.get(… … ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <script setup > import axios from 'axios' import { onMounted,ref,reactive,toRaw } from 'vue' ; let jsonData =reactive ({code :1 ,content :'我努力不是为了你而是因为你' }) let getLoveWords = ( )=>{ try { return axios.get ( 'https://api.uomg.com/api/rand.qinghua' , { params :{ format :'json' , username :'zhangsan' , password :'123456' }, headers :{ 'Accept' : 'application/json, text/plain, text/html,*/*' } } ) }catch (e){ return e } } let getLoveMessage = async ( )=>{ let {data} = await getLoveWords () Object .assign (jsonData,data) } onMounted (()=> { getLoveMessage () }) </script > <template > <div > <h1 > 今日土味情话:{{jsonData.content}}</h1 > <button @click ="getLoveMessage" > 获取今日土味情话</button > </div > </template > <style scoped > </style >
测试 axios.post(… …):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script setup type ="module" > import axios from 'axios' import { onMounted,ref,reactive,toRaw } from 'vue' ; let jsonData =reactive ({code :1 ,content :'我努力不是为了你而是因为你' }) let getLoveWords = async ( )=>{ try { return axios.post ( 'https://api.uomg.com/api/rand.qinghua' , { username :'zhangsan' , password :'123456' }, { params :{ format :'json' , }, headers :{ 'Accept' : 'application/json, text/plain, text/html,*/*' , 'X-Requested-With' : 'XMLHttpRequest' } } ) }catch (e){ return e } } let getLoveMessage =async ( )=>{ let {data} = await getLoveWords () Object .assign (jsonData,data) } onMounted (()=> { getLoveMessage () }) </script > <template > <div > <h1 > 今日土味情话:{{jsonData.content}}</h1 > <button @click ="getLoveMessage" > 获取今日土味情话</button > </div > </template > <style scoped > </style >
前面的测试可能出现跨域问题。不要测试过多次数,可以过会再测试试。或在vite.config.js中配置代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig ({ plugins : [vue ()], server : { proxy : { '/' : { target : 'https://api.uomg.com/api/rand.qinghua?format=json' , changeOrigin : true }, }, }, })
6.5 Axios 拦截器
如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 axios.interceptors .request .use ( function (config ) { return config; }, function (error ) { return Promise .reject (error); } ); axios.interceptors .response .use ( function (response ) { return response; }, function (error ) { return Promise .reject (error); } );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import axios from 'axios' const instance = axios.create ({ baseURL :'https://api.uomg.com' , timeout :10000 }) instance.interceptors .request .use ( config => { console .log ("before request" ) config.headers .Accept = 'application/json, text/plain, text/html,*/*' return config }, error => { console .log ("request error" ) return Promise .reject (error) } ) instance.interceptors .response .use ( response => { console .log ("after success response" ) console .log (response) return response }, error => { console .log ("after fail response" ) console .log (error) return Promise .reject (error) } ) export default instance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script setup type ="module" > import axios from './axios.js' import { onMounted,ref,reactive,toRaw } from 'vue' ; let jsonData =reactive ({code :1 ,content :'我努力不是为了你而是因为你' }) let getLoveWords = async ( )=>{ try { return axios.post ( 'api/rand.qinghua' , { username :'zhangsan' , password :'123456' }, { params :{ format :'json' , } } ) }catch (e){ return e } } let getLoveMessage =async ( )=>{ let {data} = await getLoveWords () Object .assign (jsonData,data) } onMounted (()=> { getLoveMessage () }) </script > <template > <div > <h1 > 今日土味情话:{{jsonData.content}}</h1 > <button @click ="getLoveMessage" > 获取今日土味情话</button > </div > </template > <style scoped > </style >
第7章 Element-plus组件库
7.1 Element-plus介绍
Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。
Element Plus 支持按需加载,且不依赖于任何第三方 CSS 库,它可以轻松地集成到任何 Vue.js 项目中。Element Plus 的文档十分清晰,提供了各种组件的使用方法和示例代码,方便开发者快速上手。
Element Plus 目前已经推出了大量的常用 UI 组件,如按钮、表单、表格、对话框、选项卡等,此外还提供了一些高级组件,如日期选择器、时间选择器、级联选择器、滑块、颜色选择器等。这些组件具有一致的设计和可靠的代码质量,可以为开发者提供稳定的使用体验。
与 Element UI 相比,Element Plus 采用了现代化的技术架构和更加先进的设计理念,同时具备更好的性能和更好的兼容性。Element Plus 的更新迭代也更加频繁,可以为开发者提供更好的使用体验和更多的功能特性。
Element Plus 可以在支持 ES2018 和 ResizeObserver 的浏览器上运行。 如果您确实需要支持旧版本的浏览器,请自行添加 Babel 和相应的 Polyfill
官网一个 Vue 3 UI 框架 | Element Plus (element-plus.org)
由于 Vue 3 不再支持 IE11,Element Plus 也不再支持 IE 浏览器。
7.2 Element-plus环境搭建
1 准备vite项目
1 2 3 npm create vite 进入项目 npm install
2 安装element-plus
1 npm install element-plus
3 完整引入element-plus
1 2 3 4 5 6 7 8 9 10 11 import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp (App )app.use (ElementPlus ) app.mount ('#app' )
7.3 Element-plus常用组件
结合官网演示以下组件
https://element-plus.org/zh-CN
https://element-plus-docs.bklab.cn/zh-CN/
Button组件和Card组件
Table组件和Pagination组件
Form组件和表单数据校验
Message、Message Box及Popconfirm弹框组件
TypeScript
TypeScript 教程 | 菜鸟教程
1 2 const hello : string = "Yangjiayu!" console .log (hello)
项目开发