Vue速成手册
0 前言
本文使用的是Vue2.6.14
C:\Users\z004abwh>node -v
v16.14.2
C:\Users\z004abwh>npm -v
8.5.0
-
IDE
- Vscode
- 插件Vetur、ES6、HTML和CSS等基础插件
-
调试工具 vue devtools 两种安装方式:
-
第一种:github下载zip包, https://github.com/vuejs/devtools#vue-devtools
最新版本的调试工具要用yarn命令 npm install -g yarn 解压从github上下载的vue-devtools,进入目录 执行:yarn install 执行:yarn run build 最后把打包的结果导入到浏览器的扩展程序中 -
第二种:直接在一个空目录中使用npm i vue-devtools(亲测可用) 在vue-devtools文件夹下:shells>chrome>manifest.json,将配置里的persistent的值修改为true; 打开chrome,进入扩展程序管理界面(chrome://extensions/),加载已解压的扩展程序。选择vue-devtools>shells>chrome。加载完成就会出现以下的Vue.js devtools扩展程序。然后重启浏览器,再打开vue项目页面就可以在chrome的Devtools(就是console的那一栏,一般都排在最后面的。)找到Vue面板了。
1 基础篇
项目的目录结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzZauMtX-1663494649201)(D:\markdown\Vue速成手册.assets\image-20220720104855387.png)]
1.1 Hello world
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script src="./vue.js"></script>
<script>
<!-- 实例化一个Vue对象-->
const vm = new Vue({
el: "#app",
data: {
message: "Hello Vue 2.6.14"
}
})
</script>
</body>
</html>
浏览器调试窗口预留:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EJGdfUx-1663494649202)(D:\markdown\Vue速成手册.assets\image-20220720104954011.png)]
1.1.1 小结
- 想让Vue工作,必须要创建一个Vue实例,Vue构造函数接收一个唯一参数就是配置对象。
- app这个div内依然遵守html规范,只是扩展了一些Vue语法
- id为app这个div被称为Vue的模板
- Vue实例和容器是一一对应的
- 真实开发中,一个项目一般只会有一个Vue实例,Vue实例配合组件一起使用
- data中的数据改变,模板中使用data的位置会自动更新
1.2 模板语法
vue中的模板语法分为两大类
- 插值语法
- 用于解析标签内容
- 写法:{{ xxx }}, xxx是js表达式,可以直接读取到data中的所有区域
?
1.3 v-bind数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-bind数据绑定</title>
</head>
<body>
<div id="app">
<input type="text" v-bind:value="message"> <br><br>
<input type="text" :value="message">
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "数据绑定"
}
})
</script>
</body>
</html>
1.4 v-model双向数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-bind数据绑定</title>
</head>
<body>
<div id="app">
<input type="text" v-model:value="message"> <br><br>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "数据绑定"
}
})
</script>
</body>
</html>
1.5 el的两种写法
-
第一种写法:el做为Vue构造函数的参数对象中的一个key,其值是一个符合css选择器的一个容器 const vm = new Vue({
el: '#app'
})
-
第二种写法:先创建Vue实例,最后通过vm.$mount(‘#app’) 挂载对象 const vm = new Vue({})
vm.$mount('#app')
1.6 data的两种写法
-
对象形式 const vm = new Vue({
el: '#app',
data: {
message: "hello"
}
})
-
函数形式(在组件中必须是函数形式的) const vm = new Vue({
el: '#app',
data: function(){
return {
message: "hello world"
}
}
data(){
return {
message: "hello world"
}
}
data: ()=>{
console.log(this) # 这个this指向的是Window
return{message: 'Hello'}
}
})
由Vue管理的函数,一定不要写箭头函数,否则this指向就不再是Vue实例了
1.7 MVVM 模型
-
M-Model 指data中的数据 -
V-View 指模板 -
VM-ViewModel 视图模型,Vue实例 -
vm对象 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-knIl6gzX-1663494649202)(D:\markdown\Vue速成手册.assets\image-20220720120634191.png)] vm对象里面的属性和其原型对象上面的属性都可以在模板中直接使用。
1.8 数据代理的本质
1.8.1 JS基础知识复习
Object对象是原型链的尽头,也可以说是所有对象的超类,在Object对象上有一个defineProperty方法。下面看下这个方法的具体使用流程:
let temp = ''
let obj = {
name: '张三',
age: 18
}
Object.defineProperty(obj, 'name', {
get(){
console.log('obj的name属性被读取了')
return '小乌龟'
},
set(value){
console.log('obj的name属性被改写了')
temp = value
}
})
obj.name = '张无忌'
console.log(temp)
console.log(obj['name'])
console.log(obj.name)
let obj1 = {
name: '张三',
age: 19
}
let obj2 = {
name: '里斯',
age: 24
}
Object.defineProperty(obj1, 'name', {
get(){
return obj2.name
},
set(value){
obj2.name = value
}
})
Object.defineProperty(obj1, 'age', {
get(){
return obj2.age
},
set(value){
obj2.age = value
}
})
1.8.2 vue中数据代理的解读
vue中的数据代理是通过vm对象来代理data对象中属性的操作
使用vm对象代理data对象的好处是更方便,在模板中可以直接操作data中的数据,在vm实例中可以通过this.xxx直接获取到xxx属性。
new Vue({…})创建vm实例成功后,会把data中的值赋值到vm的_data属性下,vm.xxx本质上会调用vm._ _ data.xxx,_ data.xxx又会调用defineProperty的get()方法,进行实现使用vm对象代理data对象的行为。
- 通过object.defineProperty()把data中所有属性添加到vm上
- 为每一个添加到vm上的属性都指定一个setter和getter
- 在getter、setter内部去操作data中对象的属性
- vue将data中的数据拷贝一份到_data属性中,又将_data里面的属性提到Vue实例中,通过defineProperty实现数据代理,通过getter、setter操作属性,进而操作_data中的属性,_ _ data对data进行数据劫持,实现响应式。
1.9 处理事件
1.9.1 基础用法
-
以点击事件为例演示Vue中如何绑定事件 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绑定事件练习</title>
</head>
<body>
<div id="app">
<!-- v-on普通写法-->
<button v-on:click="test">绑定事件练习</button>
<!-- v-on简单写法-->
<button @click="test">绑定事件简单写法</button>
<!-- 参数传递@click="sendParams"等价于@click="sendParams($event)" $event保存着绑定的事件-->
<button @click="sendParams">绑定事件默认传参</button>
<!-- 如果有多个参数-->
<button @click="sendMultiParams($event, 123)">绑定事件传递多个参数</button>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'hello'
},
methods: {
test() {
alert(this.msg)
},
sendParams(e) {
console.log(e)
},
sendMultiParams(e, x) {
console.log(e)
console.log(x)
}
}
})
</script>
</body>
</html>
-
简单总结一下:
-
绑定事件的正常写法: v-on:event = “callback”; 绑定事件的简单写法:@event = “callback” -
@click=“callback” 等价于 @click=“callback($event)” ,如果回调不需要传其他参数那么前面的两种写法都可以 -
如果回调需要用到事件对象和其他参数,那必须这样写:@click=“callback($event, args)” -
回调函数定义在methods上 -
methods函数中的this指向问题 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>methods中this的指向问题</title>
</head>
<body>
<div id="app">
<button @click="getMsg">获取data中的msg</button>
<button @click="getMsg2" style="background-color: red">获取data中的msg</button>
</div>
<script src="./vue.js"></script>
<script>
msg = '全局我最大'
const vm = new Vue({
el: '#app',
data: {
msg: '嘻嘻哈哈好开心'
},
methods: {
getMsg: function () {
console.log(this)
console.log(this.msg)
},
getMsg2: () => {
console.log(this)
console.log(this.msg)
}
}
})
</script>
</body>
</html>
- 简单总结:
- methods中定义的函数最终会出现在vm对象或者组件实例对象中
- methods中的函数必须使用普通的函数定义方式,不可以使用箭头函数,如果使用箭头函数,会改变函数内this的指向
1.9.2 常用事件修饰符
- prevent 阻止默认事件
- stop 阻止事件冒泡
- once 事件只触发一次
- capture 使用事件的捕获模式
- self 只有event.target是当前操作的元素才触发事件
- passtive 事件的默认行为立即执行,无需等待事件回调执行完毕
事件描述符可以连续写
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件修饰符</title>
<script type="text/javascript" src="../js/vue.js"></script>
<style>
* {margin-top: 20px;}
.demo1 {height: 50px;background-color: skyblue;}
.box1 {padding: 5px;background-color: skyblue;}
.box2 {padding: 5px;background-color: white;}
.list {width: 200px;height: 200px;background-color: skyblue;overflow: auto;}
li {height: 100px;}
</style>
</head>
<body>
<div id="root">
<h2>欢迎来到{{ name }}学习</h2>
<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
</div>
<button @click.once="showInfo">点我提示信息</button>
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '尚硅谷'
},
methods: {
showInfo(e) {
alert('同学你好!')
},
showMsg(msg) {
console.log(msg)
},
demo() {
for (let i = 0; i < 100000; i++) {
console.log('#')
}
console.log('累坏了')
}
}
})
</script>
</body>
</html>
1.9.3 监听键盘事件
-
Vue定制的常用按键别名
- enter 回车
- delete 删除
- esc 退出
- space 空格
- up
- down
- left
- right
- tab 必须配合keydown使用
-
自定义按键别名 Vue.config.KeyCodes.自定义键名 = 键码
-
案例 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>键盘事件</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>欢迎打开{{name}}笔记</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"><br/>
<input type="text" placeholder="按下tab提示输入" @keydown.tab="showInfo"><br/>
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo"><br/>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
Vue.config.keyCodes.huiche = 13
new Vue({
el: '#root',
data: {
name: 'cess'
},
methods: {
showInfo(e) {
console.log(e.target.value)
}
},
})
</script>
</body>
</html>
1.10 计算属性
1.10.1 一个小例子引入计算属性
-
使用插值表达式实现: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性引入小例子</title>
</head>
<body>
<div id="app">
first name: <input type="text" v-model:value="firstName"/> <br>
last name: <input type="text" v-model:value="lastName"/>
<h3 style="color: red">full name: {{ firstName + '-' + lastName }}</h3>
</div>
<script src="./vue.js"></script>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
},
})
</script>
</body>
</html>
-
使用methods实现: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性引入小例子</title>
</head>
<body>
<div id="app">
first name: <input type="text" v-model:value="firstName"/> <br>
last name: <input type="text" v-model:value="lastName"/>
<h3 style="color: red">full name: {{ getFullName() }}</h3>
</div>
<script src="./vue.js"></script>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
},
methods: {
getFullName() {
console.log('@getFullName被执行了。。。')
return this.firstName + this.lastName
},
}
})
</script>
</body>
</html>
-
使用计算属性写法 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性引入小例子</title>
</head>
<body>
<div id="app">
first name: <input type="text" v-model:value="firstName"/> <br>
last name: <input type="text" v-model:value="lastName"/>
<h3 style="color: green">full name: {{ fullName }}</h3>
</div>
<script src="./vue.js"></script>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
},
computed: {
fullName: {
get() {
return this.firstName + '-' + this.lastName
},
set(value) {
let arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
fullName(){
return this.firstName + '-' + this.lastName
}
}
})
</script>
</body>
</html>
1.10.2 计算属性小结
计算属性一般用于通过已知属性计算获取到的值,例如上面的例子。姓名都是已经的属性。连接起来获取全名
计算属性的原理也是利用Object.defineProperty()方法提供的getter和setter
get函数初次读取时执行一次,数据变化时执行一次
与methods相比,计算属性有缓存机制,数据不变化就不会更新
计算属性会出现在vm实例对象上,可以直接在模板中像data中的数据一样使用
如果需要改变计算属性,并响应式的更新页面,那么需要设计set方法,如果不需要修改计算属性和其相关的属性,那么使用简写格式是最好用的。
1.11 侦听属性watch用法
watch监视属性用于监视属性的变化,当属性发生变化时,回调函数自动被调用。
监视的属性必须存在才能被监视,可以是data里面的属性也可以是计算属性
监视属性同样具有两种写法:
- 第一种是在配置对象中直接传入
- 第二种是使用vm实例的$watch函数
1.11.1 watch基本用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>watch用法</title>
</head>
<body>
<div id="app">
<h3>好好 {{ msg }}</h3>
<button @click="changeStatus">切换状态</button>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
flag: true,
msg: ''
},
methods: {
changeStatus() {
this.flag ? this.msg = '学习' : this.msg = '玩耍'
this.flag = !this.flag
}
},
watch: {
msg: {
handler(newVal, oldVal) {
console.log('msg被修改了', newVal, oldVal)
}
}
}
})
</script>
</body>
</html>
-
计算属性的简写方式
watch: {
msg(newVal, oldVal){
console.log('msg被修改了', newVal, oldVal)
}
}
-
如果想加载时就执行一次,那么需要配置immediate属性
watch: {
msg: {
immediate: true,
handler(newVal, oldVal) {
console.log('msg被修改了', newVal, oldVal)
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFuLA8wM-1663494649203)(D:\markdown\Vue速成手册.assets\image-20220721110751321.png)] 加载时候就执行了一次监听属性的回调函数,这时候未修改时候的值是undefined。
1.11.2 监视复杂对象
在js中对象和数组属于复杂对象,复杂对象的值是一个指针,指向的是堆中的地址,修改复杂对象内部的值并不会改变对象本身,所以也就不会触发监视属性。
-
监视复杂对象的错误写法 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>watch深度监视案例</title>
</head>
<body>
<div id="app">
<h1>个人信息</h1>
<input type="text" :value="person.name">
<input type="text" :value="person.age"/>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
person: {
name: '张三',
age: 12
}
},
methods: {
changeName() {
this.person.name = 'lisi'
},
changeAge() {
this.person.age = 99
},
},
watch: {
person: {
handler(newValue, oldValue) {
console.log('person被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
}
}
}
})
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju1c6FXJ-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721112249335.png)] 无论怎么点击按钮,handler回调都不会执行。 -
监视复杂对象的正确写法1 watch: {
'person.name': {
handler(newValue, oldValue) {
console.log('person.name被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
}
},
'person.age': {
handler(newValue, oldValue) {
console.log('person.age被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2Pn9EES-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721112442174.png)] -
监视复杂对象的正确写法2 watch: {
person: {
deep: true,
handler(newValue, oldValue) {
console.log('person.name被修改了' + '新值是:' + newValue.name + '老值是:' + oldValue.name)
console.log('person.age被修改了' + '新值是:' + newValue.age + '老值是:' + oldValue.age)
}
},
deep属性可以监听到复杂对象内部的数据
1.12 计算属性与侦听属性的区别
1.12.1 小实例讲解计算属性与侦听属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffnji6u6-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721114031722.png)]
-
使用计算属性实现: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性与侦听属性的用法比对</title>
</head>
<body>
<div id="app">
<input type="text" v-model:value="firstName"> <br>
<input type="text" v-model:value="lastName">
<h3>{{ fullName }}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName(){
return this.firstName + '-' + this.lastName
}
}
})
</script>
</body>
</html>
这里的代码不做过多的解释,很基础的实现过程。 -
使用侦听属性实现: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性与侦听属性的用法比对</title>
</head>
<body>
<div id="app">
<input type="text" v-model:value="firstName"> <br>
<input type="text" v-model:value="lastName">
<h3>{{ fullName }}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(newVal){
this.fullName = newVal + this.lastName
},
lastName(newVal){
this.fullName = this.firstName + newVal
}
}
})
</script>
</body>
</html>
1.12.2 更新一下需求
我现在的需求是,如果名字更新那么全名立即更新,如果是姓更新了,等一秒钟再更新全名,更新需求后发现用计算属性很难实现了
-
用侦听属性实现: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算属性与侦听属性的用法比对</title>
</head>
<body>
<div id="app">
<input type="text" v-model:value="firstName"> <br>
<input type="text" v-model:value="lastName">
<h3>{{ fullName }}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(newVal) {
setTimeout(() => {
this.fullName = newVal + this.lastName
}, 1000)
},
lastName(newVal) {
this.fullName = this.firstName + newVal
}
}
})
</script>
</body>
</html>
1.12.3 小结
-
计算属性和侦听属性的对比
-
关于this指向的小总结,极其重要
- 所有Vue管理的函数,最好写成普通函数,这样this指向的是vm或者是组件实例对象
- 所有不被Vue管理的函数,比如定时器函数,ajax回调函数,promise回调函数等,最好写成箭头函数,这样this的指向还是vm或者组件实例对象。
1.13 使用v-bind绑定样式
通过v-bind绑定任意dom元素的class属性,通过对class属性赋值
绑定样式的写法:
:class = 'xxx' // xxx可以是字符串、数组、对象
- 字符串写法用于:类名不确定、要动态获取
- 数组写法用于:要绑定多个样式,个数也不确定,名字也不确定
- 对象写法用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
也可以直接绑定dom元素的style属性
:style="[a,b]" // 其中a,b是样式对象
:style="{fontSize: xxx}" 其中xxx是动态值
示例代码:
<style>
.basic {width: 300px;height: 50px;border: 1px solid black;}
.happy {border: 3px solid red;background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg, yellow, pink, orange, yellow);}
.sad {border: 4px dashed rgb(2, 197, 2);background-color: skyblue;}
.normal {background-color: #bfa;}
.atguigu1 {background-color: yellowgreen;}
.atguigu2 {font-size: 20px;text-shadow: 2px 2px 10px red;}
.atguigu3 {border-radius: 20px;}
</style>
<div id="root">
<div class="basic" :class="mood" @click="changeMood">{{name}}</div><br/><br/>
<div class="basic" :class="classArr">{{name}}</div><br/><br/>
<div class="basic" :class="classObj">{{name}}</div><br/><br/>
<div class="basic" :style="styleObj">{{name}}</div><br/><br/>
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj: {
atguigu1: false,
atguigu2: false,
},
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [
{
fontSize: '40px',
color: 'blue',
},
{
backgroundColor: 'gray'
}
]
},
methods: {
changeMood() {
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3)
this.mood = arr[index]
}
},
})
</script>
1.14 条件渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>条件渲染</title>
</head>
<body>
<div id="app">
<h1>打分系统</h1>
<label for="number">分数:</label>
<input type="text" v-model:value="number" id="number">
<button @click="getRandomNumber">获取分数</button>
<br>
<hr>
<p>等级:</p>
<p v-if="number>=80">A</p>
<p v-else-if="number<80 && number>=60">B</p>
<p v-else-if="number<60 && number>=40">C</p>
<p v-else>D</p>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
number: 0,
},
methods: {
getRandomNumber() {
this.number = Math.round(Math.random(1, 100) * 100)
}
}
})
</script>
</body>
</html>
-
小结
-
写法 v-if = "表达式"
v-else-if = "表达式"
v-else
-
v-if 与v-show的区别 v-if 会直接操作dom元素,直接删除dom元素,v-if用于切换不频繁的场景
v-show 不会直接操作dom元素,v-show操作的是display属性,不会直接操作dom元素,用于频繁切换的场景
-
如果需要使用template标签对整块结构进行操作,那么只支持v-if,不支持v-show <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-if与v-show</title>
</head>
<body>
<div id="app">
<template v-if="isShow">
<p>静夜思</p>
<br>
<p>窗前明月光</p>
<p>疑是地上霜</p>
<p>举头望明月</p>
<p>低头思故乡</p>
</template>
<button @click="show"> 显示/隐藏 </button>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
isShow: true
},
methods: {
show(){
this.isShow = !this.isShow
}
}
})
</script>
</body>
</html>
1.15 v-for
v-for用于遍历数组、对象、字符串
-
语法: <li v-for=(item, index) of items :key="index"></li>
-
示例代码 <title>基本列表</title>
<script type="text/javascript" src="../js/vue.js"></script>
<div id="root">
<h3>人员列表(遍历数组)</h3>
<ul>
<li v-for="(p,index) of persons" :key="index">{{ p.name }}-{{ p.age }}</li>
</ul>
<h3>汽车信息(遍历对象)</h3>
<ul>e
<li v-for="(value,k) of car" :key="k">{{ k }}-{{ value }}</li>
</ul>
<h3>测试遍历字符串(用得少)</h3>
<ul>
<li v-for="(char,index) of str" :key="index">{{ char }}-{{ index }}</li>
</ul>
<h3>测试遍历指定次数(用得少)</h3>
<ul>
<li v-for="(number,index) of 5" :key="index">{{ index }}-{{ number }}</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
],
car: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
}
})
</script>
1.15.x key原理 diff算法 vm.$set() api的用法
待补充
1.16 收集表单数据
-
表单类型
- text :v-model收集value值
- radio:value值
- checkbox :
- 没有配置value属性, 那么收集的是checked属性(勾选与未勾选,是布尔值)
- 如果配置value属性:
- v-model的初始值是非数组,那么收集的是checked属性(勾选或者未勾选,布尔型)
- v-model的初始值是数组,那么收集的就是value组成的数组
-
v-model 修饰符
- lazy 失去焦点后再收集数据
- number 输入字符串转换成有效的数字
- trim 输入首尾空格过滤
-
收集表单示例 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用v-model收集表单数据</title>
</head>
<body>
<div id="root">
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
密码:<input type="password" v-model="userInfo.password"> <br/><br/>
年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
爱好:
学习<input type="checkbox" v-model="userInfo.hobby" value="study">
打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
<br/><br/>
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">成都</option>
</select>
<br/><br/>
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
<input type="checkbox" v-model="userInfo.agree">阅读并接受
<a href="https://www.yuque.com/cessstudy">《用户协议》</a>
<button>提交</button>
</form>
</div>
<script src="./vue.js"></script>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
userInfo: {
account: '',
password: '',
age: 18,
sex: 'female',
hobby: [],
city: 'beijing',
other: '',
agree: ''
}
},
methods: {
demo() {
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
</body>
</html>
1.17 自定义过滤器(vue3将移除)
对需要再模板中显示的数据进行特定格式化后再显示,适合做一些简单的逻辑
-
注册过滤器
-
使用过滤器 {{ xxx | 过滤器名 }}
v-bind:属性名 = ”xxx | 过滤器名“
过滤器可以接收额外参数,多个过滤器可以串联 并不是改变源数据,而是产生新的数据用于模板的展示 -
代码示例 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>过滤器</title>
<script type="text/javascript" src="./vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
</head>
<body>
<div id="root">
<h2>时间</h2>
<h3>当前时间戳:{{time}}</h3>
<h3>转换后时间:{{time | timeFormater()}}</h3>
<h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
<h3>截取年月日:{{time | timeFormater() | mySlice}}</h3>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
Vue.filter('mySlice',function(value){
return value.slice(0,11)
})
new Vue({
el:'#root',
data:{
time:1626750147901,
},
filters:{
timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
return dayjs(value).format(str)
}
}
})
</script>
</html>
1.18 vue内置指令小结
-
v-bind -
v-model -
v-for -
v-on -
v-show -
v-if -
v-else-if -
v-else -
v-text
- 向其所在的节点中渲染文本内容
- 与插值语法的区别,v-text会直接替换掉dom元素中的其他内容,插值表达式可以拼接,更加灵活
-
v-html
- v-html会解析html元素,可能造成xss攻击
- v-html与插值表达式的区别也是前者可以替换掉dom中的内容
- v-html与v-text的区别是前者可以解析html元素,后者不可以
-
v-cloak
-
使用css配和v-cloak可以解决网速慢时页面展示出{{ xxx }} 的问题 -
示例代码 <title>v-cloak指令</title>
<style>
[v-cloak] {
display:none;
}
</style>
<div id="root">
<h2 v-cloak>{{ name }}</h2>
</div>
// 够延迟5秒收到vue.js
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
<script type="text/javascript">
console.log(1)
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{name:'cess'}
})
</script>
-
v-once
- v-once所在的dom节点只在第一次动态渲染后,就视为静态内容了
- 用于优化性能
-
v-pre
- 跳过v-pre修饰的dom节点,不参与编译过程
- 加快编译过程
1.19 自定义Vue指令
待补充
1.20 vue生命周期与钩子函数
这是vue基础的最后一个内容,也是vue中最重要的内容。
1.20.1 小实例引入vue生命周期
需求是这样的,我想打开网页就能看到文字具有动态效果。
不使用vue周期函数的实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>练习生命周期</title>
</head>
<body>
<div id="app">
<h1 :style="{opacity: opacity}">欢迎就入魔鬼训练营</h1>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
opacity: 1
},
})
setInterval(function () {
if (vm.opacity>0) {
vm.opacity -= 0.01
} else {
vm.opacity = 1
}
}, 16)
</script>
</body>
</html>
- 缺点:
- 每次打开界面都会创建一个定时器,定时器数量会越来越多,造成浏览器卡死。
- vue外部使用vm也是不推荐的
使用vue钩子的实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>练习生命周期</title>
</head>
<body>
<div id="app">
<h1 :style="{opacity: opacity}">欢迎就入魔鬼训练营</h1>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
opacity: 1
},
mounted(){
this.interval = setInterval(()=>{
if (vm.opacity > 0) {
vm.opacity -= 0.01
} else {
vm.opacity = 1
}
}, 16)
},
beforeDestroy(){
clearInterval(this.interval)
}
})
</script>
</body>
</html>
1.20.2 小结
常用的生命周期函数:
- mounted发送ajax请求,启动定时器,绑定自定义事件,订阅消息等初始化操作
- beforeDestroy清除定时器、解绑自定义事件,取消订阅消息等收尾工作
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生dom事件依然有效
- 一般不会在beforedestroy操作数据,因为即便操作数据,也不会再触发更新流程了
2 组件化开发
2.1 理解组件化
2.1.1 概念理解
- 模块
- 在前端工程中模块一般指定一个.js文件,可以在js文件引入其他模块暴露的信息,模块是一个名词,用于描述一个单独的js文件
- 模块化
- 使用多文件js编写的项目就可以称为模块化的项目,模块化是一个形容词,用来形容项目
- 组件(用于实现局部功能的代码和资源的集合)
- 组件用于描述前端项目中的一块内容,组件具有一定的可复用性,类似于函数,可以通过传递参数的不同输出不同的结果
- 组件化
2.1.2 模块化编写项目的缺点
在没有类似vue这种前端框架之前,我们都是用模块化编写的前端项目的,模块化开发的缺点是依赖关系混乱,不好维护,代码复用性不高,具有很高的耦合性,代码冗余。
组件化编程具有低耦合,代码复用率高等特点:
2.2 非单文件组件
2.3 单文件组件
2.4 组件的基本使用
使用组件的三大步骤
1定义组件
2注册组件
3使用组件
2.4.1 定义组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>创建组件</title>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<person></person>
<hr>
<dog></dog>
</div>
<template id="cdog">
<div>
<h1>狗详细信息</h1>
<label for="dog-name"></label>
<input type="text" v-model:value="name" id="dog-name"> <br>
<label for="dog-sex"></label>
<input type="text" v-model:value="sex" id="dog-sex">
<button @click="showDogInfo">显示狗的信息</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const person = Vue.extend({
template: `
<div>
<label for="name"></label>
<input id="name" type="text" v-model:value="name"> <br>
<label for="age"></label>
<input id="age" type="text" v-model:value="age">
<button @click="showInfo">显示人员信息</button>
</div>
`,
data() {
return {
name: '张三',
age: 19
}
},
methods: {
showInfo() {
alert(this.name + '-' + this.age)
}
}
})
const dog = Vue.extend(
{
template: '#cdog',
data() {
return {
name: '大黄',
sex: '小公狗'
}
},
methods: {
showDogInfo() {
alert(this.name + '-' + this.sex)
}
}
}
)
Vue.component('person', person)
const vm = new Vue({
el: '#app',
data: {
title: '人员详细信息'
},
components: {
dog,
}
})
</script>
</body>
</html>
-
对上面的案例简单总结:
-
定义组件 普通写法 const component-name = Vue.extend({
data(){
return {
xxxx: 'yyyy'
}
}
})
简单写法 const component-name = {
data(){},
methods: {}
}
Vue.extend传入的配置项于new Vue() 传入的配置项基本相同,有两点需要注意: Vue.extend中不能有el,因为所有的组件最终都会挂载到vm上面 Vue.extend中的data必须写成函数形式的,不可以写成对象形式的 -
注册组件
-
全局注册 Vue.component('组件名', 组件)
Vue.component('person', person)
-
局部注册 new Vue({
'''
components: {
组件名: 组件
}
})
new Vue({
’‘’
components: {
组件,
}
})
-
使用组件
-
用法1 <组件名></组件名>
-
用法2 (需要脚手架支持,否则会有问题) <组件名/>
-
注意事项
-
关于组件名
-
如果是一个单词组成
- 第一种写法(首字母小写):person
- 第二种写法(首字母大写):Person
-
如果是多个单词组成
- 第一种写法(kebab-case命名): person-info
- 第二种写法(CamelCase命名): PersonInfo
-
特别注意
-
组件名要尽可能回避html标签 -
可以在定义组件时候使用name配置项指定组件在开发者工具中呈现的名字 const cat = {
name: 'xiao-zhu-pei-qi',
template: '#ccat',
data(){
return {
msg: '我是一只小花猫'
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyDDRgvZ-1663494649205)(D:\markdown\Vue速成手册.assets\image-20220722133451776.png)]
2.5 组件的嵌套用法
vm可以理解为所有组件的父组件,一个组件内部可以挂多个组件,组件是可以嵌套的,类似与数据结构中的树,vm类似于根节点
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件嵌套</title>
</head>
<body>
<div id="app">
<app></app>
</div>
<script src="../js/vue.js"></script>
<script>
const student = {
name: 'student',
data() {
return {
name: '张三',
age: 11
}
},
template: `
<div>
<h3>学生信息展示</h3>
<p>学生姓名: {{ name }}</p>
<p>学生年龄: {{ age }}</p>
</div>
`,
}
const school = {
name: 'school',
data() {
return {
name: '家里蹲',
address: '蹲家里'
}
},
template: `
<div>
<h2>学校信息展示</h2>
<p>学校名称:{{ name }}</p>
<p>学校地址:{{ address }}</p>
<hr>
<student></student>
</div>
`,
components: {
student,
}
}
const app = {
name: 'app',
template: `
<div>
<h1>{{ title }}</h1>
<school></school>
</div>
`,
data() {
return {
title: 'app标题'
}
},
components: {
school
}
}
const vm = new Vue({
el: '#app',
components: {
app
}
})
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4aug9T0-1663494649206)(D:\markdown\Vue速成手册.assets\image-20220722142336299.png)]
2.6 关于VueComponent
通过const school = Vue.extend({options}),会定义一个组件,school本质上是一个VueComponent构造函数
当我们在模板中写 或者,Vue解析时会创建school的实例对象,Vue自动调用了new Component(options)
每次调用Vue.extend,都会返回一个全新的VueComponent,不同的组件是不同的对象
关于this指向:
组件配置中data函数、methods中的函数、watch中的函数、computed中的函数他们的this均是VueComponent实例对象
new Vue(options) 配置中:data函数或者对象、 methods中的函数、watch中的函数、computed中的函数他们的this均是Vue实例对象
VueComponent实例对象简称vc, Vue实例对象简称vm
组件实例对象vc可以访问到Vue原型上面的属性和方法
**VueComponent.prototype. _ _ proto _ _ === Vue.prototype **
2.6.1 简单说一下prototype和 _ _ proto _ _
_ _ proto _ _ 是对象特有的,负责对象的属性方法的查找,如果对象自身没有需要的属性或者方法,_ _ proto _ _会指向对象构造函数的原型对象, _ _ proto _ _的终点是null
prototype 是函数特有的,但是因为js中函数也是一种特殊的对象,所以在函数中也有 _ _ proto _ _ 这是js原型结构中很难理解的地方
2.7 单文件组件
单文件组件就是一个文件只有一个组件,一个文件只完成局部的功能和包装局部的静态资源,vue中单文件组件的文件后缀是.vue
定义vue中单文件组件的常规目录结构结构:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2022/7/22 14:49 114 app.vue
-a---- 2022/7/22 14:49 132 index.html
-a---- 2022/7/22 14:48 0 main.js
-a---- 2022/7/22 14:49 117 school.vue
-a---- 2022/7/22 14:49 118 student.vue
-
school.vue <template>
<div id='Demo'>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'UESTC',
address:'成都'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
#Demo{
background: orange;
}
</style>
-
student.vue <template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'cess',
age:20
}
},
}
</script>
-
App.vue <template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
-
main.js import App from './App.vue'
new Vue({
template:`<App></App>`,
el:'#root',
components:{App}
})
-
index.html <!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>单文件组件练习</title>
</head>
<body>
<div id="root"></div>
<script src="../../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>
3 vue脚手架
3.1 初始化脚手架
3.1.1 基本介绍
vue-cli(vue脚手架)是vue官方提供的标准化开发平台
目前最新 的版本是5.x
3.1.2 使用步骤
-
配置淘宝镜像cnpm (这一步不是必须的,主要为了安装扩展包时候加速的) npm config set register http://registry.npm.taobao.org
-
全局安装@vue/cli npm install -g @vue/cli
-
切换到需要创建项目的目录,创建一个空的vue项目 vue create xxx // xxx是项目名称
-
选择使用的vue版本 选择vue2,等待下载安装依赖包,这里需要联网 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DAbFV7wu-1663494649206)(D:\markdown\Vue速成手册.assets\image-20220722150357812.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ab37CKoo-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220722150416187.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-slE6bzIt-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220722150523086.png)]
? 表示成功创建了项目。
vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行vue inspect > output.js
output.js只能用于查看配置项,在output.js中修改配置并不能改变vue的配置项
3.2 脚手架项目文件结构
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
3.3 修改配置项
使用vue.config.js 可以对脚手架进行个性化定制
module.exports = {
pages: {
index: {
entry: 'src/index/main.js'
}
},
lineOnSave: false
}
配置参考链接:
https://cli.vuejs.org/zh/config/#devserver-proxy
3.4 main.js 文件解读
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
3.4.1 不同版本的vue
选择runtime版本的vue的目的是为了减少生产环境的空间,提高效率
默认情况下,vue脚手架使用的vue是runtime版本
-
vue.js与vue.runtime.xxx.js的区别 vue.js是完整的Vue,包含核心功能+模板解析器 vue.runtime.xxx.js,只包含核心功能,没有模板解析器 vue.runtime.xxx.js中没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去执行具体内容
3.4.2 render函数
render(createElement){
return createElement(App)
}
4 脚手架内组件通讯
4.1 ref属性
ref用来给元素或子组件注册引用信息,类似于id
将ref应用在html标签上获取的是真实的DOM元素;应用在组件标签上获取的是组件实例对象vc
-
设置ref标识
<h1 ref="h1"> 组件间通讯 </h1>
<School ref="school"></School>
-
获取ref this.$refs.xxx 获取ref
this.$refs 返回一个对象
-
App.vue <template>
<div>
<div class="first-menu">
<h1 ref="h1"> 组件间通讯 </h1>
<h3>ref属性</h3>
<button @click="testTag">测试标签</button>
<button @click="testCpn">测试组件</button>
<hr>
</div>
<div class="content">
<School ref="school"></School>
</div>
</div>
</template>
<script>
import School from "@/components/School";
export default {
name: 'App',
components: {
School
},
methods: {
testTag() {
console.log(this.$refs.h1) // 获取到的是真实的DOM,this.$refs.h1.innerHTML 获取到标签内的文本
},
testCpn() {
console.log(this.$refs.school) // 获取到的是组件实例对象,this.$refs.school.name 获取到的是组件实例对象的name
}
}
}
</script>
<style>
.active {
background-color: cornflowerblue;
}
.content {
position: absolute;
left: 200px;
}
</style>
-
School.vue <template>
<div>
<h4 ref="info">学校信息</h4>
<p>学校名称: {{ name }}</p>
<p>学校地址:{{ addr }} </p>
</div>
</template>
<script>
export default {
name: "School",
data(){
return {
name: '清华大学',
addr: '北京'
}
},
}
</script>
<style scoped>
</style>
4.2 props属性
props 让组件接收外部传来的数据
注意:
props是只读的,vue底层会监视props属性中的值,如果对其进行修改,不会报错,但是会提示,如果需要修改props中的值,请将props复制到data中进行修改。
-
demo
-
普通接收
<template>
<div>
<div class="first-menu">
<h1 ref="h1"> 组件间通讯 </h1>
<h3>props属性</h3>
<hr>
</div>
<div class="content">
<School ref="school" name="清华大学" addr="北京"></School>
<School ref="school" name="武汉大学" addr="武汉"></School>
<School ref="school" name="厦门大学" addr="厦门"></School>
</div>
</div>
</template>
<script>
import School from "@/components/School";
export default {
name: 'App',
components: {
School
},
}
</script>
<style>
.active {
background-color: cornflowerblue;
}
.content {
position: absolute;
left: 200px;
}
</style>
-
School.vue <template>
<div>
<h4 ref="info">学校信息</h4>
<p>学校名称: {{ name }}</p>
<p>学校地址:{{ addr }} </p>
<hr>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {}
},
props: ['name', 'addr'] // 直接接收属性值
}
</script>
<style scoped>
</style>
-
限制变量类型接收 <template>
<div>
<h4 ref="info">学校信息</h4>
<p>学校名称: {{ name }}</p>
<p>学校地址:{{ addr }} </p>
<hr>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {}
},
props: {
name: String,
addr: Number
}
}
</script>
<style scoped>
</style>
-
限制类型、必要性、默认值 <template>
<div>
<h4 ref="info">学校信息</h4>
<p>学校名称: {{ name }}</p>
<p>学校地址:{{ addr }} </p>
<hr>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {}
},
props: {
name: {
type: String,
required: true,
default: '家里蹲大学'
},
addr: {
type: String,
required: true,
default: 'home'
}
}
}
</script>
<style scoped>
</style>
4.3 mixin 属性
可以把多个组件共用的配置提取成一个混入mixin对象.
4.4 plugin 属性
用于提升、扩展Vue的功能
4.5 scoped 属性
## 4.6 自定义事件
父组件传递数据到子组件,使用props
==**自定义事件是从子组件传递数据到父组件**==
子组件想要给父组件传递数据,要在父组件中给子组件定义自定义事件,事件回调在父组件中
- 语法
- 父组件
```vue
<template>
<children @customEvent="callback"> </children> // 或者 v-on:customEvent="callback"
</template>
<script>
methods: {
callback(...params){
// params 接收来自子组件中的数据
}
}
</script>
```
- 子组件
```vue
<script>
methods: {
alertCustomEvent(){
this.$emit('customEvent', param1, param2, ...)
// 参数1 是父组件为子组件设定的自定义事件名
// 参数2~参数n 是子组件为父组件传递的数据,最终被父组件的回调函数接收并处理。
}
}
</script>
```
```js
this.$emit('customEvent') // 解绑一个自定义事件
this.$emit(['event1', 'event2']) // 解绑多个自定义事件
this.$destroy() // 销毁子组件,绑定其身上的自定义事件随着全部失效
```
- 父组件 方式2
```vue
<template>
<div>
<div class="top">
<bookHeader ref="bookHeader"></bookHeader>
</div>
</div>
</template>
<script>
methods: {
addBookCallback(obj){
this.bookList.push(obj)
}
},
mounted() {
this.$refs.bookHeader.$on('addBook', this.addBookCallback)
// 如果想让自定义事件只执行一次,将$on转换成$once即可
}
</script>
```
- 父组件无论使用方式1还是方式2,子组件的使用方式均相同。
- 组件绑定原生事件
在原生事件后面加上==**.native**== 否则原生事件也会被当做是自定义事件。
```js
<template>
<div>
<div class="top">
<bookHeader @click.native="sayHello"></bookHeader>
</div>
</div>
</template>
4.7 全局事件总线
4.7.1 定义
一种可以在任意组件之间通信的方式,不限于父子组件之间,本质上就是一个对象
- 所有的组件都必须能够看到这个对象
- 这个对象必须能够使用$on、$emit、$off、方法去绑定、触发和解绑事件
4.7.2 使用步骤
4.7.3 小结
全局事件总线相比较自定义事件总线更加灵活。
4.8 发布与订阅
用的较少,先不展开,后面有时间在搞。
5 vuex
5.1 搭建vuex环境
-
安装 npm i vuex@3 vuex目前最新版本是4版本,4版本必须使用vue3,如果使用vue2版本,必须安装vuex3,否则安装会报错 -
配置 在src文件下创建store目录,在目录内创建index.js 看图说话: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IludkSgi-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220802231636436.png)]
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {
xxx(context, value) {
context.commit('ADD', value)
},
}
const mutations = {
ADD(state, value) {
state.sum += value
},
}
const state = {
sum: 10
}
export default new Vuex.Store({
actions,
mutations,
state
})
5.2 getter
5.3 模块化
6 vue-router
6.1 安装
当前最新的vue-router版本是V4.x,V4.x版本的vue-router适用于vue3,如果使用vue2版本,请使用vue-router3.x版本
npm i vue-router@3
cnpm i vue-router@3
6.2 配置
-
src/router/index.js import Vue from 'vue'
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
}
]
})
-
src/main.js import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
-
src/components/App.vue <template>
<div>
<div id="app">
跳转标签
<router-link active-class="active" to="/about">About</router-link>
<router-link active-class="active" to="/home">Home</router-link>
</div>
<div>
组件内容展示区
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
.active {
background-color: cornflowerblue;
}
</style>
-
src/components/About.vue -
src/components/Home.vue
6.3 路由传参query
假设有一个图书管理系统,首页显示所有图书的列表,当点击每本图书后,显示图书的详细视图,点击不同的图书显示不同的详细视图,类似这种需求就需要给路由传参。
vue-router中路由传参有两种写法,分别是字符串写法和对象写法。
src/router/index.js
import Vue from "vue"
import VueRouter from 'vue-router'
import Detail from "@/components/Detail";
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/books',
component: Detail
}
]
})
src/App.vue
<template>
<div>
<div id="app">
<h1>图书管理系统</h1>
<table border="1px">
<thead>
<th>ID</th>
<th>书名</th>
<th>出版社</th>
</thead>
<tbody>
<tr v-for="book in bookList" :key="book.id">
<th>{{ book.id }}</th>
<th>
<router-link :to="`/books?id=${book.id}&name=${book.name}&publish=${book.publish}`">
{{ book.name }}
</router-link>
</th>
<th>{{ book.publish }}</th>
</tr>
</tbody>
</table>
</div>
<hr>
<div style="background-color: cornflowerblue">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
bookList: [
{id: '001', name: '天龙八部', publish: '中国出版社'},
{id: '002', name: '西游记', publish: '中国出版社'},
{id: '003', name: '水浒传', publish: '中国出版社'},
{id: '004', name: '金瓶梅', publish: '中国出版社'},
{id: '005', name: '神雕侠侣', publish: '中国出版社'},
{id: '006', name: '天下第一', publish: '中国出版社'},
]
}
}
}
</script>
<style>
.active {
background-color: cornflowerblue;
}
</style>
src/components/Detail.vue
<template>
<div>
<h1>书籍详细信息</h1>
<h2>书籍名称:{{ $route.query.name }}</h2>
<h2>书籍出版社:{{ $route.query.publish }}</h2>
</div>
</template>
<script>
export default {
name: "Detail"
}
</script>
<style scoped>
</style>
-
路由传参方式1: <router-link :to="`/books?id=${id}&name=${name}&publish=${publish}`">跳转</router-link>
在组件中使用$route.query.name 等获取路由中传递的参数 -
路由传参方式2: <router-link :to="{
path: '/books',
query: {
id: book.id,
name: book.name,
publish: book.publish
}
}">
{{ book.name }}
</router-link>
在组件中使用$route.query.name 等获取路由中传递的参数
6.4 命令路由
为了简化多级路由的写法,可以在定义路由时添加一个name属性。
src/router/index.js
routes: [
{
name: 'detail',
path: '/books',
component: Detail
}
]
src/App.vue
-
不需要传参,to属性使用字符串拼接写法和对象写法都可以 字符串写法 <router-link :to="{name: 'detail'}">跳转</router-link>
对象写法 <router-link :to="{
name:'detail'}">
<router-link>
-
需要传参,to属性必须使用对象写法 <router-link :to="{
name:'detail',
query: {
id: '01',
name: '水浒传',
publish: '中国出版社'
}
}">
<router-link>
6.5 路由传参params
query传参是在跳转时传递参数,类似get请求传参,将参数通过?链接到url后面,通过&符号链接多个key-value.
http://127.0.0.1:8000/books?id=1&name=水浒传
id和name就会传递给/books路由对应组件的$router.query中。
params在定义路由的时候就要确定要传递的参数,在path中使用占位符声明接收params参数,如果使用params传参,to的对象写法不能使用path属性,必须使用name属性。
src/router/index.js
import Vue from "vue"
import VueRouter from 'vue-router'
import Detail from "@/components/Detail";
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
name: 'detail',
path: '/books/:id/:name/:publish',
component: Detail
}
]
})
src/App.vue
'''
<router-link :to="`/books/${book.id}/${book.name}/${book.publish}`">
{{ book.name }}
</router-link>
'''
再次重申,如果to使用对象写法,必须使用命名路由,不能使用path
'''
<router-link :to="{
name: 'detail',
params: {
id: book.id,
name: book.name,
publish: book.publish
}
}">{{ book.name }}</router-link>
'''
src/component/Detail.vue
使用$route.params.xxx接收路由中的params参数
<template>
<div>
<h1>书籍详细信息</h1>
<h2>书籍ID:{{ $route.params.id }}</h2>
<h2>书籍名称:{{ $route.params.name }}</h2>
<h2>书籍出版社:{{ $route.params.publish }}</h2>
</div>
</template>
<script>
export default {
name: "Detail"
}
</script>
<style scoped>
</style>
6.6 路由props属性
props可以让路由更方便接收到参数:
{
name:'detail',
path:'books/:id/:name/:publish',
component:Detail,
props($route){
return {
id: $route.query.id,
title: $route.query.title
}
}
}
src/route/index.js
import Vue from "vue"
import VueRouter from 'vue-router'
import Detail from "@/components/Detail";
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
name: 'detail',
path: '/books/:id/:name/:publish',
component: Detail,
props($route){
return {
xixi:$route.params.id,
haha:$route.params.name,
xxoo: $route.params.publish
}
}
}
]
})
/src/components/Detail.vue
<template>
<div>
<h1>书籍详细信息</h1>
<h2>书籍ID:{{ xixi }}</h2>
<h2>书籍名称:{{ haha }}</h2>
<h2>书籍出版社:{{ xxoo }}</h2>
<!-- <h2>我来自路由中的props:{{ hello }}</h2>-->
</div>
</template>
<script>
export default {
name: "Detail",
// 写法1:对应路由中props的对象写法
// props: ['hello']
// 写法2:对应路由中props的布尔值写法,如果为true,params中传递的参数都将到组件的props中
// props: ['id','name','publish']
// 写法3:函数写法
props: ['xixi', 'haha', 'xxoo']
}
</script>
<style scoped>
</style>
6.7 编程式路由导航
6.7.1 浏览器缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXxXgPPA-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220917172112078.png)]
在浏览器左上角一般都会有两个箭头,分别表示“向前"和"向后".
浏览器的路由缓存类似于一个栈,有两种存储地址的方式:
6.7.2 编程式路由组件(不使用router-link实现路由跳转)
不借助实现路由跳转,让路由跳转更加灵活
this.$router.push({})
this.$router.replace({})
this.$router.forward()
this.$router.back()
this.$router.go(n)
push和replace里面的参数和to相同:
this.$router.push({
name: 'router name'
})
<template>
<div>
<h2>Test push&replace</h2>
<div>
<router-link v-for="book in bookInfo" :key="book.id" :to="{name:'xiangxi', params:{id:book.id, title:book.title}}">
{{ book.title }}
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
bookInfo: [
{id: 1001, title: '三国演义'},
{id: 1002, title: '水浒传'},
{id: 1003, title: '西游记'},
{id: 1004, title: '红楼梦'},
]
}
},
methods: {
showPush(book){
this.$router.push({
name: 'xiangxi',
params: {
id: book.id,
title: book.title
}
})
},
showReplace(book){
this.$router.replace({
name: 'xiangxi',
params: {
id: book.id,
title: book.title
}
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<h1>图书详情信息</h1>
<p>图书编号: {{ id }}</p>
<p>图书名称: {{ title }}</p>
</div>
</template>
<script>
export default {
name: "Xiangxi",
data() {
return {
'id': this.$route.params.id,
'title': this.$route.params.title
}
}
}
</script>
<style scoped>
</style>
6.8 缓存路由组件
默认情况下:路由组件被切换后,会被vue销毁,所以在该组件中的数据都将被清除。
keep-alive 标签可以将组件缓存,不被销毁,所以在路由切换后能够保存组件中的数据。
-
缓存一个组件 include 属性中填写组件名 <keep-alive include="Python">
<router-view></router-view>
</keep-alive>
-
缓存多个组件 include属性中填写组件列表 注意:缓存一个组件的时候,include前面没有冒号:,缓存多个组件的时候,include前面有冒号: <keep-alive :include="['Python', 'Java']">
<router-view></router-view>
</keep-alive>
-
缓存所有组件 不写include属性就代表缓存所有 <keep-alive>
<router-view></router-view>
</keep-alive>
6.9 路由相关的钩子函数
-
activated 路由组件被激活时触发 -
deactivated 路由组件失活时触发
<template>
<div>
<h1>Python学科</h1>
<p><span :style="{opacity}">人生苦短,我用Python</span>
<input type="text" v-model="msg">
</p>
</div>
</template>
<script>
export default {
name: "Python",
data() {
return {
msg: 'python:',
opacity: 0,
}
},
activated() {
this.timer = setInterval(() => {
if (this.opacity <= 1) {
this.opacity += 0.01
} else {
this.opacity = 0
}
}, 16)
},
deactivated() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
</style>
6.10 路由守卫
对路由权限进行控制
路由配置中通过:meta配置源信息
6.10.1 全局守卫
对所有组件都起作用
-
全局前置守卫:初始化时,每次路由切换前执行 router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
let c = localStorage.getItem('class')
if (c === 'v') {
next()
} else {
alert('无权访问')
next(false)
}
} else {
next()
}
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYvPKB9A-1663494649208)(D:\markdown\Vue速成手册.assets\image-20220918093757914.png)] -
全局后置守卫:初始化时,每次路由切换后执行 router.afterEach((to, from)=>{
document.title = to.meta.title
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cyq5sdyx-1663494649208)(D:\markdown\Vue速成手册.assets\image-20220918093838110.png)] -
小结 to,from,next三个参数都是必要的
to:即将要进入的目标 路由对象
from:当前导航正要离开的路由
next:一定要调用该方法来处理这个钩子,如果不写next()或者next(false),页面路由不会跳转,也就是页面被阻止在当前页面了
to,from是一个对象,就是 routes[] 数组里面配置的某个具体的路由对象,
比如:to.path, to,name, to.meta 或 from.path, from.name, from.meta 【path,name,meta】这些字段都是自己在路由里面定义的字段,这样就可以开始写逻辑了。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
6.10.2 独享守卫
对某个组件起作用,你可以在路由配置上直接定义 beforeEnter 守卫:
参数与全局前置守卫相同:
{
path: '/class',
name: 'class',
meta: {
requireAuth: true,
title: 'Class'
},
component: Class,
children: [
{
path: 'python',
name: 'python',
component: Python,
beforeEnter: (to, from, next)=>{
if (localStorage.getItem('class') === 'v1'){
next()
}else{
alert('无权访问')
next(false)
}
}
},
]
}
6.10.3 组件内守卫
-
通过路由匹配后,进入路由组件时守卫 beforeRouteEnter((to, from, next)=>{})
-
离开路由组件时守卫 beforeRouteLeave((to, from, next)=>{})
6.10.4 路由的两种模式
-
hash 地址中有#号
地址中#号后面的值不会出现在http请求中
兼容性好
不美观
app检验严格的情况会被标记为不合法
-
history 地址干净、美观
兼容性比hash差
可能会出现404报错,因为路径中的数据会被发送到后端
-
设置模式 const router = new VueRouter({
mode: 'history' or 'hash'
})
7 vue ui组件库
1 安装element-ui
cnpm i element-ui -S
2 全局配置
src/main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')
src/App.vue
<template>
<div>
<div id="app">
<el-row>
<el-button type="primary" icon="el-icon-edit circle" @click="sayHello"></el-button>
</el-row>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: {},
methods: {
sayHello(){alert('say hello!')}
}
}
</script>
<style>
</style>
3 部分配置
1 安装babel-plugin-component
npm i babel-plugin-component -D
2 修改babel-config-js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
src/main.js
import Vue from 'vue'
import App from './App.vue'
import { Button,Row } from 'element-ui'
Vue.config.productionTip = false
Vue.component(Button.name, Button);
Vue.component(Row.name, Row);
new Vue({
el:"#app",
render: h => h(App),
})
一般如果对性能要求不是太苛刻的话都会选择全局配置
8 LocalStorage 和 SessionStorage
8.1 LocalStorage(本地存储)
-
setItem localStorage.setItem('k1', 'v1')
-
getItem localStorage.getItem('k1')
'v1'
-
length 属性 localStorage.length
1
-
getItem localStorage.getItem('k2')
'v2'
-
clear 清楚所有存储 localStorage.clear()
-
localStorage.removeItem localStorage.removeItem('k1')
8.2 SessionStorage(会话存储)
常见问题总结:
1 Component name “Class” should always be multi-word vue/multi-word-component-names
-
解决方式1: 在vue.config.js中添加关闭语言检查功能 const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false
})
-
解决方式2: 按照要求修改组件名
2 路由文件与main文件分模块时报如下错误:Error in beforeCreate hook: “TypeError: this._router.init is not a function”
问题原因: 导出和导入模块的方式不对
正确的处理方式:
格式一 变量形式 --暴露
export default {
router
}
--接收
import {router} from './router'
格式二 直接暴露
export default router
接收
import router from './router'
|