前端工程化 Vue

前端工程化是使用软件工程的方法单独解决前端的开发流程中模块化、组件化、规范化、自动化的问题,其主要目的为了提高效率和降低成本

前端工程化实现的技术栈有很多,我们采用ES6+nodejs+npm+Vite+VUE3+router+pinia+axios+Element-plus组合来实现

  • ECMAScript6 VUE3中大量使用ES6语法
  • Nodejs 前端项目运行环境
  • npm 依赖下载工具
  • vite 前端项目构建工具
  • VUE3 优秀的渐进式前端框架
  • router 通过路由实现页面切换
  • pinia 通过状态管理实现组件数据传递
  • axios ajax异步请求封装技术实现前后端数据交互
  • Element-plus 可以提供丰富的快速构建网页的组件仓库
  • TypeScript 静态类型检查和其他面向对象编程特性

ECMA6Script

ECMAScript 6,简称ES6,是JavaScript语言的一次重大更新。它于2015年发布,是原来的ECMAScript标准的第六个版本。ES6带来了大量的新特性,包括箭头函数、模板字符串、let和const关键字、解构、默认参数值、模块系统等等,大大提升了JavaScript的开发体验。由于VUE3中大量使用了ES6的语法,所以ES6成为了学习VUE3的门槛之一

ES6 新增了letconst,用来声明变量,使用的细节上也存在诸多差异

  • let 和var的差别

    1、let 不能重复声明

    2、let有块级作用域,非函数的花括号遇见let会有块级作用域,也就是只能在花括号里面访问。

    3、let不会预解析进行变量提升

    4、let 定义的全局变量不会作为window的属性

    5、let在es6中推荐优先使用

    模板字符串(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>
// 1 多行普通字符串
let ulStr =
'<ul>'+
'<li>JAVA</li>'+
'<li>html</li>'+
'<li>VUE</li>'+
'</ul>'
console.log(ulStr)
// 2 多行模板字符串
let ulStr2 = `
<ul>
<li>JAVA</li>
<li>html</li>
<li>VUE</li>
</ul>`
console.log(ulStr2)
// 3 普通字符串拼接
let name ='张小明'
let infoStr =name+'被评为本年级优秀学员'
console.log(infoStr)
// 4 模板字符串拼接
let infoStr2 =`${name}被评为本年级优秀学员`
console.log(infoStr2)
</script>

解构表达式

image-20241016182554013

箭头函数 类似lambda表达式

1
2
3
4
5
6
7
8
9
10
// 方案2
xdd.onclick = function(){
console.log(this)
//开启定时器
setTimeout(()=>{
console.log(this)// 使用setTimeout() 方法所在环境时的this对象
//变粉色
this.style.backgroundColor = 'pink';
},2000);
}

rest和spread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1...作为参数列表,称之为rest参数 
let fun3 = function (...args){console.log(args)}
let fun4 = (...args) =>{console.log(args)}
fun3(1,2,3)
fun4(1,2,3,4)
//2 spread
//应用场景1 合并数组
let arr2=[4,5,6]
let arr3=[...arr,...arr2]
console.log(arr3)
//应用场景2 合并对象属性
let p1={name:"张三"}
let p2={age:10}
let p3={gender:"boy"}
let person ={...p1,...p2,...p3}
console.log(person)

创建对象

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
class Person{
// 属性
#n;
age;
get name(){
return this.n;
}
set name(n){
this.n =n;
}
// 实例方法
eat(food){
console.log(this.age+"岁的"+this.n+"用筷子吃"+food)
}
// 静态方法
static sum(a,b){
return a+b;
}
// 构造器
constructor(name,age){
this.n=name;
this.age = age;

}
}
let person =new Person("张三",10);
// 访问对象属性
// 调用对象方法
console.log(person.name)
console.log(person.n)
person.name="小明"
console.log(person.age)
person.eat("火锅")
console.log(Person.sum(1,2))

class Student extends Person{
grade ;
score ;
study(){

}
constructor(name,age ) {
super(name,age);
}
}

let stu =new Student("学生小李",18);
stu.eat("面条")

浅拷贝和深拷贝

1
2
3
4
5
6
7
8
9
10
11
// 浅拷贝,person2和person指向相同的内存
let person2 = person;
person2.name="小黑"
console.log(person.name) //"小黑"
// 深拷贝,通过JSON和字符串的转换形成一个新的对象
let person2 = JSON.parse(JSON.stringify(person))
//或用spread 解构表达式
let person3 = {...person}
person2.name="小黑"
console.log(person.name) //不是"小黑"
console.log(person2.name) //不是"小黑"

模块化处理

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
//1.分别暴露

// 模块想对外导出,添加export关键字即可!
// 导出一个变量
export const PI = 3.14
// 导出一个函数
export function sum(a, b) {
return a + b;
}
// 导出一个类
export class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
}
}
3默认和混合暴露
/*
默认暴露语法 export default sum
默认暴露相当于是在暴露的对象中增加了一个名字为default的属性
三种暴露方式可以在一个module中混合使用

*/
export const PI = 3.14
// 导出一个函数
function sum(a, b) {
return a + b;
}
// 导出一个类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
}
}

// 导出默认
export default sum
// 统一导出
export {
Person
}
1
2
3
import * as m1 from './module.js'
import {Person,sum,PI} from './module.js'

TypeScript 快速上手

它为 JavaScript 增加了静态类型检查和其他面向对象编程特性。TypeScript 的主要优势在于:提高代码可读性和可维护性、在编译时发现错误、支持大型项目开发。它最终会被编译成 JavaScript 代码,可以在任何支持 JavaScript 的环境中运行。

类型推断 ——强类型语言

1
2
3
4
5
6
7
8
9
10
let str = 'abc'
str = 10 //报错

let str: string //推荐
str = 'abc'

let arr: number[]= [1, 2, 3]
arr[1] = '1' //报错
let arrl: Array<string> = ['a', 'b', 'c']
let t1: [number,srting,number?] = [1,'2'] //?可空类型

类型断言

1
2
3
let numArrive = [1,2,3]
const result = numArr.find(item =>item>2) as number //断言result是number类型
result*2 //不加as number 的话会报错

联合类型

1
2
3
4
let str: string | null = null
let num: 1|2|3 = 2
type Mytype = string | number //申明类型
let a:Mytype = 'abc'

枚举类型

1
2
3
4
5
enum MyEnum{
A,B,C
}
console.log(MyEnum.A)
console.log(MyEnum[0]) //相当于上一行号

函数

1
2
3
4
function MyFn (a = 10, b: string, c?: boolean, ...rest: number[]): number {
return 100
}
const f = MyFn(20, 'abc', true, 1, 2, 3)

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Obj1{
name: string,
age: number
}

interface Obj2 extends Obj1{ //继承
sex: string
}

const obj:Obj2 = { //实现接口
name: 'a',
age: 8
sex: 'man'
}

泛型

1
2
3
4
5
function myFn<T>(a: T,b: T): T[] {
return [a, b]
}
myFn<number>(1, 2)
myFn('a',1) //1报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Article {
public title: string
content: string
aaa?: string
bbb = 100
private password: string
protected innerData? : string
static readonly author: string //静态 只读
constructor (title: string, content: string){
this.title = title
this.content = content
}
//getter和setter
get password():string{
return '*******'
}
set password(newPwd: string){
this.password = newPwd
}
}
const a = new Article('标题','内容')

前端工程化环境搭建

安装 nodejs npm

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,可以使 JavaScript 运行在服务器端。使用 Node.js,可以方便地开发服务器端应用程序,如 Web 应用、API、后端服务,还可以通过 Node.js 构建命令行工具等。

Node.js 的核心是其管理事件和异步 I/O 的能力。Node.js 的异步 I/O 使其能够处理大量并发请求,并且能够避免在等待 I/O 资源时造成的阻塞。此外,Node.js 还拥有高性能网络库和文件系统库,可用于搭建 WebSocket 服务器、上传文件等。在 Node.js 中,我们可以使用 JavaScript 来编写服务器端程序,这也使得前端开发人员可以利用自己已经熟悉的技能来开发服务器端程序,同时也让 JavaScript 成为一种全栈语言。

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

NPM配置依赖下载使用阿里镜像

1
npm config set registry https://registry.npmmirror.com
  • 在 Windows 系统上,npm 的全局依赖默认安装在 <用户目录>\AppData\Roaming\npm 目录下。

  • 如果需要修改全局依赖的安装路径,可以按照以下步骤操作:

    1. 创建一个新的全局依赖存储目录,例如 D:\GlobalNodeModules

    2. 打开命令行终端,执行以下命令来配置新的全局依赖存储路径:

      1
      npm config set prefix "F:\GlobalNodeModules"
    3. 确认配置已生效,可以使用以下命令查看当前的全局依赖存储路径:

      1
      npm config get prefix

常用命令:

npm -v 查看版本

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
    • 安装package.json中的所有记录的依赖

3.升级依赖

  • npm update 包名
    • 将依赖升级到最新版本

4.卸载依赖

  • npm uninstall 包名

5.查看依赖

  • npm ls

    • 查看项目依赖
  • npm list -g

    • 查看全局依赖

Vue

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

Vue的两个核心功能:

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

Vue3通过Vite(前端脚手架)实现工程化

创建一个空目录用于存储多个前端项目,命令行运行如下命令

1
npm create vite@latest

Vue3使用 Composition API (组合式API)

使用setup代替 配置式API(option)-> data, methods

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 lang="ts">
import { ref } from 'vue';

let message = ref('你好')
function changeMessage() {
message.value = '666'
}
</script>
<script lang="ts">
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>


<template>
{{ message }}
{{ msg }}
<button @click="changeMessage">changeMessage</button>
</template>

在VScode扩展使用 Vetur 开发 Vue + TypeScript 项目时会显示报错组件没默认导出,程序能正常执行,需更换支持ts的语法高亮插件Vue - Official以取代 Vetur

Vue 响应式数据

定义一些要展示到html上的一些数据变量/对象
响应式数据:在数据变化时,vue框架会将变量最新的值更新到dom树中,页面数据就是实时最新的
非响应式数据:在数据变化时,vue框架不会将变量最新的值更新到dom树中,页面数据就不是实时最新的
vue2中,数据不做特殊处理,默认就是响应式的
vue3中,数据要经过ref /reactive函数的处理才是响应式的
ref reactive函数时vue框中给我们提供的方法,导入进来即可使用

1
import {ref,reactive} from 'vue'

让一个普通数据转换为响应式数据 两种方式

1.ref函数

更适合单个变量
在script标签中操作ref响应式数据要通过.value
在template中操作ref响应式数据则无需.value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
let counter = ref(10);
function add(){
counter.value++;
}
</script>
<template>
<div>
<button @click="add()">counter</button>
<!--或-->
<button @click="counter++">counter</button>
</div>
</template>

2.reactive函数 更适合对象

在script template 操作reactive响应式数据都直接使用对象名.属性名的方式即可

1
2
3
4
5
6
7
8
9
10
11
<script>
let person = reactive({
name:"",
age:10
})
</script>
<template>
<div>
<button @click="reactive.age++">reactive</button>
</div>
</template>

3. 用Object.assgin() 改变整个对象 或者ref

不改变其地址值 原地改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup()>
let person = reactive({
name:"",
age:10
})
function change(){
Object.assgin(person,{name:'张三',age:12})
}
</script>
<template>
<div>
<button @click="change()">reactive</button>
</div>
</template>

解构响应式数据需要加上toRefs或toRef

1
2
3
4
5
6
let person = reactive({
name:'zhangsan',
age:18
})
let {name,age} = toRefs(person)
let n1 = toRef(person,'name')

用ref绑定标签

1
2
3
4
5
6
 <button @click="showH2">打印h2</button>

const good = ref('good')
function showH2() {
console.log(good.value)
}

插值表达式

插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}

  • 插值表达式是将数据渲染到元素的指定位置的手段之一
  • 插值表达式不绝对依赖标签,其位置相对自由
  • 插值表达式中支持javascript的运算表达式
  • 插值表达式中也支持函数的调用
1
2
3
4
5
6
7
8
9
10
11
12
<!--script略-->
<template>
<div>
<h1>{{ msg }}</h1>
msg的值为: {{ msg }} <br>
getMsg返回的值为:{{ getMsg() }} <br>
是否成年: {{ age>=18?'true':'false' }} <br>
反转: {{ bee.split(' ').reverse().join('-') }} <br>
购物车总金额: {{ compute() }} <br/>
购物车总金额: {{carts[0].price*carts[0].number + carts[1].price*carts[1].number}} <br>
</div>
</template>

Props

Props 是 Vue 组件系统中父组件向子组件传递数据的主要方式。

下面一个案例介绍下基本用法

PersonInter.ts:

1
2
3
4
5
6
interface PersonInter {
name: string;
age: number;
sex?: string;
}
export type Persons = PersonInter[]

Person.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
<template>
<h1>{{ a }}</h1>
<h2>{{ list }}</h2>
</template>
<script lang="ts" setup name="Person">
import { PropType } from 'vue'
import {type Persons} from './PersonInter'
// 定义props 并接收props
let x = defineProps({
a: {
type: String,
default: '123'
},
list: {
//定义props时指定复杂类型
type: Object as PropType<Persons>,
default:() => [{name:'张三',age:18},{name:'李四',age:19}]
}
})
// 也可以这样定义props
defineProps<{a:string,list:Persons}>()
console.log(x.a)
console.log(x.list)
</script>

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import Person from './components/Person.vue'
import { ref } from 'vue'
import {type Persons} from './components/PersonInter'
const personList = ref<Persons>([
{name: '张三',age: 18,sex: '男'},
{name: '李四',age: 19,sex: '女'}
])
</script>

<template>
<div>
<Person a="123" :list="personList"></Person>
</div>
</template>

在 Vue 3.5 及以上版本中,从 defineProps 返回值解构出的变量是响应式的。当在同一个 <script setup> 块中的代码访问从 defineProps 解构出的变量时,Vue 的编译器会自动在前面添加 props.

文本渲染命令

v-text/v-html 类似 innerText innerHTML

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 type="module">
let msg ='hello vue3'
let getMsg= ()=>{
return msg
}
let age = 19
let bee = '蜜 蜂'
let redMsg ='<font color=\'red\'>msg</font>'
let greenMsg =`<font color=\'green\'>${msg}</font>`
</script>

<template>
<div>
<span v-text='msg'></span> <br>
<span v-text='redMsg'></span> <br>
<span v-text='getMsg()'></span> <br>
<span v-text='age>18?"成年":"未成年"'></span> <br>
<span v-text='bee.split(" ").reverse().join("-")'></span> <br>
<span v-html='msg'></span> <br>
<span v-html='redMsg'></span> <br>
<span v-html='greenMsg'></span> <br>
<span v-html="`<font color='green'>${msg}</font>`"></span> <br>
</div>
</template>

属性渲染命令

v-bind可以用于渲染任何元素的属性,语法为 v-bind:属性名='数据名', 可以简写为 :属性名='数据名'

image-20241022192220112

事件渲染命令

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

  • 用法:v-on:click="handler" 或简写为 @click="handler"
  • 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下
    • .once:只触发一次事件。[重点]
    • .prevent:阻止默认事件。[重点]
    • .stop:阻止事件冒泡。
    • .capture:使用事件捕获模式而不是冒泡模式。
    • .self:只在事件发送者自身触发时才触发事件。

image-20241022193306019

image-20241022193335105

条件渲染(展不展示)

v-if =”表达式/数据”数据为true 则当前元素会渲染进入dom树
v-else 自动和前一个v-if做取反操作
v-show=””数据为true 元素则展示在页面上,否则不展示

v-if 数据为false时,元素则不再dom树中了
v-show 数据为false是,元素仍在dom树中,通过display的css样式控制元素

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

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="module" setup>
import {ref} from 'vue'
let awesome = ref(true)
</script>

<template>
<div>
<h1 id="ha" v-show="awesome">Vue is awesome!</h1>
<h1 id="hb" v-if="awesome">Vue is awesome!</h1>
<h1 id="hc" v-else>Oh no 😢</h1>
<button @click="awesome = !awesome">Toggle</button>
</div>
</template>

列表渲染(展示一组)

我们可以使用 v-for 指令基于一个数组来渲染一个列表。

  • v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

  • v-for 块中可以完整地访问父作用域内的属性和变量。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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<script type="module" setup>

//引入模块
import { reactive} from 'vue'
//准备购物车数据,设置成响应数据
const carts = reactive([{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}])

//计算购物车总金额
function compute(){
let count = 0;
for(let index in carts){
count += carts[index].price*carts[index].number;
}
return count;
}
//删除购物项方法
function removeCart(index){
carts.splice(index,1);//(0,xx.length)是删除全部
}

</script>

<template>
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>商品名</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="carts.length > 0">
<!-- 有数据显示-->
<tr v-for="cart,index in carts" :key="index">
<th>{{ index+1 }}</th>
<th>{{ cart.name }}</th>
<th>{{ cart.price + '元' }}</th>
<th>{{ cart.number }}</th>
<th>{{ cart.price*cart.number + '元'}}</th>
<th> <button @click="removeCart(index)">删除</button> </th>
</tr>
</tbody>
<tbody v-else>
<!-- 没有数据显示-->
<tr>
<td colspan="6">购物车没有数据!</td>
</tr>
</tbody>
</table>
购物车总金额: {{ compute() }} 元
</div>
</template>

<style scoped>
</style>

双向绑定

单项绑定: v-bind 响应式数据发生变化时,更新dom树 用户的操作如果造成页面内容的改变不会影响响应式数据
双向绑定: v-model 页面上的数据由于用户的操作造成了改变,也会同步修改对应的响应式数据
双向绑定一般都用于表单标签
双向绑定也有人称呼为收集表单信息的命令
v-model:value=”数据”双向 绑定
v-model:value 一般都省略 :value -> v-model=” “

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
<script type="module" setup>

//引入模块
import { reactive,ref} from 'vue'
let hbs = ref([]); //装爱好的值
let user = reactive({username:null,password:null,introduce:null,pro:null})
function login(){
alert(hbs.value);
alert(JSON.stringify(user));
}
function clearx(){
//user = {};// 这中写法会将数据变成非响应的,应该是user.username=""
user.username=''
user.password=''
user.introduce=''
user.pro=''
hbs.value.splice(0,hbs.value.length);;
}
</script>

<template>
<div>
账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br>
密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br>
爱好:
吃 <input type="checkbox" name="hbs" v-model="hbs" value="吃">
喝 <input type="checkbox" name="hbs" v-model="hbs" value="喝">
玩 <input type="checkbox" name="hbs" v-model="hbs" value="玩">
乐 <input type="checkbox" name="hbs" v-model="hbs" value="乐">
<br>
简介:<textarea v-model="user.introduce"></textarea>
<br>
籍贯:
<select v-model="user.pro">
<option value="1">黑</option>
<option value="2">吉</option>
<option value="3">辽</option>
<option value="4">京</option>
<option value="5">津</option>
<option value="6">冀</option>
</select>
<br>
<button @click="login()">登录</button>
<button @click="clearx()">重置</button>
<hr>
显示爱好:{{ hbs }}
<hr>
显示用户信息:{{ user }}
</div>
</template>

<style scoped>
</style>

新( Vue3.4+ ):

defineModel()

这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声明 "modelValue" prop,由父组件通过 v-model 使用
const model = defineModel()
// 或者:声明带选项的 "modelValue" prop
const model = defineModel({ type: String })

// 在被修改时,触发 "update:modelValue" 事件
model.value = "hello"

// 声明 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel("count")
// 或者:声明带选项的 "count" prop
const count = defineModel("count", { type: Number, default: 0 })

function inc() {
// 在被修改时,触发 "update:count" 事件
count.value++
}

属性计算 computed

通过方法返回数据 每使用一次 执行一次

通过计算属性获得数据 每次使用是,如果和上次使用时,数据没有变化,则直接使用上一次的结果(缓存)

推荐使用计算属性来描述依赖响应式状态的复杂逻辑

1
2
3
4
5
6
7
8
9
//引入模块
import { reactive,computed} from 'vue'


// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
console.log("publishedBooksMessage")
return author.books.length > 0 ? 'Yes' : 'No'
})
1
2
3
4
5
6
7
8
9
10
let fullName = computed({
get() {
return firstName.value +'-'+ lastName.value
},
set(newValue) {
const [first,last] = newValue.split('-')
firstName.value = first
lastName.value = last
}
})

数据监听器 watch

更改 DOM,或是根据异步操作的结果去修改另一处的状态。我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:

  • watch主要用于以下场景:
    • 当数据发生变化时需要执行相应的操作
    • 监听数据变化,当满足一定条件时触发相应操作
    • 在异步操作前或操作后需要执行相应的操作
1
2
3
4
5
6
7
8
//监视响应式数据 ref(fullName)不加.value
const stopWatch = watch(fullName, (newValue, oldValue) => {
console.log('fullName', oldValue,'变成了',newValue)
if(newValue === '666') {
stopWatch()//停止监视
}
},{deep:true,immediate:true})//配置 deep:监视对象的属性值变化 immediate:立即执行一次监视方法
//如果监视的是reactive数据 默认且只能开启deep ref数据默认false

newValue,oldValue 所指向的是对象的地址 所以如果只是对象属性值的变化 newValue===oldValue

1
2
3
4
//只监视reactive数据的某个属性值的变化 相当于监视ref
watch(()=>person.name,(value)=>{
console.log('name变化了: ',value)
},{deep:true})

监视多个数据用数组包裹 略

1
2
3
4
5
6
7
8
//监听所有响应式数据
watchEffect(()=>{
//直接在内部使用监听属性即可!不用外部声明
//也不需要,即时回调设置!默认初始化就加载!
console.log(firstname.value)
console.log(lastname.name)
fullname.value=`${firstname.value}${lastname.name}`
})

组件(SFC)拼接页面

组件拼接 创建vue项目 安装依赖 在components目录下写需要引进的vue文件 在App.vue 引入

其中需要引进的vue文件 的css样式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.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;//
}

父子 兄弟传递 参数(数据) 略 见 下文Pinia

路由 router

定义:路由就是根据不同的 URL 地址展示不同的内容或页面。(选择展示的vue组件的切换)

  • 单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验;
  • 路由还可以实现页面的认证和权限控制,保护用户的隐私和安全;
  • 路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面。
1
npm i vue-router

需要被路由的页面组件一般放在view或pages目录下

路由组件放在router目录下 默认起名 index.ts

其他一般路由放在components目录下

History 路由模式 vs Hash 模式

  • Hash 模式:URL 形如 http://example.com/#/path# 后的变化不会触发页面刷新,服务器始终收到 /#/path 前的部分(如 /),因此无需特殊配置。

  • History 模式:URL 形如 http://example.com/path,更简洁。但用户直接访问或刷新时,浏览器会向服务器请求 /path,如果服务器未配置处理逻辑

    配置见上一篇笔记 Nginx重定向

    ),会返回 404(因为 /path 不是真实存在的文件或目录)。

2.App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<h1>这是测试路由的主页</h1>
<!-- 导航栏 -->
<div class="nav">
<router-link to="/" class="active">首页</router-link>
<router-link to="/help">帮助</router-link>
<router-link to="/news">新闻1</router-link>
</div>
<div class="content">
<router-view></router-view>
</div>
</template>

3.src/router/index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   const router = createRouter({
// History 路由模式
history: createWebHistory(),
routes: [
{ name:'home', path: '/', component: Home },
{ name:'help', path: '/help', component: Help },
{name:'news', path:'/news', component:News, children:[
//params传参写法
{name:'news-detail', path:'detail/:id/:title/:content', component:NewsDetail}
]}
//query传参写法
{name:'news-detail', path:'detail', component:NewsDetail}
]
})
export default router;


4.main.ts

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.mount('#app')
app.use(router)

5.News.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
<script setup lang="ts">
import { reactive } from 'vue';

let newsList = reactive([
{id:1,title:'新闻1',content:'新闻1内容'},
{id:2,title:'新闻2',content:'新闻2内容'},
{id:3,title:'新闻3',content:'新闻3内容'},
{id:4,title:'新闻4',content:'新闻4内容'},
{id:5,title:'新闻5',content:'新闻5内容'}
])
</script>

<template>
<div class="news">News
<ul>
<li v-for="item in newsList" :key="item.id">
<!-- 这里可修改query或params -->
<router-link :to="{name:'news-detail',
query:{id:item.id,
title:item.title,
content:item.content}}">{{item.title}}</router-link>
</li>
</ul>
</div>
<div class="news-content">
<router-view ></router-view>
</div>


</template>

6.NewsDetail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import { useRoute } from 'vue-router';
let route = useRoute();

</script>

<template>
<ul class="news-detail">
<!-- 这里可修改query或params -->
<li>编号: {{route.query.id}}</li>
<li>标题:{{ route.query.title }}</li>
<li>内容:{{ route.query.content }}</li>

</ul>

</template>

路由Props

第一种写法 params传参写法

index.ts:

1
{name:'news-detail', path:'detail/:id/:title/:content', component:NewsDetail,props:true}

NewsDetail.vue:

1
2
3
4
5
6
7
8
9
10
11
12
<script setup lang="ts">
defineProps(['id','title','content'])
</script>

<template>
<ul class="news-detail">
<li>编号: {{id}}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</template>

第二种函数写法 自己决定返回什么

index.ts:

1
2
{name:'news-detail', path:'detail', component:NewsDetail,props(route)
{return route.query}}

NewsDetail.vue同上。记得在父级展示路由的组件里改变params/query传参模式

编程式路由(useRouter)

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 {useRouter} from 'vue-router'
import {ref} from 'vue'
//创建动态路由对象
let router = useRouter()

let routePath =ref('')
let showList= ()=>{
// 编程式路由
// router-link里的to怎么写(字符串、对象)这里就怎么写
router.push({path:'/list'})
}
</script>

<template>
<div>

<!-- 动态输入路径,点击按钮,触发单击事件的函数,在函数中通过编程是路由切换页面 -->
<button @click="showList()">showList</button> <br>

</div>
</template>

<style scoped>
</style>

回调函数

一些特殊的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了

1
2
3
4
5
6
7
8
//两秒后触发一次事件
setTimeout(()=>{
console.log("setTimeout invoked")
},2000)
//每两秒触发一次事件
setInterval(()=>{
console.log("setInterval invoked")
},2000)

Promise

  • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

  • 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

image-20241030175911401

async和await

async 用于标识函数的

  1. async标识函数后,async函数的返回值会变成一个promise对象
  2. 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象
  3. 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定
  4. 如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象
1
2
3
4
5
6
7
8
async function fun1(){
//return 10
//throw new Error("something wrong")
let promise = Promise.reject("heihei")
return promise
}

let promise =fun1()

await

  1. await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
  2. 如果表达式是promise对象,await返回的是promise成功的值
  3. await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行
  4. 如果表达式是其他值,则直接返回该值
  5. await必须在async函数中,但是async函数中可以没有await
  6. 如果await右边的promise失败了,就会抛出异常,需要通过 try … catch捕获处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function fun1(){
return 10

}

async function fun2(){
try{

let res = await fun1()
//let res = await Promise.reject("something wrong")
}catch(e){
console.log("catch got:"+e)
}

console.log("await got:"+res)
}

fun2()

Axios

1
npm install axios

Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。它有如下特性

  • 从浏览器创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF
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
<script setup>
import { onMounted,ref } from 'vue'
import axios from 'axios'

let message = ref('')
function getMessage() {
axios({
method: 'get',
url: 'https://api.uomg.com/api/rand.qinghua?format=json',
data: {

}
}).then(function (response) {
console.log(response)
console.log(response.data)
console.log(response.data.content)
message.value = response.data.content

}).catch(function (error) {
console.log(error)
})
}
// 通过onMounted生命周期函数,在页面加载时,调用getMessage方法
// onMounted(() => {
// getMessage()
// })

</script>

<template>
<div>
<h1 v-text="message"></h1>
<button @click="getMessage()">变</button>
</div>
</template>

<style scoped>
</style>

简写:

1
2
3
4
5
6
7
function getWords(){
return axios.get("https://api.uomg.com/api/rand.qinghua?format=json")
}
async function getMessage(){
let {data} = await getWords()
Object.assign(message,data) //自动赋值
}

Axios 拦截器

如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成

定义src/axios.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
32
33
34
35
36
37
38
39
40
41
import axios from 'axios'


// 创建instance实例
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

跨域

同源策略(Sameoriginpolicy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号

产生跨域的原因

前后端分离模式下,客户端请求前端服务器获取视图资源,然后客户端自行向后端服务器获取数据资源,前端服务器的 协议,IP和端口和后端服务器很可能是不一样的,这样就产生了跨域

解决方案

代理或使用过滤器

image-20241031210908158

未来我们使用框架,直接用一个@CrossOrigin 就可以解决跨域问题了

现在需手动添加: 后端/filter/CrosFilter

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
package com.atguigu.schedule.filter;

import com.atguigu.schedule.common.Result;
import com.atguigu.schedule.util.WebUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter("/*")
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println(request.getMethod());
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT,OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
// 如果是跨域预检请求,则直接在此响应200业务码
if(request.getMethod().equalsIgnoreCase("OPTIONS")){
WebUtil.writeJson(response, Result.ok(null));
}else{
// 非预检请求,放行即可
filterChain.doFilter(servletRequest, servletResponse);
}
}
}

Pinia

1
npm install pinia

集中式状态(数据)管理

当我们有多个组件共享一个共同的状态(数据源)时,多个视图可能都依赖于同一份状态。来自不同视图的交互也可能需要更改同一份状态。虽然我们的手动状态管理解决方案(props,组件间通信,模块化)在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑:

  • 更强的团队协作约定
  • 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
  • 模块热更新 (HMR)
  • 服务端渲染支持

Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。https://pinia.vuejs.org/zh/introduction.html

main.ts 引入并使用

1
2
3
4
import {createPinia} from 'pinia'
let pinia = createPinia()
...
app.use(pinia)

定义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
32
33
import {defineStore } from 'pinia'

//定义数据并且对外暴露
// store就是定义共享状态的包装对象
// 内部包含四个属性: id 唯一标识 state 完整类型推理,推荐使用箭头函数 存放的数据 getters 类似属性计算,存储放对数据
// 操作的方法 actions 存储数据的复杂业务逻辑方法
// 理解: store类似Java中的实体类, id就是类名, state 就是装数据值的属性 getters就是get方法,actions就是对数据操作的其他方法
export const definedPerson = defineStore(
{
id: 'personPinia', //必须唯一
state:()=>{ // state中用于定义数据
return {
username:'张三',
age:0,
hobbies:['唱歌','跳舞']
}
},
getters:{// 用于定义一些通过数据计算而得到结果的一些方法 一般在此处不做对数据的修改操作
// getters中的方法可以当做属性值方式使用
getHobbiesCount(){
return this.hobbies.length
},
getAge(){
return this.age
}
},
actions:{ // 用于定义一些对数据修改的方法
doubleAge(){
this.age=this.age*2
}
}
}
)

(要操作pinia的vue).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
28
29
30
31
32
33
34
35
<script setup type="module">
import { ref} from 'vue';
import { definedPerson} from '../store/store';
// 读取存储的数据
let person= definedPerson()
//订阅 store 的状态变化 本次修改的信息 谁 和 修改后的数据 是什么
peron.$subscribe((mutate,state)=>{
consolg.log('peron数据发生了变化',mutate,state)
})

let hobby = ref('')

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

<!-- 事件中调用person的doubleAge()方法 -->
<button @click="person.doubleAge()">年龄加倍</button> <br>
<!-- 事件中调用pinia提供的$reset()方法恢复数据的默认值 -->
<button @click="person.$reset()">恢复默认值</button> <br>
<!-- 事件中调用$patch方法一次性修改多个属性值 -->
<button @click="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})">变身奥特曼</button> <br>
显示pinia中的person数据:{{person}}
</div>
</template>
<style scoped>
</style>

(要展示pinna的vue).vue 展示

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

另:

要解构store的数据,不用toRefs(会把所有的数据和方法都转成响应式),应引入storeToRefs(只把数据转成响应式)。

还可以用组合式写法 参考上面的setup 不过需要主动返回使用的方法、数据

自定义事件

用自定义事件实现数据 子传父

image-20250329232115711

用mitt实现组件通信

1
npm i mitt

在utils/tools文件夹下 创建emitter.ts

1
2
3
4
import mitt from 'mitt'
//emitter能绑定事件、触发事件
const emitter = mitt()
export default emitter

main.ts 引入:

1
import emitter from '@/utils/emitter'

image-20250330044300573

其他方法

off:解绑事件

all:查看所有事件

建议在组件卸载(onUnmounted)时解绑对应事件

用依赖注入进行父传后代组件通信

provide,injectimage-20250330052604060

插槽

默认插槽

image-20250330054914198

具名插槽

v-slot:s1可以写成#

image-20250330055502839

作用域插槽image-20250330060518633

其他API

teleport

让组件传送到指定的容器/选择器下

image-20250330061924349

Suspense

在异步请求完成之前先用别的组件作占位符

image-20250330062812978

全局API

image-20250330063545499

Element-plus

Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。

官网https://element-plus.gitee.io/zh-CN/

1
npm install element-plus

选vue+TypeScript

main.ts

1
2
3
4
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
...
app.use(ElementPlus)

Apache ECharts

ApacheECharts是一款基于Javascript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。
官网地址:https://echarts.apache.org/zh/index.html



新ICP备2025018290号-1
本站总访问量