前期准备

下载node.js,安装VueCLI

1
2
npm install -g @vue/cli #-g 全局安装
vue --version

创建项目

image-20250301182928730

1
vue create 项目名 #项目名中不能有大写字母,可以有小写字母和-

image-20250301182940607

项目结构

image-20250301182949806

在src下编写代码,其他是配置文件

src/assets:存放静态文件(公共CSS文件,图片)

src/components:公共组件

App.vue:根组件

main.js:主入口文件

模板语法

image-20250301183009709

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="hello">
<h3>学习模板语法</h3>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- <template>中是HTML,<script>中是业务逻辑-->

数据绑定

image-20250301183245974

双大括号搭配js中的data()函数,data()返回一个对象型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="hello">
<h3>学习模板语法</h3>
<span>{{ message }}</span>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data(){
return{
message:'study'
}
}
}
</script>

结果:

image-20250301183647990

原始HTML

image-20250301183729876

{{}}与v-html区别就好比document.getElementById().innerHTML和document.getElementById().innerText的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="hello">
<h3>学习模板语法</h3>
<span>{{ message }}</span>
<span>{{ rawHtml }}</span>
<p>v<span v-html="rawHtml"></span></p>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data(){
return{
message:'study',
rawHtml:'<a href="https://www.bilibili.com">bilibili</a>'
}
}
}
</script>

image-20250301184209600

属性Attribute

image-20250301215314417

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="hello">
<h3>学习模板语法</h3>
<span>{{ message }}</span>
<span>{{ rawHtml }}</span>
<p>v<span v-html="rawHtml"></span></p>
<span v-bind:id="Id"></span>
<span :id="Id"></span>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data(){
return{
message:'study',
rawHtml:'<a href="https://www.bilibili.com">bilibili</a>',
Id:114514
}
}
}
</script>

使用JavaScript表达式

image-20250301215705367

image-20250301220643449

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
<template>
<div class="hello">
<h3>学习模板语法</h3>
<span>{{ message }}</span>
<span>{{ rawHtml }}</span>
<p>v<span v-html="rawHtml"></span></p>
<span v-bind:id="Id"></span>
<span>{{ flag?"123":"456" }}</span>
<br>
<span>{{ num+10 }}</span>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data(){
return{
message:'study',
rawHtml:'<a href="https://www.bilibili.com">bilibili</a>',
Id:114514,
flag:true,
num:20
}
}
}
</script>

不能在双括号里++ – +=,会引发无限递归,在 Vue 的模板中,每次渲染时都会执行这个表达式,导致 num 的值不断递增

image-20250301220417011

条件渲染

v-if

image-20250301220831076

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div v-if="flag">你好</div>
</template>

<script>
export default {
data(){
return{
flag:true
}
}
}
</script>

v-else

image-20250301221339380

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div v-if="!flag">你好</div>
<div v-else>不好</div>
</template>

<script>
export default {
data(){
return{
flag:true
}
}
}
</script>

v-show

image-20250301221847964

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div v-if="!flag">你好</div>
<div v-else>不好</div>
<div v-show="flag">芜湖</div>
<div v-show="!flag">不芜湖</div>
</template>

<script>
export default {
data(){
return{
flag:true
}
}
}
</script>

image-20250301222132146

即使!flag为false,不芜湖这句依然存在,只是css样式被替换

列表渲染

image-20250301222654851

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<li v-for="item in List">
{{ item.message }}
</li>
</template>

<script>
export default {
data(){
return{
List:[
{message:"1"},
{message:"2"},
{message:"3"}
]
}
}
}
</script>

image-20250301223635659

维护状态

image-20250301224216102

就地更新指的是

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
<!--原来-->
<script>
export default {
data(){
return{
List:[
{message:"1"},
{message:"2"},
{message:"3"}
]
}
}
}
</script>
<!--发生更改-->
<script>
export default {
data(){
return{
List:[
{message:"1"},
{message:"2"},
{message:"3"},
{message:"4"}//只会重新渲染这一条新的
]
}
}
}
</script>

增加v-bind:key属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<li v-for="item in List" :key="item.id">
{{ item.message }}
</li>
</template>

<script>
export default {
data(){
return{
List:[
{id:1,message:"1"},
{id:2,message:"2"},
{id:3,message:"33"},
{id:4,message:"4"}
]
}
}
}
</script>

如果数组中元素没有id怎么办,v-for遍历时其实有一个索引index,将index作为key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<li v-for="(item,index) in List" :key="index">
{{ item.message }}
</li>
</template>

<script>
export default {
data(){
return{
List:[
{id:1,message:"1"},
{id:2,message:"2"},
{id:3,message:"33"},
{id:4,message:"4"}
]
}
}
}
</script>

事件处理

监听事件

image-20250301232347901

v-on缩写为@

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<button @click="count+=1">{{ count }}</button>
</template>

<script>
export default {
data(){
return{
count:0
}
}
}
</script>

事件处理方法

image-20250301232618798

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<button @click="clickEvent">{{ count }}</button>
</template>

<script>
export default {
data(){
return{
count:0
}
},
methods:{
clickEvent(){
this.count+=1;//在事件中读取data的属性需要用this.属性
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<button @click="clickEvent">{{ message }}</button>
<button @click="Increment">count = {{ count }}</button>
</template>

<script>
export default {
data() {
return {
count: 0,
message: "通知消息"
}
},
methods: {
clickEvent(event) {
event.target.innerHTML = "点击了通知消息";//对原生dom事件处理
},
Increment() {
this.count += 1;
}
}
}
</script>

点击前:

image-20250301233631192

点击两个按钮后:

image-20250301233652783

内联处理器中的方法/事件传递参数

image-20250301234956862

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>
<li @click="ItemHandle(item)" v-for="(item,index) in List" :key="index">
{{ item }}
</li>
</template>

<script>
export default {
data() {
return {
count: 0,
message: "通知消息",
List:[
'111','222','333'
]
}
},
methods: {
ItemHandle(item){
alert(item);
}
}
}
</script>

表单输入绑定

image-20250302111039703

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<input v-model="username">
<p>{{ username }}</p>
</template>

<script>
export default {
data(){
return{
username:""
}
}
}
</script>

实现效果,p标签中的值根据表单输入数据实时更新

修饰符

.lazy

image-20250302111847948

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<input v-model.lazy="username">
<p>{{ username }}</p>
</template>

<script>
export default {
data(){
return{
username:""
}
}
}
</script>

回车之后才会同步更新,不会再一边输入一边同步

.trim

image-20250302112125443

组件基础

单文件组件

image-20250302112313964

模板:html(必须存在),逻辑:js(看需求存在),样式:css(看需求存在

1
2
3
4
5
6
7
8
9
10
11
<template>

</template>

<script>

</script>

<style>

</style>

加载组件

image-20250302120131965

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
<!-- 
在根组件中App.vue中:
引入组件:import 组件名 from '组件文件的路径'
挂在组件:在export default{}块中加入components:{组件名}
显示组件:template块中加上标签<组件名/>
-->

<!--MyComponent.vue-->
<template>
<h3>MyComponent</h3>
</template>

<script>
export default{//默认组件
name:'Mycomponent'//组件名
}
</script>

<!-- scoped:样式只在当前样式中生效 -->
<style scoped>
h3{
color: crimson;
}
</style>
<!--App.vue-->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<MyComponent/>
<my-component/><!--也可以这样写,用-分开单词-->
</template>

<script>

import MyComponent from './components/MyComponent.vue';
export default {
name: 'App',
components: {
MyComponent
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

效果:

image-20250302120856305

组件的组织

image-20250302132914953

Props组件交互

image-20250302133211194

父组件怎么向子组件中传参?

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
<!--传参-->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<MyComponent :title="title"/><!--:key="value"的形式-->
</template>

<script>
import MyComponent from './components/MyComponent.vue';

export default {
name: 'App',
components: {
MyComponent
},
data(){
return {
title:"props组件交互"
}
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<!--接收参数-->
<template>
<h3>props组件交互</h3>
<p>{{ title }}</p>
</template>

<script>
export default{
name:'MyComponent',
props:{//固定格式props:{参数名:{type:,default:}}
title:{
type:String,
default:"null"//默认值
}
}
}
</script>

<style scoped>
h3{
color: crimson;
}
p{
color:aquamarine;
}
</style>

image-20250302140230218

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
<!-- 传递多个参数与数组 -->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<MyComponent :title="name" :age="age" :List="List"/>
</template>

<script>
import MyComponent from './components/MyComponent.vue';

export default {
name: 'App',
components: {
MyComponent
},
data(){
return {
name:"props",
age:20,
List:['amy','bob','cindy']
}
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<!-- 接收多个参数与数组 -->
<template>
<h3>props组件交互</h3>
<p>{{ title }}</p>
<p>age = {{ age }}</p>
<p v-for="(item,index) in List" :key="index"> {{ item }} </p>
</template>

<script>
export default{
name:'MyComponent',
props:{
title:{
type:String,
default:"null"
},
age:{
type:Number,
default:0
},
List:{
type:Array,
//数组和对象必须以函数形式返回(工厂模式)
default:function(){
return [];
}
}
}
}
</script>

<style scoped>
h3{
color: crimson;
}
p{
color:aquamarine;
}
</style>

自定义事件组件交互

用$emit()

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
<!-- 发送 -->
<template>
<h3>自定义事件组件交互</h3>
<button @click="SendMessage">Click~</button>
</template>

<script>
export default{
name:'MyComponent',
data(){
return{
message:'这是一个信息'
}
},
methods:{
SendMessage(){
/*
参数一:字符串,是自定义的事件名
参数二:要传递的内容
*/
this.$emit('OnEvent',this.message);
}
}
}
</script>

<style scoped>
h3{
color: crimson;
}
p{
color:aquamarine;
}
</style>
<!-- 接收 -->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<MyComponent @OnEvent="GetMessage" /><!--@自定义事件="处理的函数"-->
<p>{{ Message }}</p>
</template>

<script>
import MyComponent from './components/MyComponent.vue';

export default {
name: 'App',
components: {
MyComponent
},
data(){
return{
Message:""
}
},
methods: {
GetMessage(Message) {//Message就是自定义事件OnEvent传递过来的参数
this.Message = Message
}
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

组件生命周期

image-20250302183721270

示意图

image-20250526162807435

1
2
3
4
5
6
#先执行以下命令
npm init vue@latest
#一路选n
npm install / cnpm i

npm run dev

生命周期

1
2
3
4
5
6
7
/*
分为:
创建期:beforeCreate created
挂载期:beforeMount mounted
更新期:beforeUpdate updated
销毁期:beforeUnmount unmounted
*/

钩子函数不能写在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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<h3>组件生命周期</h3>
<button @click="Increment">count = {{ count }}</button>
</template>

<script>
export default {
data() {
return {
count: 0
}
},
methods: {
Increment() {
this.count += 1;
},
},
beforeCreate() {
console.log('创建前');
},
created() {
console.log('已创建');
},
beforeMount() {
console.log('挂载前');
},
mounted() {
console.log('已挂载');
},
beforeUpdate() {
console.log('更新前');
},
updated() {
console.log('更新后');
}
}
</script>

<style>
h3 {
color: crimson;
}
</style>

结果:

image-20250302191722582

Vue引入第三方

https://github.com/vuejs/awesome-vue

Swiper

官方文档:https://swiperjs.com/vue

必须引入Swiper和SwiperSlide组件和swiper/css中的样式

1
2
#引入swiper,指定8.1.6版本
npm install --save swiper@8.1.6
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
<template>
<Swiper>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
</Swiper>
</template>

<script>
import { Swiper, SwiperSlide } from 'swiper/vue';//引入组件
import 'swiper/css';//引入样式


export default {
name: 'HelloWorld',
components: {//注入组件
Swiper,
SwiperSlide
}
}
</script>

<style scoped></style>

添加指示器

1
2
3
4
5
6
7
8
9
10
//script标签中加上
import { Pagination } from 'swiper';
import 'swiper/css/pagination';
//export default块中
data(){
return{
modules:[Pagination]
}
}
//Swiper标签中加上 :modules="modules" :pagination="{clickable:true}"
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
<template>
<Swiper :modules="modules" :pagination="{clickable:true}">
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
<SwiperSlide>
<img src="../assets/logo.png">
</SwiperSlide>
</Swiper>
</template>

<script>
import { Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/pagination';


export default {
name: 'HelloWorld',
data(){
return{
modules:[Pagination]
}
},
components: {
Swiper,
SwiperSlide
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

效果:

image-20250302195501331

生命周期应用

通过ref获取元素DOM结构

获取DOM的时机:

不能是创建期:UI还未渲染到页面上,无DOM结构

挂载期:挂载前不行,理由同上;挂载后(mounted)可以

更新期与销毁期也可以

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
<template>
<!-- 要获取的DOM -->
<p ref="name">ref~</p>
</template>

<script>
export default{
mounted(){
console.log(this.$refs.name);//this.$refs.name获取
}
}
</script>

<!-- 根组件 -->
<template>
<User />
</template>

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

模拟网络请求渲染数据

时机:

created:先获取到数据,但是还没渲染

mounted:先渲染再获取数据

我们选择后者的逻辑

AXios网络请求封装

1
2
3
4
cnpm install --save axios
cnpm install --save querystring
vue create demo
npm run serve

在src下新建一个目录utils,创建文件request.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
42
43
44
45
46
47
48
import querystring from 'querystring'
import axios from 'axios'
import { config } from 'process';
import { log } from 'console';

const instance = axios.create({
//网络请求的公共属性
timeout: 10000 //ms
})

//处理错误
const errorHandle = (status,info) => {
switch(status){
default:
console.log(info);
}
}
//常用于拦截器
//发送数据前request
instance.interceptors.request.use(
//成功
config => {//config包含网络请求所有信息
if(config.method === 'post'){//对post请求特殊处理,axios中post返回json格式,querystring.stringify转换为url编码
config.data = querystring.stringify(config.data);
}
return config;
},
//失败
error => {
//Promise.reject(error)会将错误抛出,从而可以在调用axios的地方通过 .catch()捕获错误
return Promise.reject(error);
}
);
//获取数据前response
instance.interceptors.response.use(
//成功
response => {
return response.status===200?Promise.resolve(response):Promise.reject(response)
},
//失败
error => {
//将error.response赋给response
const {response} = error;
errorHandle(response.status,response.info);
}
);

export default instance;

将网络请求集中放在src/api中

image-20250302214622773

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//path.js
const Base = {
//公共路径
baseURL: "",
//拼在后面的
extra:""
}

export default Base;

//index.js
import axios from "../utils/request";
import path from "./path";

const api = {
get(){
return axios.get(path.baseURL+path.extra);
}
}

export default api;

动态组件

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
<template>
<component :is="Component"></component> <!-- :is来控制当前组件 -->
<button @click="changeComponent">切换组件</button>
</template>

<script>
import A from './components/A.vue';
import B from './components/B.vue';

export default {
data() {
return {
Component: A, // 初始组件
componentsList: [A, B], // 组件列表
currentIndex: 0, // 当前组件索引
};
},
methods: {
changeComponent() {
// 切换组件
this.currentIndex = (this.currentIndex + 1) % this.componentsList.length;
this.Component = this.componentsList[this.currentIndex];
},
},
};
</script>

路由

路由配置

image-20250303133659625

1.安装路由

1
2
3
4
npm install --save vue-router@4 #不使用@4的话,createWebHashHistory和createRouter不能同时使用
vue create demo
cd demo
npm run serve

2.配置独立的路由文件/src/route/index.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
//index.js
import { createRouter,createWebHashHistory } from "vue-router";
import Home from '../view/Home.vue';

/**

* 页面的配置信息
* path:路径
* component:组件
*/
const routes = [
{path:'/',component:Home},
//@通常指向src目录;()=>import()属于异步加载,指向这个路径时才会挂载
{path:'/about',component:() => import('@/view/About.vue')}
];

const router = createRouter({
history:createWebHashHistory(),
routes,
});

export default router;

/**
* createWebHashHistory()和createWebHistory()的区别
*
* createWebHashHistory():
* 路径是这样的:/#/path...
* 不需要后端配合重定向
* 原理:<a>的锚点链接
* createWebHistory():
* 路径是这样的:/path...
* 需要后端配合重定向,不然会404
* 原理:H5 pushState()
*/

3.在main.js中注册组件

1
2
3
4
5
6
7
8
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './route'

//在mount前用use(router)注册组件
createApp(App).use(router).mount('#app');
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
<!-- App.vue -->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 路由跳转 -->
<RouterLink to="/">首页 |</RouterLink>
<RouterLink to="/about"> 关于</RouterLink>
<!-- 路由的显示入口 -->
<router-view></router-view>
</template>

<script>

export default {
name: 'App',
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

传递参数

创建vue项目时把Router也勾选上

image-20250303150439342

1.在routers数组中的path:’/path/:参数名’

2.传递参数:router-link的to中’/path/参数值’

3.读取参数

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
//index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
},
{
path:'/news',
//@通常指向src目录;()=>import()属于异步加载,指向这个路径时才会挂载
component: ()=>import('@/views/News.vue')
},
{
path:'/news/details/:name',
//@通常指向src目录;()=>import()属于异步加载,指向这个路径时才会挂载
component: ()=>import('@/views/NewsDetails.vue')
}
]

const router = createRouter({
history: createWebHashHistory(),
routes
})

export default router
1
2
3
4
5
<!-- 读取参数 -->
<template>
<h3>平台:</h3>
<p>{{ $route.params.name }}</p>
</template>

嵌套路由配置

在页面配置信息的routes数组中的对象中加上children数组,填上子页面的信息(路径不能有/)

如果希望默认打开某个子页面,用redirect重定向

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
//index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
//默认的子导航
redirect:'/about/us',
//子导航
children:[
{
//子导航不加/
path:'us',
component: () => import('../views/AboutUs.vue')
},
{
path:'info',
component: () => import('../views/AboutInfo.vue')
}
]
}
]

const router = createRouter({
history: createWebHashHistory(),
routes
})

export default router

Vue状态管理(Vuex)

image-20250303160946625

image-20250303161129606

store就相当于集中式存储管理

引入步骤

1.安装Vuex

1
cnpm install --save vuex

2.配置文件/src/store/index.js

1
2
3
4
5
6
7
8
9
import { createStore } from 'vuex'

//Vuex的作用就是管理组件间状态的
export default createStore({
//所有状态都放在这里(数据),可以被所有组件读取
state:{
count:0
}
});

3.main.js中use()

1
2
3
4
5
6
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import store from '@/store'

createApp(App).use(store).mount('#app')

3.访问数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<!-- 方法1 -->
<p>{{ $store.state.count }}</p>
<!-- 方法2 -->
<p>{{ count }}</p>
</template>

<script>
// <!-- 方法2 -->
import { mapState } from 'vuex';
export default{
computed:{
...mapState(['count'])
}
};
</script>

Vue状态管理核心(Vuex)

image-20250303164818191

Getter

对Vuex中数据进行过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createStore } from 'vuex'

export default createStore({
state: {
count:0
},
getters: {
//要传入state
getCount(state){
return state.count>0?state.count:'太小了!!!!!!';
}
},
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<img alt="Vue logo" src="./assets/logo.png">
<p>访问方法1=>{{ $store.getters.getCount }}</p>
<p>访问方法2=>{{ getCount }}</p>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters(['getCount'])
}
}
</script>

Mutation

image-20250303170427611

1
2
3
4
5
6
7
8
9
10
11
12
13
//index.js
import { createStore } from 'vuex'

export default createStore({
state: {
count: 0
},
mutations: {
addCount(state) {
state.count++;
}
}
})

调用方法

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
<template>
<img alt="Vue logo" src="./assets/logo.png">
<p>访问方法1=>{{ $store.getters.getCount }}</p>
<p>访问方法2=>{{ getCount }}</p>
<button @click="addCountHandle">Click~</button>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getCount'])
},
methods: {
addCountHandle() {
this.$store.commit('addCount');//this.$store.commit('mutations中的方法名')
}
}
}
</script>
<!-- 或者可以这样写 -->
<script>
import { mapGetters,mapMutations } from 'vuex'
export default {
computed: {
...mapGetters(['getCount'])
},
methods: {
...mapMutations(['addCount']),
addCountHandle() {
this.addCount();
}
}
}
</script>

Action

image-20250303171600399

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 { createStore } from 'vuex'
import axios from 'axios'

export default createStore({
state: {
count: 0
},
getters: {
getCount(state) {
return state.count > 0 ? state.count : '太小了!!!!!!';
}
},
mutations: {
addCount(state) {
state.count++;
}
},
//只能异步
actions: {
//{commit}:固定写法
asyncAddCount({ commit }) {
axios.get('')//axios封装网络请求
.then(
res=>{
commit('addCount');
}
)
}
}
})

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>
<img alt="Vue logo" src="./assets/logo.png">
<p>访问方法1=>{{ $store.getters.getCount }}</p>
<p>访问方法2=>{{ getCount }}</p>
<button @click="addCountHandle">Click~</button>
<button @click="AsyncAddCountHandle">Click~</button>
</template>

<script>
import { mapGetters,mapMutations } from 'vuex'
export default {
computed: {
...mapGetters(['getCount'])
},
methods: {
...mapMutations(['addCount']),
addCountHandle() {
this.addCount();
},
AsyncAddCountHandle(){
this.$store.dispatch('asyncAddCount');
}
}
}
</script>

Vue3新特性

image-20250304214035796

ref或reactive

组合式API替换data(){},在setup()中声明变量(必须return)

vue2.x中在data的return中声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h3>vue3特性</h3>
<span>{{ message }}</span>
</template>

<script>
export default{
data(){
return{
message:"message"
}
}
}
</script>

用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
<template>
<h3>vue3特性</h3>
<span>{{ message }}</span>
<ul>
<li v-for="(item,index) in name.list" :key="index">{{ item }}</li>
</ul>
</template>

<script>
import {reactive, ref} from 'vue'
export default{
//组合式API必须要有setup
setup(){
//ref
const message = ref("ref");
//reactive:用于对象
const name = reactive({
list:['111','222','333']
})
//必须return
return{
message,
name//name是一个对象,要访问的是name.list
}
}
}
</script>

methods中定义的方法写在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
<template>
<h3>vue3特性</h3>
<button @click="Increment">count = {{ count }}</button>
</template>

<script>
import { reactive, ref } from 'vue'
export default {
//组合式API必须要有setup
setup() {
let count = ref(0);
const Increment = () => {
//ref返回的是一个对象,要访问它的值需要.value
count.value += 1;
};

return {
count,
//函数作为变量返回
Increment
}
}
}
</script>

setup()中使用props和content

image-20250305154230155

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h3>vue3特性</h3>
<button @click="Increment">count = {{ count }}</button>
</template>

<script>
import { reactive, ref } from 'vue'
export default {
//组合式API必须要有setup
setup(props) {
//props是一个Proxy代理对象
}
}
</script>

image-20250305154707094

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
<template>
<h3>vue3特性</h3>
</template>

<script>
// import { reactive, ref } from 'vue'
export default {
//这步不能省略
props: {
message: String//只需要声明类型
},
setup(props) {
console.log(props.message)
}
}
</script>

<template>
<HelloWorld message="msg" />
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';

export default {
components:{
HelloWorld
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<h3>vue3特性</h3>
<h3>{{ msg }}</h3>
</template>

<script>
// import { reactive, ref } from 'vue'
export default {
//这步不能省略
props: {
message: String//只需要声明类型
},
setup(props) {
//也通过这样声明,返回回去可以显示在页面上
const msg = props.message;
return {
msg,
}
}
}
</script>

setup中无法使用this获取实例对象,使用content可以获取实例对象

setup()中使用生命周期函数

优势:setup中可以同时存在多个生命周期函数

image-20250305161228466

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
<template>
<h3>vue3特性</h3>
<h3>{{ msg }}</h3>
</template>

<script>
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated } from 'vue';

export default {
setup() {
//内部是箭头函数
onMounted(() => {
console.log('onMounted1');

}),

onMounted(() => {
console.log('onMounted2');
}),

onBeforeMount(() => {

}),
onUpdated(() => {

}),
onBeforeUpdate(() => {

}),
onUnmounted(() => {

}),
onBeforeUnmount(() => {

})
}
}
</script>

Provide/Inject

image-20250305161838926

可以跨层级传递,但是必须从上至下

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
<!-- HelloWorld.vue -->
<template>
<h3>{{ msg }}</h3>
</template>

<script>
import { inject } from 'vue';



export default{
setup(){
const msg = inject("msg");
return{
msg
}
}
}
</script>

<!-- App.vue -->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld/>
</template>

<script>
import { provide } from 'vue';
import HelloWorld from './components/HelloWorld.vue'

export default {
name: 'App',
components: {
HelloWorld
},
setup() {
provide("msg","provide的消息")
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Fragment

image-20250305163230321

Element-Plus

官网:https://element-plus.sxtxhy.com/zh-CN/

安装

1
npm install element-plus --save

完整引入

image-20250305163955494

1
2
3
4
5
6
7
8
//main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'//引入组件
import 'element-plus/dist/index.css'//必须引入css样式
import App from './App.vue'
import './registerServiceWorker'

createApp(App).use(ElementPlus).mount('#app')//.use挂载组件

在官网上复制相关组件的代码

按需引入

image-20250305164149182

1
npm install -D unplugin-vue-components unplugin-auto-import

image-20250305164419439

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//vue.config.js
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
}
})

加载字体图标

1
2
npm update unplugin-auto-import unplugin-vue-components
npm install @element-plus/icons-vue

全局注册

创建/src/plugins/icon.js

image-20250305194014301

1
2
3
4
5
6
7
8
9
10
//icon.js
import * as components from "@element-plus/icons-vue";
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};
1
2
3
4
5
6
7
8
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import 'element-plus/dist/index.css'
import icon from './plugins/icon'//引入icon.js

createApp(App).use(icon).mount('#app')

然后从官网复制图像的代码

Vue应用

应用实例

image-20250305201555244

Vue的实例对象有且仅有一个

根组件

image-20250305202022580

挂载应用

image-20250305202151292

#app会去index.html(与src同级)中寻找dom元素app

image-20250305203330731

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue</title>
</head>
<body>
<div id="app">
111
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

公共资源

image-20250305203011640

两种风格的API

选项式API

image-20250305203514881

1
npm init vue@latest
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
<template>
<button @click="Increment">count = {{ count }}</button>
</template>

<script>
export default {
data() {
return {
count: 0
}
},
methods: {
Increment() {
this.count++;
}
}
}
</script>

<template>
<Options />
</template>

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

组合式API

image-20250305205142028

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<button @click="Increment">count = {{ count }}</button>
</template>

<!-- 使用 <script setup> 语法糖,不用显式返回对象 -->
<script setup>
import {ref,onMounted} from 'vue'
const count = ref(0);

function Increment(){
count.value ++;
}

onMounted(()=>{

})
</script>

简约写法

1
<script setup></script>
是一种语法糖,不用在setup()中显式return,直接在template中{{ }}调用就行,但是就不能组合式与选项式混写,只能组合式

组合式与选项式的区别

image-20250305210454512

响应式

组合式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<h3>{{ message }}</h3><br>
<h3>{{ obj.name }}</h3>
</template>

<!-- <script setup>语法糖,不用显示return{} -->
<script setup>
import { reactive, ref } from 'vue';

const message=ref("Com~");
const obj = reactive({
name:"jellycat",
message:message
})
</script>

image-20250305211426791

image-20250305211638443

选项式

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<h3>{{ message }}</h3><br>
</template>

<script>
export default{
data(){
return{
message:"Options!"
}
}
}
</script>

计算属性

组合式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h3>Com-reverse:{{ res }}</h3>
</template>

<script setup>
import { ref,computed } from 'vue';

const message = ref("Hello World!");

const res = computed(() =>{
//调用ref对象的值必须.value
return message.value.split("").reverse().join('');
})
</script>

image-20250305215101732

优势:可以把内容单独拆到一个文件中

eg:

1
2
3
4
5
6
7
8
import { computed } from 'vue';

export function MyDemo(message) {
const demo = computed(() => {
return message.value + "114514";
})
return demo
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h3>Com-reverse:{{ res }}</h3>
<h3>demo:{{ res1 }}</h3>
</template>

<script setup>
import { ref,computed } from 'vue';
import {MyDemo} from './Comcomputedsingle'//引入函数

const message = ref("Hello World!");

const res = computed(() =>{
//调用ref对象的值必须.value
return message.value.split("").reverse().join('');
})

const res1 = MyDemo(message);
</script>

选项式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h3>Opt-Reverse:{{ Fliter }}</h3>
</template>

<script>
export default{
data(){
return {
message:"Hello World!"
}
},
computed:{
Fliter(){
return this.message.split("").reverse().join();
}
}
}
</script>

组合式API_事件处理

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
<template>
<button @click="Increment">选项式API:Count = {{ count }}</button><br>
</template>

<script>
export default{
data(){
return{
count:0
}
},
methods:{
Increment(){
this.count++
}
}
}
</script>

<template>
<button @click="Increment">组合式API: count = {{ count }}</button>
</template>

<script setup>
import {ref} from 'vue'

const count = ref(0);

function Increment(){
count.value++;
}
</script>

组合式API_侦听器

image-20250306185957297

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<button @click="Increment">{{ count }}</button>
</template>

<script>
export default{
data(){
return{
count:0
}
},
methods:{
Increment(){
this.count++
}
},
watch:{
//方法名就是要监听的变量
count(OldValue,NewValue){
console.log(OldValue,NewValue);
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<button @click="Increment">{{ count }}</button>
</template>

<script setup>
import {ref,watch} from 'vue'
count = ref(0);
function Increment(){
count.value++
};

//参数1:要监听的变量,残数2:带两个参数的箭头函数
watch(count,(OldValue,NewValue) => {
console.log(OldValue,NewValue);
})
</script>

将侦听器放到单独一个文件中

1
2
3
4
5
6
7
8
import {watch} from 'vue'

export function Watcher(count){
watch(count,(OldValue,NewValue) => {
console.log(OldValue,NewValue);

})
}

注意:watch监听时,ref对象不需要.value

组合式API_生命周期

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
<!-- 选项式 -->
<template>

</template>

<script>
export default{
beforeCreate(){

},
created(){

},
beforeMount(){

},
mounted(){

},
beforeUpdate(){

},
updated(){

},
beforeUnmount(){

},
unmounted(){

}

}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 组合式API,优点是可以同时存在多个生命周期函数 -->
<template>

</template>

<script>
import { onMounted, onUpdated } from 'vue'
onMounted(() => {

}),
onUpdated(() => {

})


</script>

组合式API_模板引用

1
2
3
4
5
6
7
8
9
10
11
<!-- 组合式API -->
<template>
<h3 ref="message">Opt</h3>
</template>
<script>
export default{
mounted(){
this.$refs.message.innerHTML='OOOppt';
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 选项式API -->
<template>
<h3 ref="message">Com</h3>
</template>
<script setup>
import {ref,onMounted} from 'vue'

//获取原生DOM,声明一个ref来存放该元素的引用,必须和模板中的ref同名,否则读取不到
const message = ref(null);
//修改DOM内容前提是页面已渲染,所以该操作要放在生命周期函数中
onMounted(()=>{
message.value.innerHTML = 'CCCOOM';
})

</script>

组合式API_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
28
29
<!-- 选项式API父组件 -->
<template>
<OptChild msg="114514" />
</template>

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

<!-- 选项式API子组件 -->
<template>
<p>{{ msg }}</p>
</template>

<script>
export default {
props: {
msg: {
type: String,
default: ""
},
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 组合式API父组件 -->
<template>
<!-- 不用再去export default中挂载组件 -->
<ComChild msg="1919810"/>
</template>

<script setup>
import ComChild from './ComChild.vue';
</script>
<!-- 组合式API子组件 -->
<template>
<p>{{ msg }}</p>
</template>

<script setup>
//defineProps的方式进行接收,内部是对象的形式
defineProps({
msg:{
type:String,
default:""
},
});
</script>

组合式API_事件

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
<!-- 选项式API子组件 -->
<template>
<button @click="sendMsg">发送数据</button>
</template>

<script>
export default {
data(){
return{
message:"114514"
}
},
methods:{
sendMsg(){
this.$emit('OnEvent',this.message);
}
}
}
</script>
<!-- 选项式API父组件 -->
<template>
<OptChild @onSomeEvent="acpt" />
<p>接收数据:{{ message }}</p>
</template>

<script>
import OptChild from './OptChild.vue';
export default{
components:{
OptChild,
},
data(){
return{
message:""
}
},
methods:{
acpt(data){
this.message = data;
}
}
}
</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
<!-- 组合式API父组件 -->
<template>
<ComChild @onEvent="getMsg" />
<p>收到数据{{ message }}</p>
</template>

<script setup>
import ComChild from './ComChild.vue';
import {ref} from 'vue'

const message = ref("");

function getMsg(data){
message.value = data;
// console.log(data);

}
</script>
<!-- 组合式API子组件 -->
<template>
<button @click="sendMsg">获取数据</button>
</template>

<script setup>
import {ref} from 'vue'
const message = ref("1919810");
//相当于获取this.$emit
const emit = defineEmits(["onEvent"]);

function sendMsg(){
emit("onEvent",message.value);
}
</script>

自定义指令

选项式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<!-- 用v-自定义指令来调用自定义指令 -->
<h3 v-jellycat>hello world</h3>
</template>

<script>
export default{
//通过directives创建自定义指令
directives:{
//指令名
jellycat:{
//指令要做什么
mounted(element){
console.log(element);

}
}
}
}
</script>

组合式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<span v-jellycat>hello</span>
</template>

<script setup>
// 前面+v表示自定义指令
const vJellycat = {
//注意写法 mouted:
mounted:(element) => {
console.log(element);

// 内部逻辑
}
}
</script>

全局与局部自定义指令

image-20250306213559556

局部

1
2
3
4
5
6
7
8
9
10
11
<template>
<h3 v-scp>Scoped</h3>
</template>

<script setup>
const vScp = {
mounted(element){
element.style.color = "green"
}
}
</script>

全局

需要在main.js中创建自定义指令

1
2
3
4
5
6
7
8
9
10
11
12
13

import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App);
//参数1:指令名 参数2:箭头函数,指令实现的功能
app.directive("glb",(Element) => {
Element.style.color = "blue"
})
app.mount('#app')

自定义指令的钩子函数

image-20250306214800105

image-20250306214903950

image-20250306214921379

参数

image-20250306215347379

eg.模拟v-show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<p v-myShow="true">11</p>
</template>

<script setup>
import {ref} from 'vue'
const flag = ref(true)
const vMyShow = {
mounted(el,binding,vNode,prevNode){
if(binding.value){
el.style.display = 'block';
}else{
el.style.display = 'none';
}
}
}
</script>