VUE2
插值表达式
<div id="app">{{nickname}}</div>
<script src="https://"> </script>
<script>
const app= new Vue({
el:'#app',
nickname:'tony'
})
</script>
注意:插值表达式不具备解析标签的能力
这时候需要用到:
v-html
<div id="app">
<div v-html="msg"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
msg: '<a>jshjhdj\\\\\ </a>'
}
})
v-show v-if v-bind v-on
v-for
v-for 一定要加key,不然会默认原地修改,删除不了想删除的标签样式,只是改了内容而已
v-model
v-model应用于其他表单属性
小例子
<div id="app">
<h3>小黑学习网</h3>
姓名:
<input type="text" v-model="username">
<br><br>
是否单身:
<input type="checkbox" v-model="isSingle">
<br><br>
<!--
前置理解:
1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
结合 Vue 使用 → v-model
-->
性别:
<input v-model="gender" type="radio" name="gender" value="1">男
<input v-model="gender" type="radio" name="gender" value="2">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
2. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
所在城市:
<select v-model="cityId">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">成都</option>
<option value="104">南京</option>
</select>
<br><br>
自我描述:
<textarea v-model="desc"></textarea>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
isSingle: false,
gender: "2",
cityId: '102',
desc: ""
}
})
</script>
原理详解:
表单类组件封装 v-model简化代码
v-model直接写法
v-model原理写法
<template>
<div class="app">
<input type="text" v-model="msg1" />
<br />
<!-- v-model的底层其实就是:value和 @input的简写 -->
<input type="text" :value="msg2" @input="msg2 = $event.target.value" />
</div>
</template>
<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
}
</script>
<style>
</style>
methods
小黑记事本案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/index.css" />
<title>记事本</title>
</head>
<body>
<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input v-model="todoName" placeholder="请输入任务" class="new-todo" />
<button @click="add" class="add">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item, index) in list" :key="item.id">
<div class="view">
<span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
<button @click="del(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 → 如果没有任务了,底部隐藏掉 → v-show -->
<footer class="footer" v-show="list.length > 0">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
<!-- 清空 -->
<button @click="clear" class="clear-completed">
清空任务
</button>
</footer>
</section>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 添加功能
// 1. 通过 v-model 绑定 输入框 → 实时获取表单元素的内容
// 2. 点击按钮,进行新增,往数组最前面加 unshift
const app = new Vue({
el: '#app',
data: {
todoName: '',
list: [
{ id: 1, name: '跑步一公里' },
{ id: 2, name: '跳绳200个' },
{ id: 3, name: '游泳100米' },
]
},
methods: {
del (id) {
// console.log(id) => filter 保留所有不等于该 id 的项
this.list = this.list.filter(item => item.id !== id)
},
add () {
if (this.todoName.trim() === '') {
alert('请输入任务名称')
return
}
this.list.unshift({
id: +new Date(),
name: this.todoName
})
this.todoName = ''
},
clear () {
this.list = []
}
}
})
</script>
</body>
</html>
指令修饰符
@keyup.enter
@keyup.enter → 监听键盘回车事件 类比原生JS中的 if (e.key === 'Enter') 这里把指这个方法直接封装了
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input @keyup.enter="fn" v-model="username" type="text">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
fn (e) {
// if (e.key === 'Enter') {
// console.log('键盘回车的时候触发', this.username)
// }
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>
v-model.trim .number @事件名.stop(.prevent)
<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model.trim="username" type="text"><br>
//去掉的是首位空格
年纪:<input v-model.number="age" type="text"><br>
<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click.stop="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn (e) {
// e.stopPropagation()
alert('儿子被点击了')
}
}
})
</script>
v-bind 对于样式控制的增强
:class={pink:true , big :true }
:class=['pink','big']
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
border: 3px solid #000;
font-size: 30px;
margin-top: 10px;
}
.pink {
background-color: pink;
}
.big {
width: 300px;
height: 300px;
}
</style>
</head>
<body>
<div id="app">
<div class="box" :class="{ pink: true, big: true }">黑马程序员</div>
<div class="box" :class="['pink', 'big']">黑马程序员</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
案例tab高亮
<div id="app">
<ul>
<li v-for="(item,index) in list" :key="item.id" @click="activeIndex = index">
<a :class="{active:index===activeIndex}" href="#">{{ item.name }}</a>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeIndex: 2, // 记录高亮
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
: style
<div id="app" >
<div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
案例:进度条
<div id="app">
<!-- 外层盒子底色 (黑色) -->
<div class="progress">
<!-- 内层盒子 - 进度(蓝色) -->
<div class="inner" :style="{ width: percent + '%' }">
<span>{{ percent }}%</span>
</div>
</div>
<button @click="percent = 25">设置25%</button>
<button @click="percent = 50">设置50%</button>
<button @click="percent = 75">设置75%</button>
<button @click="percent = 100">设置100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
percent: 30
}
})
</script>
计算属性:computed
computed完整写法
<body>
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span>{{ fullName }}</span><br><br>
<button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备',
},
methods: {
changeName () {
this.fullName = '黄忠'
}
},
computed: {
// 简写 → 获取,没有配置设置的逻辑
// fullName () {
// return this.firstName + this.lastName
// }
// 完整写法 → 获取 + 设置
fullName: {
// (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)
// 会将返回值作为,求值的结果
get () {
return this.firstName + this.lastName
},
// (2) 当fullName计算属性,被修改赋值时,执行set
// 修改的值,传递给set方法的形参
set (value) {
// console.log(value.slice(0, 1))
// console.log(value.slice(1))
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
}
})
</script>
</body>
案例:成绩表
<body>
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length > 0">
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.subject }}</td>
<!-- 需求:不及格的标红, < 60 分, 加上 red 类 -->
<td :class="{ red: item.score < 60 }">{{ item.score }}</td>
<td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{ totalScore }}</span>
<span style="margin-left: 50px">平均分:{{ averageScore }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input
type="text"
placeholder="请输入科目"
v-model.trim="subject"
/>
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input
type="text"
placeholder="请输入分数"
v-model.number="score"
/>
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button @click="add" class="submit" >添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 62 },
{ id: 7, subject: '数学', score: 89 },
{ id: 12, subject: '英语', score: 70 },
],
subject: '',
score: ''
},
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0)
},
averageScore () {
if (this.list.length === 0) {
return 0
}
return (this.totalScore / this.list.length).toFixed(2)
}
},
methods: {
del (id) {
// console.log(id)
this.list = this.list.filter(item => item.id !== id)
},
add () {
if (!this.subject) {
alert('请输入科目')
return
}
if (typeof this.score !== 'number') {
alert('请输入正确的成绩')
return
}
this.list.unshift({
id: +new Date(),
subject: this.subject,
score: this.score
})
this.subject = ''
this.score = ''
}
}
})
</script>
</body>
watch 侦听器
watch: {
// 该方法会在数据变化时调用执行
// newValue新值, oldValue老值(一般不用)
// words (newValue) {
// console.log('变化了', newValue)
// }
'obj.words' (newValue) {
// console.log('变化了', newValue)
// 防抖: 延迟执行 → 干啥事先等一等,延迟一会,一段时间内没有再次触发,才执行
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: {
words: newValue
}
})
this.result = res.data.data
console.log(res.data.data)
}, 300)
}
}
})
watch完整写法
deep: true, // 深度监视
immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
const app = new Vue({
el: '#app',
data: {
obj: {
words: '小黑',
lang: 'italy'
},
result: '', // 翻译结果
},
watch: {
obj: {
deep: true, // 深度监视
immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
handler (newValue) {
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: newValue
})
this.result = res.data.data
console.log(res.data.data)
}, 300)
}
}
水果购物车案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/inputnumber.css" />
<link rel="stylesheet" href="./css/index.css" />
<title>购物车</title>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
<!-- 面包屑 -->
<div class="breadcrumb">
<span>🏠</span>
/
<span>购物车</span>
</div>
<!-- 购物车主体 -->
<div class="main" v-if="fruitList.length > 0">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<div class="th">选中</div>
<div class="th th-pic">图片</div>
<div class="th">单价</div>
<div class="th num-th">个数</div>
<div class="th">小计</div>
<div class="th">操作</div>
</div>
</div>
<!-- 身体 -->
<div class="tbody">
<div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }">
<div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
<div class="td"><img :src="item.icon" alt="" /></div>
<div class="td">{{ item.price }}</div>
<div class="td">
<div class="my-input-number">
<button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button>
<span class="my-input__inner">{{ item.num }}</span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td">{{ item.num * item.price }}</div>
<div class="td"><button @click="del(item.id)">删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="isAll"/>
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price">{{ totalPrice }}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{ totalCount }} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const defaultArr = [
{
id: 1,
icon: 'http://autumnfish.cn/static/火龙果.png',
isChecked: true,
num: 2,
price: 6,
},
{
id: 2,
icon: 'http://autumnfish.cn/static/荔枝.png',
isChecked: false,
num: 7,
price: 20,
},
{
id: 3,
icon: 'http://autumnfish.cn/static/榴莲.png',
isChecked: false,
num: 3,
price: 40,
},
{
id: 4,
icon: 'http://autumnfish.cn/static/鸭梨.png',
isChecked: true,
num: 10,
price: 3,
},
{
id: 5,
icon: 'http://autumnfish.cn/static/樱桃.png',
isChecked: false,
num: 20,
price: 34,
},
]
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,
},
computed: {
// 默认计算属性:只能获取不能设置,要设置需要写完整写法
// isAll () {
// // 必须所有的小选框都选中,全选按钮才选中 → every
// return this.fruitList.every(item => item.isChecked)
// }
// 完整写法 = get + set
isAll: {
get () {
return this.fruitList.every(item => item.isChecked)
},
set (value) {
// 基于拿到的布尔值,要让所有的小选框 同步状态
this.fruitList.forEach(item => item.isChecked = value)
}
},
// 统计选中的总数 reduce
totalCount () {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
// 选中 → 需要累加
return sum + item.num
} else {
// 没选中 → 不需要累加
return sum
}
}, 0)
},
// 总计选中的总价 num * price
totalPrice () {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
return sum + item.num * item.price
} else {
return sum
}
}, 0)
}
},
methods: {
del (id) {
this.fruitList = this.fruitList.filter(item => item.id !== id)
},
add (id) {
// 1. 根据 id 找到数组中的对应项 → find
const fruit = this.fruitList.find(item => item.id === id)
// 2. 操作 num 数量
fruit.num++
},
sub (id) {
// 1. 根据 id 找到数组中的对应项 → find
const fruit = this.fruitList.find(item => item.id === id)
// 2. 操作 num 数量
fruit.num--
}
},
watch: {
fruitList: {
deep: true,
handler (newValue) {
// 需要将变化后的 newValue 存入本地 (转JSON)
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>
VUE的生命周期
四个周期: 创建 挂载 更新 销毁
<div id="app">
<h3>{{ title }}</h3>
<div>
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count: 100,
title: '计数器'
},
// 1. 创建阶段(准备数据)
beforeCreate () {
console.log('beforeCreate 响应式数据准备好之前', this.count)
},
created () {
console.log('created 响应式数据准备好之后', this.count)
// this.数据名 = 请求回来的数据
// 可以开始发送初始化渲染的请求了
},
// 2. 挂载阶段(渲染模板)
beforeMount () {
console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
},
mounted () {
console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
// 可以开始操作dom了
},
// 3. 更新阶段(修改数据 → 更新视图)
beforeUpdate () {
console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
},
updated () {
console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
},
// 4. 卸载阶段
beforeDestroy () {
console.log('beforeDestroy, 卸载前')
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
},
destroyed () {
console.log('destroyed,卸载后')
}
})
</script>
created()应用
一进页面就获取到信息数据
<script>
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
const app = new Vue({
el: '#app',
data: {
list: []
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
}
})
</script>
工程化开发和脚手架
局部注册与全局注册
通用的组件注册为全局,在main.js中
scoped
data必须是一个函数
<template>
<div class="base-count">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
// data() {
// console.log('函数执行了')
// return {
// count: 100,
// }
// },
data: function () {
return {
count: 100,
}
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
组件关系
组件通信解决方案:
//父子关系:props,$emit
//非父子关系:provide&inject , eventbus
//通用解决方案:Vuex(适合复杂业务场景)
什么是props
定义:组件上注册的一些自定义属性 作用:向子组件传递数据 可以传递任意数量以及任意类型
props 类型校验
data和prop的区别
单向数据流
非父子通信
event bus
事件总线----- 简单数据传递
///在util文件夹中创建
import Vue from 'vue'
const Bus = new Vue()
export default Bus
provide&inject
作用:跨层级共享数据
.sync修饰符
可以自定义属性名字,更方便阅读
ref和refs
问题: 在VUE中,因为用document.querySelector()获取元素是获取整个页面的元素,这种情况下可能会获取到其他组件中的节点,而添加ref属性可以有目的性的获取我们需要的节点(而且跨组件也可以)
给想获取的盒子或组件添加ref=“ base” ; this.$refs.base获取到该节点或组件 如果是在子组件内添加ref,那么父组件内可以用子组件中的方法
vue的异步更新,$nextTick
异步更新:
<script setup>
import { ref } from "vue";
const count = ref(0);
const increment = () => {
count.value += 1; // 这里状态改变了
console.log(count.value); // 这里的 count 可能没有立即更新
};
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
🔹 问题:为什么 console.log(count.value) 可能不会立即更新?
➡️ Vue 采用异步批量更新,它会等 所有的状态变更完成后再一起更新 DOM。这样可以优化性能,减少不必要的 DOM 重新渲染。
自定义指令
以焦点为例
el是指绑定的元素 下列代码中打印el是
<input>标签
还有一种局部注册方法
指令的值
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'orange'
}
},
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时的逻辑
inserted (el, binding) {
// console.log(el, binding.value);
// binding.value 就是指令的值
el.style.color = binding.value
},
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update (el, binding) {
console.log('指令的值修改了');
el.style.color = binding.value
}
}
}
}
</script>
<style>
</style>
v-loading 指令封装
场景: 实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 =>用户体验不好
需求: 封装一个 v-loading 指令,实现加载中的效果
本质就是一个蒙层,当页面加载完成再把蒙层移除就可以了
<template>
<div class="main">
<div class="box" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
<div class="box2" v-loading="isLoading2"></div>
</div>
</template>
<script>
// 安装axios => yarn add axios
import axios from 'axios'
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading: true,
isLoading2: true
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
this.isLoading = false
}, 2000)
},
directives: {
loading: {
inserted (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
}
</script>
<style>
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}
</style>
插槽
分类:
如何设置默认内容(后备内容)---默认值
具名插槽
需要定制化插槽
作用域插槽(插槽的传参语法)
单页面应用程序SPA
要用到路由 页面按需更新 明确访问路径和组件的关系
路由
指的是路由和组件之间的映射关系
VueRouter
views目录和components目录的区别
router-link
/*
router-link-active 模糊匹配(更多)
to="/find" => 地址栏 /find /find/one /find/two ...
router-link-exact-active 精确匹配
to="/find" => 地址栏 /find
*/
.footer_wrap a.router-link-active {
background-color: purple;
}
如何定制类名,自定义
const router = new VueRouter({
// routes 路由规则们
// route 一条路由规则 { path: 路径, component: 组件 }
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
],
// link自定义高亮类名
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})
//
.footer_wrap a.active {
background-color: purple;
}
声明式导航----跳转传参
声明式导航指的就是 router-link
查询参数传参
适合多个参数
动态路由 传参
适合单个参数
动态路由参数可选符
性能优化:空白
组件缓存keep-alive
可以用v-if=“article.id”有数据再渲染,或者用loading
注意
自定义创建项目
vuex
1.
store
辅助函数
mutations--用于修改
上线时把strict取消
actions
getters
modules
按功能分类store里面的数据 vuex单一状态树
vant组件库
全部导入,按需导入 封装成 vant-ui.js
移动端vw适配
toast(vant)
封装api
request.js
响应拦截器
将token存入vuex
storage--vuex持久化处理
添加请求loading
mixins
复用
全局前置导航守卫
打包上线
脚手架不参与上线
路由懒加载
提高页面加载的性能
VUE3
pinia
响应拦截器
401:权限不足或token过期
?.的语法
可选链(Optional Chaining) 语法,属于 ES2020(ES11) 的特性
if (err.response.status === 401) { // ❌ 如果 response 是 undefined,会报错!
console.log("未授权,重定向到登录页面");
}
报错信息:
Uncaught TypeError: Cannot read properties of undefined (reading 'status')
if (err.response?.status === 401) {
console.log("未授权,重定向到登录页面");
}
如果 err.response 不存在,则 err.response?.status 直接返回 undefined,不会报错。
let user = { profile: { name: "Alice" } };
console.log(user.profile?.name); // ✅ "Alice"
console.log(user.profile?.age); // ✅ undefined(不会报错)
console.log(user.contact?.email); // ✅ undefined(不会报错)