前置
-
全局安装脚手架
npm install -g @vue/cli -
创建项目
-
💥终端命令创建项目
vue create 项目名称三个选项:vue2、vue3、手动选择特性
-
终端输入命令图形化创建项目
vue ui
-
-
单文件的创建
基础
基础指令
插值表达式
<template>
<div>
<h1>插值表达式</h1>
<!-- 1. 基础使用:直接渲染指定变量的内容,💥数据必须先定义好 -->
<div>{{ msg }}</div>
<!-- 2. 可以使用 api -->
<div>{{ msg.toUpperCase() }}</div>
<!-- 3. 可以字符串拼接 -->
<div>{{ msg + ' world' }}</div>
<!-- 4. 可以使用三元表达式 -->
<div>{{ msg === 'hello' ? '你好' : '不太好' }}</div>
<!-- 注意:
1. 不能在插值表达式中使用例如 for、if 等逻辑结构
2. 不要在插值表达式中使用自增、自减和单目运算符
-->
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
};
},
};
</script>
v-text
<template>
<div>
<h1>v-text</h1>
<!-- 作用:类似于插值表达式 -->
<!-- 1.直接使用 -->
<div v-text="msg"></div>
<!-- 2. 可以使用 api -->
<div v-text="msg.toUpperCase()"></div>
<!-- 3. 可以字符串拼接 -->
<div v-text="msg + ' world'"></div>
<!-- 4. 可以使用三元表达式 -->
<div v-text="msg === 'hello' ? '你好' : '不太好'"></div>
<!-- 区别:
插值表达式可以部分更新,v-text 则是全部覆盖
-->
<div v-text="text">已存在的内容</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
text: '覆盖的内容',
};
},
};
</script>
v-html
<template>
<div>
<h1>v-html</h1>
<!-- 1.直接使用 -->
<div v-html="msg"></div>
<!-- 2. 可以使用 api -->
<div v-html="msg.toUpperCase()"></div>
<!-- 3. 可以字符串拼接 -->
<div v-html="msg + ' world'"></div>
<!-- 4. 可以使用三元表达式 -->
<div v-html="msg === 'hello' ? '你好' : '不太好'"></div>
<!-- 5. 类似 v-text,替换全部内容 -->
<div v-text="text">已存在的内容</div>
<!-- 特点:可以解析 html 结构 -->
<div v-html="node"></div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
text: '覆盖的内容',
node: '<h4>后台返回的html结构</h4>',
};
},
};
</script>
v-for
<template>
<div>
<h1>v-for</h1>
<!-- key 的作用:
1. 使用 v-for 时,一定要指定 key 值,既提升性能又防止列表状态紊乱
2. key 值只能是字符串或数字类型
3. key 值必须具有唯一性
4. 建议把 id 作为 key ,因为 id 具有唯一性。不推荐 index 作为 key,因为 index 不具有唯一性,没有任何意义
-->
<!-- 遍历数组 -->
<!-- 1.完整语法 -->
<div v-for="(value, index) in arr">{{ index + ':' + value }}</div>
<!-- 2.只需要值 -->
<div v-for="value in arr">{{ value }}</div>
<!-- 3.key 的使用 -->
<div v-for="value in arr" :key="value">{{ value }}</div>
<!-- 遍历对象 -->
<!-- 1.完整语法 -->
<div v-for="(value, key, index) in obj">{{ key + ' - ' + value + ' - ' + index }}</div>
<!-- 2.只需要值 -->
<div v-for="value in obj">{{ value }}</div>
<!-- 3.key 的使用 -->
<div v-for="(value, id) in obj" :key="id">{{ value }}</div>
<!-- 遍历对象数组 -->
<!-- 1.基础使用 -->
<div v-for="item in list">
<div>{{ item.name + item.age }}</div>
</div>
<!-- 2.key 的使用 -->
<div v-for="item in list" :key="item.id">
<div>{{ item.name + item.age }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4, 5],
obj: {
name: '张三',
age: 43,
job: '律师',
id: '0',
},
list: [
{
name: '小明',
age: '20',
id: 1,
},
{
name: '小白',
age: '21',
id: 2,
},
{
name: '大黄',
age: '22',
id: 3,
},
],
};
},
};
</script>
v-model
<template>
<div>
<h1>v-model</h1>
<!--
作用:实现数据与元素的双向绑定
限制:只有 input、textarea、select 可以使用
使用场景:展示默认数据或收集用户数据
修饰符:
.number:把用户输入的值转化为数值类型
.trim:去除首尾空白字符
.lazy:在输入框失焦时触发并更新数据
-->
<!-- 1. 基础使用 -->
<input v-model="msg" type="text" />
<div>展示数据:{{ msg }}</div>
<!-- 2. 修饰符 .number -->
<input v-model.number="num" type="number" />
<div>展示数据:{{ num + 10 }}</div>
<!-- 3. 修饰符 .trim -->
<input v-model.trim="tri" type="text" />
<div>展示数据:{{ tri }}</div>
<!-- 4. 修饰符 .lazy -->
<input v-model.lazy="laz" type="text" />
<div>展示数据:{{ laz }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: '',
num: 1,
tri: '',
laz: '',
};
},
};
</script>
v-on
<template>
<div>
<h1>v-on</h1>
<!--
作用:为元素绑定事件,事件类型由 v-on 后面的参数决定,可以简写为 @
语法:v-on:事件类型="事件处理函数" || @事件类型="事件处理函数"
常用修饰符:
.prevent:阻止默认行为 - 例如阻止 a 标签的跳转、表单的提交
.once:事件只触发一次
.stop:阻止事件冒泡
.{keyCode | keyAlias}:特定按键触发
-->
<!-- 1. 无参事件:会有一个默认的事件源对象 -->
<button v-on:click="handle1">无参事件</button>
<!-- 简写 -->
<button @click="handle1">无参事件-简写</button>
<hr />
<!-- 2. 有参事件:
传递参数后,默认的事件源对象就不再传递了
如果还想用事件源对象则需手动传递 $event(不能更改)
-->
<button @click="handle2('参数')">有参事件</button>
<button @click="handle3('参数', $event)">有参事件+事件源对象</button>
<hr />
<!-- 修饰符 .prevent -->
<a @click.prevent="handlepre" href="http://www.baidu.com">点击不跳转页面</a><br />
<!-- 修饰符 .once -->
<button @click.once="handleonce">只触发一次事件</button><br />
<!-- 修饰符 .{keyCode | keyAlias} 回车触发:.13 | .enter -->
<input @keydown.enter="handleEnter" type="text" />
</div>
</template>
<script>
export default {
methods: {
handle1(e) {
console.log('触发1');
console.log(e);
},
handle2(e) {
console.log(e);
},
handle3(name, event) {
console.log(name, event);
},
handlepre() {
console.log('点击不跳转页面');
},
handleonce() {
console.log('只触发一次事件');
},
handleEnter() {
console.log('回车触发事件');
},
},
};
</script>
v-bind
<template>
<div>
<h1>v-bind</h1>
<!--
作用:为元素的属性动态绑定值,可以为任意属性动态帮定
语法:v-bind:属性名="变量" | :属性名='变量'
-->
<!-- 基础使用 -->
<img v-bind:src="yourSrc" alt="" />
<!-- 动态绑定样式 -->
<button @click="isColl = !isColl">点击折叠</button>
<!-- <div :class="{ box: true, collopse: isColl }"></div> -->
<!-- <div :class="['box', { collopse: isColl }]"></div> -->
<!-- 三种写法都可以,以下写法比较常用 -->
<div class="box" :class="{ collopse: isColl }"></div>
</div>
</template>
<script>
export default {
data() {
return {
yourSrc:
'图片地址',
isColl: false, // 是否折叠
};
},
};
</script>
<style lang="less">
.box {
width: 200px;
height: 200px;
background-color: pink;
transition: all 1s;
}
.collopse {
width: 100px;
}
</style>
v-show 与 v-if
<template>
<div>
<h1>v-show 和 v-if</h1>
<!-- v-show
作用:通过为元素设置 display 样式实现元素的显示与隐藏
语法:v-show="bool值"
v-if
作用:根据表达式的值有条件的渲染元素,true 则创建并渲染元素,false 则移除元素
语法:v-if='bool值' | v-if='bool值' v-else | v-if='bool值' v-else-if v-else
共同点:都是用来来控制元素的显示与隐藏
不同点:
1.实现原理不同
v-if:通过创建或移除 DOM 元素来实现显示与隐藏
v-show:通过设置元素的样式来实现显示与隐藏
2.性能消耗不同
v-if 有更高的切换开销
v-show 有更高的初始渲染开销
结论:频繁切换用 v-show,反之用 v-if
-->
<h2>v-show</h2>
<button @click="isShow = !isShow">控制元素显示与隐藏</button>
<div v-show="isShow">元素本身</div>
<hr />
<h2>v-if</h2>
<button @click="ifShow = !ifShow">控制元素显示与隐藏</button>
<div v-show="ifShow">元素本身</div>
<!-- 多条件的元素渲染 -->
<div>
<input v-model="score" type="number" />
<div v-if="score >= 90">A</div>
<div v-else-if="score >= 80">B</div>
<div v-else-if="score >= 70">C</div>
<div v-else>D</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
ifShow: true,
score: 60,
};
},
};
</script>
基础进阶
ref 的使用
<template>
<div>
<h1>ref 的使用</h1>
<!--
作用:
获取 DOM 元素和组件的引用,相当于为元素设置一个标识
ref 像元素的唯一标识,所以不要让他重复
每个 vue 组件的示例上都包含一个 $refs 对象,存储这对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象
使用方法:
1. 设置 ref 标识
2. 通过 this.$refs 可以获取所有设置了 ref 标识的元素,返回一个对象
注意:如果 ref 重名,则会被覆盖
-->
<!-- 场景:在页面一打开就自动聚焦文本框 -->
<input ref="myInput" type="text" />
</div>
</template>
<script>
export default {
// 生命周期:当组件挂载到页面完成之后触发,常用于发起请求
mounted() {
// console.log(this.$refs);
this.$refs.myInput.focus();
},
};
</script>
自定义指令
局部自定义指令
<template>
<div>
<h1>局部自定义指令</h1>
<!--
定义:通过 directives 定义局部指令
使用:v-自定义指令名称
语法: directives: {
指令名称: {
inserted(el, binding) {
},
},
},
-->
<!-- 场景:在页面一打开就自动聚焦文本框 -->
<!-- 1.无参指令 -->
<input v-myFocus type="text" />
<hr />
<!-- 2.有参指令 -->
<input v-myColor="'blue'" type="text" />
</div>
</template>
<script>
export default {
directives: {
myFocus: {
// inserted:元素绑定了指令,已经解析好了,等待渲染,只会触发一次
// el:指令所绑定的元素,可以用来直接操作 DOM
inserted(el) {
// 为元素设置聚焦功能
el.focus();
},
},
myColor: {
inserted(el, binding) {
el.style.color = binding.value;
},
},
},
};
</script>
全局自定义指令(不推荐)
了解即可,不推荐使用
-
创建全局指令
创建 utils/myDirectives.js
// 主要用于封装项目中所需要使用到的指令() /* 全局自定义指令 创建:通过 Vue.directive 创建命令,一次只能创建一个命令 语法:Vue.directive('指令名称', { inserted(el, binding) {}, }); 使用:通过 Vue 创建的指令挂载到全局,任何组件可以直接引入使用,不用暴露 */ import Vue from 'vue'; // 他是挂载到全局的,所以编译后常驻内存不被释放 Vue.directive('myColor', { inserted(el, binding) { el.style.color = binding.value; }, }); -
使用
<template> <div> <h1>使用全局指令</h1> <input v-myColor="'red'" v-myFocus type="text" /> </div> </template> <script> // 引入 全局指令 import myColor from '@/utils/myDirectives.js'; export default {}; </script>
指令封装(推荐)
-
封装指令
utils/myDirectives.js
// 指令封装 // 单独的封装一个指令,并不会挂载到全局 export const mycolor = { inserted(el, binding) { el.style.color = binding.value; }, }; export const myfocus = { inserted(el) { el.focus(); }, }; -
使用
<template> <div> <h1>指令的封装和使用</h1> <!-- 3. 使用指令 --> <input v-mycolor="'green'" type="text" /> </div> </template> <script> // 1. 引入指令 import { mycolor } from '@/utils/myDirectives.js'; export default { // 2. 注册指令:引入一个成员,当成指令来使用 // directives:可以创建局部指令,也可以注册局部指令 directives: { mycolor, }, }; </script>
过滤器
自定义局部过滤器
<template>
<div>
<h1>自定义局部过滤器</h1>
<!--
作用:可以用于一些常见的文本格式化
定义位置:filters 结构中定义
语法: filters: {
过滤器名称: function (数据源, [其他参数......]) {
return;
},
},
使用:需通过管道符 | 来使用过滤器
语法:
不传参:数据源 | 过滤器名称
传参:数据源 | 过滤器名称([参数])
参数说明:
不传参,默认传递管道符前面的数据源
传参,不影响默认数据的传递,用户自定义参数是从参数列表的第二个开始
-->
<!-- 基础使用 -->
<div>{{ msg | filterDemo }}</div>
<!-- 过滤时间格式 -->
<div>{{ mockList.time | filterTime('~') }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
mockList: { id: 2, name: 'Wechat', time: new Date() },
};
},
// 可以定义、注册过滤器
filters: {
// data:过滤器的默认参数,就是管道符前面的数据源
filterDemo: function (data) {
return 'abc';
},
filterTime: function (data, spe) {
data = new Date(data);
let y = data.getFullYear();
let m = data.getMonth() + 1;
let d = data.getDate();
return `${y}${spe}${m}${spe}${d}`;
},
},
};
</script>
自定义全局过滤器(不推荐)
了解即可,不推荐使用
-
创建全局过滤器
创建 utils/myFilters.js
/* 全局过滤器的创建 语法:Vue.filter('过滤器名称', function (源数据, [其他参数......]) { // 业务处理 return 结果 }) */ import Vue from 'vue'; Vue.filter('dateFormat', function (data, spe = '-') { console.log(data, spe); data = new Date(data); let y = data.getFullYear(); let m = data.getMonth() + 1; let d = data.getDate(); return `${y}${spe}${m}${spe}${d}`; }); -
使用
<template> <div> <h1>使用全局指令</h1> <input v-myColor="'red'" v-myFocus type="text" /> </div> </template> <script> // 引入 全局指令 import myColor from '@/utils/myDirectives.js'; export default {}; </script>
过滤器的封装(推荐)
-
封装并导出
utils/myFilters
// 封装过滤器 export const dateFormat = function (data, spe = '-') { data = new Date(data); let y = data.getFullYear(); let m = data.getMonth() + 1; let d = data.getDate(); return `${y}${spe}${m}${spe}${d}`; }; -
使用
<template> <div> <h1>过滤器的封装和使用</h1> <!-- 3. 使用 --> <div>{{ mockList.time | dateFormat('|') }}</div> </div> </template> <script> // 1. 引入 import { dateFormat } from '@/utils/myFilters.js'; export default { data() { return { mockList: { id: 2, name: 'Wechat', time: new Date() }, }; }, // 2. 注册 filters: { dateFormat, }, }; </script>
计算属性和侦听器
计算属性
<template>
<div>
<h1>计算属性</h1>
<!--
特点:只要计算属性中依赖项发生改变,就会自动触发计算属性
注意点:
必须在 computed 中定义
计算属性是一个 function
必须有返回值
使用:类似 data 中的普通属性使用
计算属性缓存与 methods 方法的差异:
计算属性是基于他们的响应式依赖进行缓存的,只有依赖项发生改变,计算属性才会重新执行,否则还是会使用上次执行结果,因为上次执行结果已经存储到缓存了
这就是与函数最本质的区别,意味着多次调用的情况下,计算属性性能更好。
-->
<input v-model="msg" type="text" />
<div>数据展示:{{ msg }}</div>
<div>计算属性处理后的数据展示:{{ getReverse }}</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
};
},
// 定义计算属性
// 此时计算属性的依赖项就是 this.msg
computed: {
getReverse() {
return this.msg.split('').reverse().join('');
},
},
};
</script>
侦听器
侦听器的特定啊(与计算属性的不同):
- 命名不能随意,必须与你想要侦听的属性名称完全一致
- 不能手动调用,他是自动触发
- 侦听器侧重于单个数据的变化,最终指定特定的业务处理,不需要有任何返回值。如果需要一般是将结果赋值给另外一个变量
- 可以侦听异步操作中数据的变化
<template>
<div>
<h1>侦听器</h1>
<!--
概念:
1. 可以监听指定的属性值的变化, 只要 属性值发生了变化, 就 会自动触发相应的侦听器
2. 比计算属性更通用,特别是在有异步操作的场景下
定义:
1. 使用 watch 结构定义
2. 定义为函数的形式,因为侦听函数不能手动调用,他是自动触发的。所以函数的名称必须和你想侦听的属性名称完全一致
参数:新值,旧值
其他选项:
1. immediate:默认情况下,组件加载完毕后不会调用 watch 侦听器。如果想侦听器立即被调用,则需使用immediate
2. deep:深度侦听,常用于侦听对象的属性的变化
-->
<input type="text" v-model="msg" />
<div>侦听器处理的结果展示:{{ res }}</div>
<hr />
<div>
<input type="text" v-model="obj.name" />
<input type="number" v-model="obj.age" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: '',
res: '',
obj: {
name: '张三',
age: 20,
},
};
},
watch: {
// 基础使用
msg(newValue, oldValue) {
// console.log('msg 侦听器触发');
// 侦听器处理的结果必须使用中间变量进行转储
this.res = this.msg.toUpperCase();
console.log(newValue, oldValue);
},
// 对象写法
msg: {
// handler:就是侦听器的侦听处理函数,对等于基础使用写法
handler() {
this.res = this.msg.toUpperCase();
},
immediate: true,
},
// 侦听对象所有属性
obj: {
handler(newValue) {
console.log(newValue);
},
deep: true,
},
// 侦听对象单个属性
'obj.age'(newValue) {
console.log(newValue);
},
},
};
</script>
nextTick
<template>
<div>
<h1>nextTick</h1>
<!--
场景目的:点击显示输入框后,显示输入框并聚焦
场景问题:
点击显示输入框后,因为使用的是 v-if ,所以模板需要创建输入框,然后再绑定 ref
但实际中,创建还未完成,就以已经开始聚焦操作,所以报错
解决:
1. 使用 nextTick 延迟到下一个任务队列
2. 或者使用 setTimeout
nextTick 使用语法:
this.$nextTick(() => {
//业务处理
});
-->
<button @click="handleClick">显示输入框</button>
<input type="text" v-if="isShow" ref="myInput" />
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
methods: {
handleClick() {
this.isShow = true;
this.$nextTick(() => {
this.$refs.myInput.focus();
});
},
},
};
</script>
实现过渡动画
使用自定义类样式实现过渡动画
了解即可,不推荐使用
<template>
<div>
<h1>使用自定义样式实现过渡动画</h1>
<!--
实现步骤:
1. 为元素添加 v-if 或 v-show
2. 必须将需要添加过渡动画的元素包裹再 transition 标签中
3. 为六个时机设置自定义动画样式。如果是使用自定义样式时,为 transition 设置 name,name值则为后期类样式的前缀
动画执行的六个时机:
1. v-enter:开始进入
2. v-enter-active:进入的过程
3. v-enter-to:进入完毕
4. v-leave:准备离开
5. v-leave-active:离开的过程
6. v-leave-to:离开完毕
-->
<button @click="isShow = !isShow">切换</button>
<transition name="move">
<p v-show="isShow">展示示例</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
};
</script>
<style lang="less" scoped>
p {
width: 100px;
height: 100px;
background-color: darkturquoise;
color: white;
}
// 开始进入
.move-enter {
margin-left: 300px;
opacity: 0;
}
// 进入的过程
.move-enter-active {
transition: all 2s;
}
// 进入完毕
.move-enter-to {
margin-left: 0px;
opacity: 1;
}
// 准备离开
.move-leave {
margin-left: 0px;
opacity: 1;
}
// 离开的过程
.move-leave-active {
transition: all 2s;
}
// 离开完毕
.move-leave-to {
margin-left: 300px;
opacity: 0;
}
</style>
使用第三方动画库实现过渡动画(推荐)
-
下载
npm install animate.css --save or yarn add animate.css -
引入
全局引入:所有文件都能使用
局部引入:只有当前文件才能使用
import 'animate.css'; -
使用
<template> <div> <h1>使用第三方动画库实现过渡动画</h1> <!-- 注意:不要忘记 animate__ 前缀 --> <button @click="isShow = !isShow">切换</button> <transition enter-active-class="animate__animated animate__tada" leave-active-class="animate__animated animate__bounceOutRight"> <p v-show="isShow">第三方动画库实现过渡动画</p> </transition> </div> </template> <script> // 局部引入,在组件内部引入,只有当前组件可以使用 import 'animate.css'; export default { data() { return { isShow: false, }; }, }; </script> <style lang="less" scoped> p { width: 100px; height: 100px; background-color: darkturquoise; color: white; } </style>
组件数据传递
组件的创建和渲染
创建文件
父组件中引入、注册、使用
<template>
<div class="fa">
<!-- -->
<h1>父组件:father</h1>
<!-- 3. 使用 -->
<son></son>
<sister></sister>
</div>
</template>
<script>
// 1. 引入
import son from './components/son.vue';
import sister from './components/sister.vue';
export default {
// 2. 注册
components: {
son,
sister,
},
};
</script>
父传子
绑定传值(常用)
-
子组件
子组件定义 props 用来接收传递数据的变量,并根据需求是否添加校验
<template> <div class="son"> <!-- --> <h1>子组件:son</h1> <div>接收到父组件的数据:{{ myMoney }}</div> </div> </template> <script> export default { /** * props:用来接收父组件所传递的数据 * props定义: * 1. 数组类型:数组中的成员就相当于 data 中定义的成员,他是变量的名称 * 缺点:无法为每个 prop 指定具体的数据类型,也无法进行相应的校验 * 2. 对象类型:通过对象的方式定义 props 成员,可以为每个 prop 成员指定规则(类型,校验...) * 常用有: * 1. 基础的类型检查 * 2. 多个可能的类型 * 3. 必填项校验 * 4. 属性默认值 * 5. 自定义校验函数 * 注意:必填校验 与 默认值 选其一,避免同时使用 */ // 数组写法 // props: ['myMoney'], // 对象写法 props: { // 指定单一类型 // myMoney: Number, // 多种可能的类型 // myMoney: [Number, String], // 同时设置多个规则 myMoney: { // 规定类型 type: [Number, String], // 是否必传 // required: true, // 默认值 default: 100, // 自定义校验 validator(value) { if (value < 1000) return false; return true; }, }, }, }; </script> -
父组件
使用 v-bind 绑定子组件中用来接收数据的变量进行传值
<template> <div > <h1>父组件:father</h1> <input type="number" v-model="money" /> <!-- 1. 父传子 --> <son :myMoney="money"></son> </div> </template> <script> import son from './components/son.vue'; export default { components: { son, }, data() { return { money: 10000, }; }, }; </script>
provide(少用)
-
父组件
<template> <div > <h1>父组件</h1> </div> </template> <script> import son from './components/son.vue'; export default { components: { son }, data() { return { provideValue: '祖传数据', }; }, /** * 父节点可以通过 provide 方法对其子孙组件共享数据 */ provide() { return { provideValue: this.provideValue, }; }, }; </script> -
子组件
<template> <div > <h1>子组件</h1> <div>{{ provideValue }}</div> </div> </template> <script> export default { /** * inject:用于接收父级通过 provide 共享的数据 */ inject: ['provideValue'], }; </script>
子传父
-
子组件
通过事件来传递数据
事件中使用 this.$emit 方法传递自定义函数名称和数据
<template> <div> <h1>子组件:son</h1> <!-- 1. 通过事件来传递数据 --> <button @click="handelEmit">点击子传父</button> </div> </template> <script> export default { data() { return { msg: '子组件传递给父组件的数据', }; }, methods: { handelEmit() { /** * 通过内置的方法 $emit 发出事件并传递数据 * 语法:this.$emit(自定义事件类型,需要传递的数据) */ // 2. 事件中使用 this.$emit 方法传递自定义函数名称和数据 this.$emit('getEmit', this.msg); }, }, }; </script> -
父组件
监听子组件传递过来的自定义事件
在监听事件中拿到传递来的数据并转储
显示在页面上或其他处理
<template> <div> <h1>父组件:father</h1> <!-- 3. 监听子组件传递过来的自定义事件 --> <son @getEmit="handelEmit"></son> </div> </template> <script> import son from './components/son.vue'; export default { components: { son, }, data() { return { emitData: '', }; }, methods: { // 4. 在监听事件中拿到传递来的数据并转储 handelEmit(data) { console.log(data); this.emitData = data; }, }, }; </script>
兄弟组件传递
eventBus
缺点:需要多 new 一个 vue 实例出来,性能开销大,一般不用
了解即可
-
创建 utils/eventBus.js
import Vue from 'vue'; // 暴露默认成员 = vue 实例 export default new Vue(); -
兄弟组件1
引入事件总线
通过事件传递
在事件中通过 事件总线传递 自定义事件 和 数据
<template> <div> <!-- 2. 通过事件传值 --> <button @click="brother">兄弟组件传值</button> </div> </template> <script> // 1. 引入事件总线 import eventBus from '@/utils/eventBus.js'; export default { data() { return { bMsg: '兄弟组件传值的数据', }; }, methods: { // 3. 通过 事件总线传递 自定义事件 和 数据 brother() { eventBus.$emit('brotherEmit', this.bMsg); }, }, }; </script>兄弟组件2
引入事件总线
在 mounted 或 created 中使用 事件总线.$on 监听传递来的自定义事件
<template> <div> <div>接收兄弟传来的数据:{{ bData }}</div> </div> </template> <script> // 4. 引入事件总线 import eventBus from '@/utils/eventBus.js'; export default { // 5. 在 mounted 中添加事件的监听,也可以在 created 中监听 mounted() { eventBus.$on('brotherEmit', data => { console.log(data); this.bData = data; }); }, data() { return { bData: '', }; }, }; </script>
组件封装
动态组件
- 创建组件
-
parent
<template> <div> <h1>动态组件</h1> <!-- 添加动态组件 - component 相当于一个展示指定组件的容器 is 就是当前组件所需要渲染的组件名称 --> <button @click="comName = 'component1'">显示组件1</button> <button @click="comName = 'component2'">显示组件2</button> <button @click="comName = 'component3'">显示组件3</button> <!-- keep-alive 可以在动态组件切换后也能保留当前组件的状态 --> <keep-alive> <component :is="comName"></component> </keep-alive> </div> </template> <script> import component1 from './components/component1.vue'; import component2 from './components/component2.vue'; import component3 from './components/component3.vue'; export default { components: { component1, component2, component3, }, data() { return { comName: 'component1', }; }, }; </script> -
component2.vue
<template> <div> <h2>组件2</h2> <hr /> <button @click="count++">+1</button> <div>{{ count }}</div> </div> </template> <script> export default { data() { return { count: 0, }; }, }; </script>
插槽
匿名插槽
-
index.vue
<template> <div> <h1>匿名插槽的使用</h1> <hr /> <nimingBtn>登录</nimingBtn> </div> </template> <script> import nimingBtn from './components/nimingBtn.vue'; export default { components: { nimingBtn }, }; </script> -
匿名插槽
<template> <div class="btn"> <!-- 定义:没有设置 name 属性即位匿名插槽 用户输入的值会自动填入到 slot 中 匿名只能有一个 slot,多个 slot 会导致 用户输入一个值所有 slot 都自动填入 --> <slot>匿名插槽默认值</slot> </div> </template> <script> export default {}; </script> <style lang="less" scoped> .btn { width: 100%; height: 50px; border-radius: 20px; background-color: cornflowerblue; color: white; display: flex; justify-content: center; align-items: center; } </style>
具名插槽
-
index.vue
<template> <div> <h1>具名插槽的使用</h1> <hr /> <jumingHeader backgroundColor="pink"> <!-- 1. slot="插槽的名称" --> <span slot="center">首页</span> <!-- 2. v-slot:"插槽的名称",需使用 template 标签包住,固定写法 --> <template v-slot:left> <span>返回</span> </template> <!-- 2.1 #插槽的名称 简写方式 --> <template #right> <span>首页</span> </template> </jumingHeader> </div> </template> <script> import jumingHeader from './components/jumingHeader.vue'; export default { components: { jumingHeader }, }; </script> -
具名插槽
<template> <!-- 具名插槽: 封装组件时需要预留多个插槽节点,则需要为每个 slot 指定具体的 name 名称 带有具体名称的插槽就是具名插槽 注意:没有指定 name 名称的插槽会有一个隐藏的名称 - default --> <div class="header" :style="'backgroundColor:' + backgroundColor"> <div> <slot name="left"></slot> </div> <div> <slot name="center"></slot> </div> <div> <slot name="right"></slot> </div> </div> </template> <script> export default { props: { backgroundColor: { type: String, default: '#ccc', }, }, }; </script> <style lang="less" scoped> .header { width: 100%; height: 50px; display: flex; justify-content: space-between; align-items: center; } </style>
作用域插槽
-
index.vue
#list 是 v-slot:list 的简写,绑定的是 插槽的名称
scope 是传递过来的数据都会包装在内,接收的是数据
<template> <div> <h1>作用域插槽的使用</h1> <zuoyognyu> <!-- 接收数据对应 传递数据时的名称 row length --> <!-- 数据传递过来时是个对象, 默认用 scope 接收,使用时也是对象的使用写法 --> <!-- <template #list="scope"> {{ scope.row }} --- s{{ scope.length }} </template> --> <!-- 直接解构的使用写法,更明了 --> <template #list="{ row, length }"> {{ row }} --- s{{ length }} </template> </zuoyognyu> </div> </template> <script> import zuoyognyu from './components/zuoyongyu.vue'; export default { components: { zuoyognyu }, }; </script> -
作用域插槽
<template> <div> <button>获取数据</button> <!-- 定义作用域插槽 为插槽添加数据属性 --> <!-- 此处 row 和 length 都是 传递的数据名称,接收时也需对应 --> <slot name="list" :row="userList" :length="userList.length"></slot> </div> </template> <script> export default { data() { return { userList: [], }; }, created() { this.userList = [ { name: '张三', age: 14 }, { name: '李四', age: 20 }, ]; }, }; </script>
路由
基本实现
-
下包
npm i vue-router@3.5.2 -
创建模块文件 - src/router/router.js
// 当前项目的路由模块文件 // 1. 工程化 - 模块化 import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); // 引入需要映射的组件 import Login from '@/components/路由/demo/login.vue'; import Index from '@/components/路由/demo/index.vue'; // 2. 创建路由对象 const router = new VueRouter({ /** * 添加路由配置 * 通过 routes 配置路由,主要描述地址和组件的映射关系 * */ routes: [ { path: '/login', component: Login, }, { path: '/index', component: Index, }, ], }); // 3. 暴露 export default router; -
main.js 中引入并注册路由模块
import Vue from 'vue'; import App from './App.vue'; // 1. 引入路由模块 import router from './router/router'; Vue.config.productionTip = false; new Vue({ // 2. 注入路由模块 router, render: h => h(App), }).$mount('#app'); -
在 根组件 添加 router-view
<template> <div id="app"> <!-- 映射组件的展示区域 --> <router-view></router-view> </div> </template>
设置超链接 - router-link
都要看到的,可在根组件 App.vue 中添加,页面独有的需在相对页面中添加
-
App.vue
<template> <div id="app"> <router-link to="/home">首页</router-link> <router-link to="/login">登录页</router-link> <!-- 映射组件的展示区域 --> <router-view></router-view> </div> </template>
延迟加载 - () => import()
-
router.js
// 当前项目的路由模块文件 // 1. 工程化 - 模块化 import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); // 弊端:不管有没有用到,都会加载 // import Login from '@/components/login.vue'; // import Home from '@/components/home.vue'; // 2. 创建路由对象 const router = new VueRouter({ /** * 添加路由配置 * 通过 routes 配置路由,主要描述地址和组件的映射关系 * */ routes: [ { path: '/login', component: () => import('@/components/login.vue'), }, { path: '/home', component: () => import('@/components/home.vue'), }, ], }); // 3. 暴露 export default router;
动态路由匹配
设置动态路由
-
router/router.js
const router = new VueRouter({ routes: [ { // 设置路由的动态参数 参数以 : 作为标识 , name 为形参名字 // 以下配置说明当前路由需要传递一个参数,如果没有参数路由则无法匹配 path: '/login/:name', component: () => import('@/components/login.vue'), }, ], }); -
App.vue
<template> <div id="app"> <!-- 设置路由参数 --> <router-link to="/login/login">登录页</router-link> <!-- 映射组件的展示区域 --> <router-view></router-view> </div> </template>
获取路由参数
参数传给了哪个组件,就在哪个组件中获取参数
mounted 和 watch 的触发时机不一样,存在互补的功能现象
方法一 - 通过 $route.params 获取路由参数
-
login.vue
<script> export default { /** 获取路由参数 * 通过 $route.params 获取路由参数,获取到的是一个对象 * */ // 从一个路由跳转到另一个路由时触发 mounted() { const name = this.$route.params.name; console.log(name); }, // 通过 watch 监听路由参数的变化 // 当同一个路由参数变化时触发 watch: { $route(to, from) { // 对路由的变化做出响应 console.log(to, from); }, }, }; </script>
方法二 - 通过 props 来获取路由参数
-
router/router.js
{ path: '/login/:name', component: () => import('@/components/login.vue'), // 开启 porps 传参 props: true, }, -
login.vue
<template> <div> 登录页 <h1>{{ name }}</h1> </div> </template> <script> export default { // 接口路由参数 props: ['name'], </script>
嵌套路由
-
router/router.js
const router = new VueRouter({ routes: [ { path: '/login/:name', component: () => import('@/components/login.vue'), props: true, /** 添加嵌套路由 * 里面的路由配置类似外层路由配置 * 根路由就是带 / 的路由,它会被渲染在 App.vue 中的 router-view 结构中 * */ children: [ { path: 'userInfo', component: () => import('@/components/userInfo.vue'), }, ], }, ], }); -
login.vue
<template> <div> <!-- 嵌套路由的映射区域 --> <router-view></router-view> </div> </template>
编程式导航
跳转页面并传参 - $router.push
-
跳转页面
methods: { jump() { // 跳转根路由 // this.$router.push('/home'); // 有参数的情况下跳转嵌套路由 // this.$router.push(`/login/${this.name}/userInfo`); // 完整写法 this.$router.push({ path: `/login/${this.name}/userInfo` }); }, }, -
传参
jump() { /** 传递参数 * 使用 path 跳转时,参数需用 query 传递 * 使用 name 路由名称跳转时,params 和 query 都可以,推荐都是用 query * */ // this.$router.push({ // path: `/login/${this.name}/userInfo`, // query: { username: '张三' }, // }); this.$router.push({ name: 'userInfo', query: { name: '张三', age: 50 }, }); }, -
后退
goBack() { // go() 方法 传负数是 后退,0 或不传 为 刷新页面 // this.$router.go(-1); // back() 也是后退方法 this.$router.back(); },
命名路由
-
router/router.js
{ path: '/login/:name', component: () => import('@/components/login.vue'), props: true, children: [ { // 配置路由名称 - 需保证名称唯一性,不可重复 name: 'userInfo', path: 'userInfo', component: () => import('@/components/userInfo.vue'), }, ], -
跳转路由时使用
jump() { this.$router.push({ name: 'userInfo', query: { name: '张三', age: 50 }, }); },
路由重定向
-
router/router.js
const router = new VueRouter({ routes: [ // 路由重定向 // 可以设置与 path 相同的路径 { path: '/', // redirect: '/home', redirect: { name: 'home' }, }, { path: '/login/:name', component: () => import('@/components/login.vue'), props: true, // 嵌套路由中 使用 name 更方便 redirect: { name: 'userInfo' }, children: [ { name: 'userInfo', path: 'userInfo', component: () => import('@/components/userInfo.vue'), }, ], }, ], });
路由高亮
方法一:使用默认的高亮 class 类 - router-link-active
-
styles/index.css 并 引入 main.js
// 添加全局的路由高亮显示 .router-link-active { font-size: 20px; color: cadetblue; }
方法二:自定义路由高亮的 class 类
-
router/router.js
const router = new VueRouter({ // 自定义路由高亮类名称 linkActiveClass: 'my-router-link-active', }); -
styles/index.css
.my-router-link-active { color: turquoise; font-size: 22px; font-weight: 700; }
导航前置守卫
-
router/router.js
const router = new VueRouter({ ... }) /** 导航前置守卫 * to:跳转的目标路由 * from:源路由 * next:下一步,如果没有 next,那么请求终止 * */ router.beforeEach((to, from, next) => { // 业务逻辑 const token = ''; if (!token) { next({ path: '/login' }); } else { next(); } }); // 3. 暴露 export default router;\