Vue
笔记:vue基础 · 语雀
代码:vue基础阶段代码
官网:Vue.js(作者: 尤雨溪)
组件库 | Vant2 |
elementui Element - The world's most popular Vue UI framework |
一、Vue介绍
1、学习前的工具安装
插件名称 | 作用 | |
chrome插件 | vue_devtools | 可以检测网站是不是Vue开发,如果是图标会亮,如果不是图标是灰暗; 可以在开发阶段帮助我们调试数据; |
安装方法:三点 → 更多工具 → 扩展程序 → 打开'开发者模式' → 将插件拖入其中(安装完成) → 工具栏中的'扩展程序',将其固定 | ||
vscode插件 | vetur | 让vscode能够识别 .vue文件; 还提供了一些vue代码的格式化功能,以及一些简单的代码段; |
vue 3 snippets (选装) | 提供更多的vue的代码段 |
2、Vue及其作用
概念:Vue是一套渐进式的JavaScript框架;
作用:开发快捷;
什么是框架?
内部做了高度封装,并提供了自己的一套语法和开发体系,用框架可能会改变原本的开发模式;
3、MVVM设计模式
二、Vue的使用方法
1、举例
<body>
<div class="box">
<p>shy</p>
3. 插值语法, 将数据放入页面中
<p>{{msg}}</p>
</div>
1. 引入vue.js文件
<script src="./vue-2.6.12.js"></script>
<script>
2. 实例化Vue对象
new Vue({
2.1 把.box的元素交给vue来管理,可以传任意的css选择器
或 new Vue 最后加上 .$mount('.box')
el: '.box',
2.2 data是vue里放数据的地方,html中这样写,vue文件中的data是函数
data: {
属性名: 属性值,
msg: '中国',
}
2.3 methods是vue里放方法的地方
methods: {
函数名() {函数体},
},
2.4 过滤器
filters: {
过滤器名字 (原数据) {
代码对数据进行操作
return 新结果
},
},
2.5 计算属性
computed: {
计算属性名 () { // 函数的写法
经过一系列计算,得到结果
return 结果
}
},
2.6 侦听器
watch: {
要监测的数据 () {
监测的数据变换了,进行的操作
}
}
})
</script>
</body>
2、插值语法
语法:{{vue的数据}}
注意:括号里可以写vue里的数据或任意的表达式,但不能写语句
例:
<p>{{ msg }}</p>
<p>{{ msg + ',998' }}</p>
<p>{{ age >= 18 ? '成年' : '未成年' }}</p>
三、指令
指令是Vue提供的一套具有特殊功能的行内属性,一般是 v- 开头;
内置指令
1、v-text和v-html
v-text | 不会解析标签,只是当纯文本(类似于 innerText) |
---|---|
v-html | 会解析标签,会解析成dom元素(类似于 innerHTML) |
共同特点:都是用来设置双标签里显示的内容 ,都会覆盖原本的内容 |
v-text和插值语法的区别:
v-text 不会解析标签,但会覆盖原来的内容;
插值语法 不会解析标签,并且会保留原本的内容,它只会把写{{}}的地方替换成最终数据;
<div v-text="msg">原本的内容1</div>
<div>原本的内容2--{{msg}}</div>
2、v-on(绑定事件)
(1)v-on的使用方法
作用:vue里面绑定元素的事件,用 v-on 指令
语法:
<标签 v-on:事件名="函数名/函数体"/> 如果事件函数的代码只有一句话,那么可以把这句话直接写到行内
<标签 v-on:事件名="一句代码" />
简写语法:将 v-on 换成 @
<标签 @事件名="函数"/>
<标签 @事件名="一句代码"/>
举例:<button v-on:click="fn1">内容</button> 点击,调用fn1函数
<button @click="msg = '鸡杀马'">内容</button> 点击,给数据中的msg设置值
注意:在JS中访问Vue里的数据,前面要加this,在HTML中Vue里的数据,前面不用加this
(2)Vue里的this
Vue里的this,指的是当前它所在的Vue实例,只不过Vue实例在创建后的某个阶段里,会把所有data中的数据和methods中的所有方法,全部挂载到这个实例的最外层,所以我们可以直接通过this来调用;
this.数据名
this.方法名()
fn() {
this.msg='SHY',
this.fn1()
}
(3) vue里事件对象($event)
总结:
- $event 在
原生标签
中,代表事件对象;
- $event 在
组件
中,代表组件传递过来的数据;
- 可以直接把子组件传递过来的数据进行赋值;
-
<son @getMsg="msg = $event"/> 将子组件传过来的数据赋值给父组件的msg变量
原生代码(APP.vue本身):
1. 在Vue中,如果事件绑定的函数没加小括号,这个参数就是事件对象
<button @click="fn1">按钮</button>
fn1 (e) {
console.log(e) // 事件对象
}
2. 在Vue中,如果事件绑定时加了小括号,就代表自己要指定传参数,此时你传什么,参数就是什么
2.1 如果加了小括号,但并没传实参,那么fn2里函数的参数的返回值是 undefined
<button @click="fn2()">按钮</button>
fn2 (e) {
console.log(e) // undefined
}
2.2 如果加了小括号,参数里传的是hello,那么fn3里函数的参数,就是一个 hello
<button @click="fn3('hello')">按钮</button>
fn3 (e) {
console.log(e) // hello
}
2.3 既想自己传参,又想拿到事件对象,就用 $event 代表事件对象
<button @click="fn4('hello', $event)">加了小括号拿事件对象</button>
fn4 (a, b) {
console.log(a) // hello
console.log(b) // 事件对象e
}
使用组件时(写在APP.vue中):
1. 在Vue中,如果事件绑定的函数没加小括号,这个参数就是子组件传递过来的数据
<son @changeMsg="fn1" />
fn1 (e) {
console.log(e) // 子组件传递过来的数据
}
2. 在Vue中,如果事件绑定时加了小括号,就代表自己要指定传参数,此时你传什么,参数就是什么
2.1 如果加了小括号,但并没传实参,那么fn2里函数的参数的返回值是 undefined
<son @changeMsg="fn1()" />
fn2 (e) {
console.log(e) // undefined
}
2.2 如果加了小括号,参数里传的是hello,那么fn3里函数的参数,就是一个 hello
<son @changeMsg="fn1(hello)" />
fn3 (e) {
console.log(e) // hello
}
2.3 $event 代表 子组件传递过来的数据
<son @changeMsg="fn1($event)" />
fn4 (e) {
console.log(e) // 子组件传递过来的数据
}
可以直接把子组件传递过来的数据进行赋值
<son @getMsg="msg = $event"/>
(4)事件修饰符
在vue中很多事件对象的功能,可通过事件修饰符就能实现;
写在事件类型后面即可,并且这些修饰符可以连在一起用,没有顺序之分;
事件修饰符 | 作用 |
---|---|
.prevent | 阻止默认行为 |
.stop | 阻止冒泡 |
.once | 代表绑定的事件只触发一次 |
.self | 代表事件只能由自身触发,不能由冒泡触发 若加在父元素身上,其子元素的事件都会有.self效果 |
例:<button @click.stop="fn2">点我</button> 写在事件类型后面
<button @click.stop.once="fn3">点我</button> 链接写法,once代表这个事件绑定只绑定了一次,那么后面再点会连同stop的效果也会没了
3、v-bind(动态绑定行内属性)
作用: 想让标签内哪个属性不写死,就可以用v-bind进行绑定
语法 <标签 v-bind:属性名="vue里的数据"/>
简写 <标签 :属性名="vue里的数据"/> // 省略 v-bind
例:a标签中的href或img中的src 随数据中的 url/pic 改变而改变
<a v-bind:href="url" target="_blank">点我跳转</a>
<img v-bind:src="pic">
4、v-model(双向绑定)
作用:v-model是用来获取/设置表单元素(input/select/textarea)的内容,除了获取也可以设置;
(界面变了,数据会跟着变; 数据变了,界面也能跟着变)
语法:<表单标签 v-model="vue里的数据">
例:<input type="text" v-model="msg"> input里的内容修改时,数据中的msg也会改变;
数据中的msg修改时,input里的内容也会改变
(1)v-model的修饰符
语法:<表单标签 v-model.修饰符="vue里的数据">
v-model的修饰符 | 作用 |
---|---|
.number | 输入框默认输入的内容,拿到的都是字符串; 用了.number 把输入的内容自动转成数字类型,如果不能转,输入的是什么就拿到什么; |
.trim | 去除输入内容的两边空格 |
.lazy | 输入完成后,才会让数据改变 |
可以同时使用多个修饰符 |
5、v-for(循环生成标签)
作用:循环/遍历(数组、对象)生成标签;
语法:
<标签 v-for="v in 数字"></标签>
<标签 v-for="(元素,下标) in 数组"></标签> 只取下标时,需要占位
<标签 v-for="(属性值,属性名) in 对象"></标签> 只有1个值时不用加小括号
例:
<div v-for="v in 9">haha -- {{ v }}</div>
会循环生成9个div, v就是从1到9的数字, 标签中的内容除了v 其它的都一样
<li v-for="(item, index) in list">{{ item }} -- {{ index }}</li>
遍历list数组,这个数组有多少个元素就会产生多少个li;
item:就是被遍历到的每个元素,index:就是被遍历到的下标(名字不一定要叫item和index)
<li v-for="(val,key) in obj"> {{ val }} -- {{ key }}</li>
遍历obj这个对象,对象有多少个属性,就会生成多少个li;
val就是每一个被遍历到的属性值,key就是每一个被遍历到的属性名
(不一定要叫val和key,也可以叫别的名字,但是第一个一定是属性值,第二个一定是属性名)
6、v-show与v-if
语法:
<标签 v-show="数据"></标签>
<标签 v-if="数据"></标签>
例:
<p v-show="isShow">我是v-show控制</p>
<p v-if="isShow">我是v-if控制</p>
作用(相同点) | 不同点 | |
---|---|---|
v-show | 都是用来控制一个元素是显示还是隐藏 | 1. v-show 是通过控制css中的 display 属性来达到看的到或者看不到的效果,所以要频繁控制显示和隐藏,用 v-show; 2. v-show没有双分支或多分支; |
v-if | 1. v-if 是通过直接操作dom树的添加或移除来达到看到或者看不到的效果,所以如果要一次性决定有或者没有某个元素,就可以用 v-if;(如,VIP权力) 2. v-if有双分支和多分支(v-if / v-else-if / v-else); |
v-if的双分支和多分支
举例:(根据输入的内容显示对应的div)
1. 双分支
<input type="text" placeholder="请输入年龄" v-model.number="age"> // 双向绑定
<div v-if="age >= 18">去网吧偷耳机</div>
<div v-else>去公园捡垃圾</div>
2. 多分支
<input type="text" placeholder="请输入班长有多少钱" v-model.number="money">
<div v-if="money >= 3000">按背</div>
<div v-else-if="money >= 2000">洗脚</div>
<div v-else-if="money >= 1000">吃饭</div>
<div v-else-if="money >= 500">网吧</div>
<div v-else>扫地</div>
自定义指令
1、全局自定义指令
写在 main.js 中,在 new Vue() 之前定义全局自定义指令
写法:Vue.directive('自定义指令名称', { 配置 })
例:
Vue.directive('color', {
bind(el,binding) { // 只执行一次
el.style.color = 'red' 或
el.style.color = binding.value
},
update(el,binding) { // 当数据变化后,更新DOM,会调用update函数
el.style.color = 'red' 或
el.style.color = binding.value
},
})
当bind和update里要执行的代码一模一样,可以简写
Vue.directive('color', (el, obj) => { el.style.color = obj.value; })
2、 私有自定义指令
directives: { // 用来声明自定义指令,与data同级
指令名: {
bind (el, obj) { // 当绑定指令的dom渲染出来时就会立即执行的函数,就执行1次
在函数内设置该指令的效果
参数1:绑定指令的元素
参数2:是一个对象,里面有指令的相关信息,其中这个对象的value属性可以拿到指令绑定的数据
},
update(el, obj) { // 本指令绑定的数据发生改变就来调用,绑定的数据指的是<h1 v-color="color">中国</h1>中的变量color
当绑定的数据发生变化后执行的代码
},
}
}
当bind和update里要执行的代码一模一样,可以简写
directives: {
指令名 (el, obj) { // 就相当于不管是bind的时机还是update时机,都是调用此函数
设置的指令效果的代码
}
}
举例:自定义属性 v-color(作用:给标签更改颜色;
使用方法:就给某个标签加 v-color 即可使用这个指令)<div v-color="msg">
directives: {
color: {
bind (el, obj) {
console.log(el,obj) // el为 <h1 style="color: red;">中国</h1>
// obj为 {name: 'color', rawName: 'v-color', value: 'red', expression: 'color', modifiers: {…}, …}
el.style.color = obj.value // 把obj.value赋值给标签的样式,obj.value拿到的是msg的值
},
update(el, obj) {
console.log(el, obj)
el.style.color = obj.value; // 把修改后的值重新赋值给标签的样式
},
}
}
因为bind和update里要执行的代码一模一样,所以可以简写
就相当于不管是bind的时机还是update时机,都是调用此函数
directives: {
color (el, obj) {
el.style.color = obj.value
}
}
三、动态样式及类
1、动态style
作用:让行内样式不写死,根据数据来得到最终效果;
利用 v-bind 绑定 style 属性
<标签 :style="{样式名1: 数据1,样式名2: 数据2}"></标签>
<div :style="{backgroundColor: red, color}">我是div</div>
注意:
- 如果样式名带
-
,那么去掉-
并把-
后面的首字母大写; - 如果要绑定多个样式,用逗号隔开;
- 若样式名和数据值一样时,根据es6简写,可以简写为样式名(color:color)
2、动态class
作用:动态绑定类名(class),让类名不写死,来实现样式的切换;
<标签 :class="{类名1:布尔值1,类名2:布尔值2}"></标签>
<div class="box" :class="{ red: isRed, op: true }">我是div</div>
<td :class="{red: item.price >= 100}">{{ item.price }}</td>
1. 当布尔值为true就有这个类,为false就没有这个类;
2. 布尔值写成比较表达式
注意:
- 可以写死一些类,然后可以再动态绑定一些类,他们并不冲突,可以同时使用;
- 如果有多个动态类,那么逗号隔开;
四、Vue过滤器和计算属性
过滤器(filters)、计算属性(computed)跟data、methods这些是平级的
(1)Vue过滤器
作用:对某个数据做处理,得到一个新的结果;
1. 声明一个过滤器
filters: {
过滤器名字 (参数) { // 参数用来接收使用过滤器是的原数据
代码对 参数(原数据) 进行处理
return 新结果
},
}
2. 使用过滤器 含义:将原数据交给过滤器处理,会从左往右依次调用过滤器,但是最终显示的结果,一定是最后一个过滤器的结果
<标签> {{ 原数据 | 过滤器名字1 | 过滤器名字2 }} </标签>
例子:
<body>
<div id="app">
<!-- 把hobby先交给sing去处理得到一个结果,
再把结果又交给jump去处理,得到最终结果,
最终显示的结果一定是最后一个过滤器的结果 -->
<p>{{ hobby | sing | jump | rap }}</p> 结果:我的兴趣是:唱,跳,rap
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
hobby: '我的兴趣是:'
},
// 写过滤器的地方
filters: {
sing (val) {
return val + '唱' // 返回 原数据val和唱拼接成的字符串
},
jump (val) {
return val + ',跳'
},
rap (val) {
return val + ',rap'
}
}
})
</script>
</body>
(2)计算属性
作用:当某个数据是依赖别的数据计算得到的,就可以用计算属性;
1. 声明一个计算属性
computed: {
计算属性名 () { // 函数的写法
经过一系列计算,得到结果
return 结果
}
}
2. 使用计算属性(和普通属性的使用方法一样)
<标签>{{计算属性名}}</标签>
例子:
// js中
computed: {
total () {
let sum = 0
this.list.forEach(v => sum += v.price)
return sum
}
}
// html中
<span>{{ total }}</span>
计算属性有缓存
a)计算属性本质上是函数,但是它默认情况下只会调用一次,得到结果后把结果缓存起来了,后面读取计算属性,都是从缓存里取出来的;
b)当计算属性里的依赖项(计算属性里用到的 别的属性)发生变化时,就会重新调用,得到一个新结果,再把新结果缓存起来;
(3)Vue过滤器和计算属性的区别
应用场景 | 是否有缓存 | |
---|---|---|
Vue过滤器 (filters) | 如果界面上展示的数据不是想要的效果,就可以用过滤器来处理,它一般用来处理时间等 | 没缓存 |
计算属性 (computed) | 计算属性主要用在一些需要计算得到的数据(总价、平均价) | 有缓存 |
五、侦听器
作用:侦听某个数据的变化(如果数据变化了,就执行一些操作);
1、普通侦听
语法:(可用来侦听data里的数据或某些数组变化)
watch: {
要监测的数据 (变化后的值,变化前的值) {
监测的数据变换了,进行的操作
}
}
例:
watch: {
监测msg(msg是data里的数据)是否有变化,一旦有变化就调用这个函数
msg (newVal, oldVal) {
console.log('msg变化了', newVal, oldVal) // 打印出 变化后的值 和 变化前的值
}
}
obj.name 对象的属性
要监测的数据是数据、对象(指向,地址不同)
2、侦听对象
new Vue({
el: '#app',
data: {
obj: {
name: 'jack',
age: 16
}
},
watch: {
1. 侦听对象本身时,是侦听不到他里面属性(obj.name)的变化(因为对象是复杂类型,指向的是地址)
当对象的指向发生改变时,才会侦听到对象的变化
obj (newVal, oldVal) {
console.log('obj被改了', newVal, oldVal)
},
2. 直接写对象.属性名就能侦听这个对象的某个属性的变化
这个是侦听的obj的name属性,不能侦听obj.age的变化
'obj.name' (newVal, oldVal) {
console.log('obj里的name变化了', newVal, oldVal)
},
3. 深度侦听,可以侦听对象(obj)里任何属性的变化
obj: {
deep: true, // 开启深度侦听
immediate: true, // 是否页面一打开就立即调用一次handler(选写),不写默认为flase
// 当侦听到obj里任何属性发生改变都会触发这个handler函数
// newVal, oldVal得到的是obj的指向,返回的值是一样的
handler (newVal, oldVal) {
console.log('obj里有属性变化了', newVal, oldVal)
}
}
}
})
3、深度侦听
作用:深度侦听可以侦听对象(obj)里任何属性的变化;
对象名: {
deep: true, // 开启深度侦听
immediate: true, // 是否页面一打开就立即调用一次handler(选写),不写默认为flase
// 参数newVal, oldVal得到的是obj的指向,返回的值是一样的(都是修改后的指向),所以参数可以省略
handler () {
当侦听到obj里任何属性发生改变都会调用这个handler函数
}
}
4、侦听数组
如果调用的是以下方法(修改原数组的方法),那么即是用一般侦听去侦听数组,那也能捕捉到变化(因为Vue对这些方法重新封装了),所以大部分情况下,对于数组而言,直接不用开启深度侦听也可侦听到。
push() / pop() / shift() / unshift() / splice() / sort() / sort() / reverse()
// html
<div id="app">
<button @click="add">添加一个元素</button>
<button @click="del">删除一个元素</button>
<ul>
<li v-for="item in list"> {{ item }}</li>
</ul>
</div>
// JS
new Vue({
el: '#app',
data: { list: [10, 20, 30] },
methods: {
add () { // 添加一个随机数
this.list.push( Math.random() )
},
del () { // 删除最后一个元素
this.list.pop()
}
},
watch: {
// 普通侦听数组list——因为数组的push、pop、unshift、shift、splice等方法都被Vue重新包装过,所以才能侦听到变化
list () {
console.log('list有变化')
}
}
})
六、Vue文件(组件)
1、概念
以 .vue结尾的文件,称之为vue文件,也叫组件;
组成网页的一部分就叫组件,一个网页就是一个一个组件组成的;
2、vue文件包含的三大部分
html、js、css
正是因为有这三大部分,所以才能代表界面中的一个小部分
3、将vue文件进行webpack打包
因为浏览器认识的是html文件,不认识vue文件,所以该用 webpack 进行打包,打包生成为html文件,而且 webpack 是一个模块化管理项目的工具,而组件可以理解为就是整个项目中的一个模块,所以更应该用 webpack 管理起来。
七、脚手架(vue-cli)
官网:4.40
1、安装脚手架
安装全局模块
1. 安装命令
npm i -g @vue/cli
2. 检测是否安装成功(这是大写的V,如果出现版本号,就代表安装成功)
vue -V
3. 更新
npm update -g @vue/cli
2、创建新项目和运行项目
a)找一个放自己项目的文件夹(文件夹中不能有vue.js文件),打开小黑窗,执行创建项目的命令
创建项目
vue create 项目名
注意:项目名不能大写、不能有特殊符号、不能用关键字、不能叫vue
b)选择版本配置
c)出现如下界面代表创建成功
d)可执行最后两行命令开启开发服务器
e)创建成功后,文件夹中会出现创建的项目文件夹
项目文件介绍
App.vue
,称之为主组件
,因为项目运行后看到的效果,就是在这里写的main.js
,称之为入口文件
,打包运行的第一个文件就是它- 以后写代码,都是写在
src
里的
3、脚手架Vue项目启动流程
4、组件开发的注意点
每个 .vue文件
有三大部分:
- template:写html结构的地方(必须有);
- script:写js代码的地方;
- style:写样式的地方;
这三大部分可以用 <vue>
来快速生成
(1)每个组件的template里要有根标签
template里是放html结构的地方,但是必须把所有写的结构
包到一个根标签
里;
根标签一般都是div;
(2)组件化开发里,data必须是个函数,在return的对象中声明vue的数据
export default { // 默认导出
data () { // 1.es6的简写形式
return {
// 在此处声明vue的数据
msg:'123',
}
},
data: function () { // 2.原生形式
return {
msg:'123',
}
}
};
5、eslint (强制遵守规范)
eslint工具在创建项目时已经默认安装了,默认是开启的(代码不规范的话会报错);
作用:让程序员写代码时能够强制遵守规范
,如果代码不符合规范就直接报错,运行不了;
如何关闭ESLint (关的是项目的规范)?
来到 vue.config.js
这个配置文件,写如下命令
lintOnSave: false
注意:以后如果小黑窗里报错,但凡出现ESLint字样,代表代码不规范
6、v-for必须加key才符合VScode规范
:key只能给字符串或数值类型,所以建议:有id给id,没id给下标
<li v-for="(item,index) in list" :key="index">{{ item }}</li> // item为字符串或数字
<li v-for="(item,index) in list" :key="item.id">{{ item }}</li> // item为对象且有id属性时
注意:在VScode标准语法规范里,写 v-for
必须要加 :key
属性;如果不写,语法检查通不过会标红报错(不是真正的报错)
八、组件
包含另外一个组件的叫父组件;被包含的组件叫子组件;
Vue组件是对UI结构的封装,提高了复用性。
1、导入与注册组件
(1)在文件夹components 中新建vue组件文件,相当于封装了一个组件;
(2)导入组件
import 组件名 from '组件的路径' // 写在父组件的<script>中,组件名可以和
例:
import mySon from './components/mySon.vue'
import mySon from './components/mySon' 可以省略后缀,意为先寻找这个的.js文件,在找.vue文件
(3)注册组件
components: { 写在与data平级的位置,注册的所有组件都写在这个components中
组件名1,
组件名2, ...
}
// 例
components: {
mySon, // 注册组件mySon,是 mySon: mySon 的简写
xx:mySon, // 注册组件mySon,并改名为xx,所以后面用要用 <xx /> 来使用
}
(4)使用组件 (在想展示的位置,写一个这个组件的标签即可)
<组件名 />
例:
<mySon />
<my-son /> 使用时支持把驼峰拆开
<xx /> 使用改名的组件
2、组件的style的属性
(1)scoped (样式只作用于当前文件)
默认情况下(没使用scoped时),某个组件里写的样式,都是全局样式,别的组件也能匹配到此样式
(1.1)scoped使用方法
在 style
标签上加一个 scoped
,就代表当前 style
里的样式,只生效于当前 组件
,不会影响到别的组件;
<style scoped>
该vue文件的样式
</style>
建议:以后在组件里写样式时,记得要加 scoped
,这样不会造成样式冲突;
(1.2)scoped原理
(1.2.1)为什么默认情况下某个组件里的样式是全局样式?
因为在app.vue
里导入某个组件,那就相当于把这个组件的内容全部放到app.vue
里了,就相当于网页里整体内容都是在app.vue
里的,所以某个组件的样式也是放到app.vue
里了,那就相当于是全局样式了;
(1.2.2)为什么加scoped
就能让它不是全局的样式了?
原理:用到了 属性选择器
如果某个组件加了 scoped
,它会把组件里所有的标签加上 data-v-hash值
的自定义属性(图一),并且把样式都变成[data-v-hash]
的 属性选择器
的样式(图二),既然是属性选择器,就代表一定要有这个属性,才能匹配到样式,没有这个属性就匹配不到,那么谁有这些属性呢?就只有加了 scoped
的组件才有这个属性,所以通过这样的操作,保证了加了scoped
的样式只给当前组件使用。
(1.2.3)如果另外一个组件
也加了 scoped
能用到这个组件里的样式吗?
不能,因为hash值不一样,这个hash值是根据组件名产生的(每一个都不一样),
这样就保证了,谁加scoped
,就只是给谁自己用,别人用不到。
(2)深度作用选择器(将样式穿透给子组件)
使用情况:若一个组件加了 scoped
,那么它的样式默认只能给自己用,无法给子组件用,如果希望某个样式能穿透给子组件用(即父组件的某个样式要做用于子组件)
,就用 深度作用选择器
深度作用选择器 | 注意 |
---|---|
>>> | vscode不认识,会有波浪线(不是真的报错) |
/deep/ | |
::v-deep(推荐) | 不会有波浪线 |
语法:
::v-deep 选择器{ 这里的样式就能给子组件用了 }
>>> 选择器{ 这里的样式就能给子组件用了 }
/deep/ 选择器{ 这里的样式就能给子组件用了 }
(3)vue的style标签支持less语法
步骤:(1)安装 less:npm install less less-loader@7.3.0 -D
(2)在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式
例:
<style lang='less'>
h1{
color: red;
span{
color: blue;
}
}
</style>
3、prop
prop 的配置选项
3.30 配置时要用对象
(1)type—给 prop 设置数据类型
(2)default—给 prop 设置默认值
(3)required—给 prop 设置是否必填
4、组件通信
(1)父传子
把父的数据传递给子组件
单向数据流
- 父可以直接给子传递数据,并且传递时
父的数据变了,子的数据会跟着变
- 单向数据流只允许
父数据改动能自动流入到子
,但是子的数据不能流入到父
- 所以如果直接在
子里
修改父传递来的数据,当修改的是栈
上的数据(即不能修改简单类型的数据,能修改复杂类型的数据),会报错。所以如果传递的是对象,在子里改了对象的属性,那是不会报错的,也允许的;
(2)子传父
子里的数据给父 或 父传给子的数据再给父
使用步骤:
a.在子
需要改父数据的时候,使用 $emit
发送一条通知
this.$emit('自定义的通知名', 数据)
例
this.$emit('changeMsg', '喵喵喵')
b.父里监听这个通知
<子组件 @自定义的通知名="函数"/>
例:
<son @changeMsg='fn'/> // fn写在父组件中
fn (val) {
val就是子传递过来的数据,就可将val进行处理
}
(3)兄弟(非父子关系)组件传值-eventBus
不一定是有一个共同的父组件的2个组件之间传值,可能是更复杂的关系。
原理:在vue中,实例化一个新的vue对象,把它作为bus,将bus作为转换平台,实现非父子关系的传值。
方法一:用了一个独立的js
文件来准备 bus
这个对象
准备文件的好处是:谁要用谁导入即可;
缺点:得多准备这么个文件,然后要写导入导出的语法
步骤:
a. 来到 src目录
,新建 eventBus
的文件夹,里面放一个 index.js
此index.js文件中写:
// 导入vue
import Vue from "vue";
// 实例化一个vue对象,并暴露出去(因为要通过它传值,所以别的组件要用)
export default new Vue()
b. 发送、接收数据的组件都要导入index.js(bus文件)
c. 使用 bus.$emit 发送数据;使用 bus.$on 接收数据;先点关注再传递数据,才能成功
发送数据的组件:
import bus from 'bus的路径'
bus.$emit('自定义的事件名', 数据) // 导入的对象不要加this,如果是访问data中的数据加this
例:
bus.$emit('son1Send', "haha")
bus.$emit('son1Send', this.list)
接收数据的组件:
import bus from 'bus路径'
bus.$on('自定义事件', (data) => {
// 当别的组件传过来值时会自动调用这个回调函数
// 参数data就是传递过来的数据
})
例:
方法二:将eventBus挂载到原型对象上
a. 挂载Vue的原型对象一般是放在 main.js
因为 main.js
是入口文件,相当于项目一运行就执行的代码,所以如果写到这里面,就代表项目一打开,vue的原型里就有bus对象了
Vue.prototype.$bus = new Vue()
注意:挂载到Vue原型上的属性,前面名字要加 $,方便区分是组件自己的数据还是原型上的数据
this.emit // 组件data里的变量
this.$emit // 原型上的$emit
b. 后面组件之间的传值,用...
this.$bus.$on('自定义事件', (data) => { 参数data是传递过来的数据 }) // 订阅
this.$bus.$emit('自定义的事件名', 数据) // 发布
补充:vue-cli脚手架里的路径说明
(1)如果文件后缀是.js或者.vue,可以省略后缀
import bus from '../eventBus/index'
(2)如果一个文件夹里的文件叫package.json、index.js 或 index.vue,文件名都可以省略
import bus from '../eventBus'
(3)在脚手架中,用@代表src目录
import bus from '@/eventBus'
5、ref与$refs(可非官方实现子传父)
作用:ref配合$refs可以方便在vue中找到 dom元素
以及 组件对象
用法:
1. 对着标签添加 ref 属性,指定一个名字
<标签 ref="名字" />
<组件 ref="名字" />
2. 在JS中,就可以通过 this.$refs.名字 就可以到这个标签对象
例:获取标签
this.$refs.h1.style
如果获取的是组件,那么拿到的是组件对象,相当于组件内部的this,所以可快速父传子
this.$refs.son1 // 找到son1组件对象
this.$refs.son1.list.push(999) // 给son1子组件里的数组list添加元素
this.$refs.son1.sayHi() // 调用子组件里的方法
九、插槽
1、插槽分类
作用:让封装的组件内某个区域不写死(放自己相放的内容),增加组件的复用性。
使用步骤:在不想写死的地方写一个 <slot />
,就相当于在这个位置挖了个坑占了个位,外界使用组件时传递了什么。slot
这个位置就显示什么
注意:给插槽传递内容时,规范是建议把所有内容包在 template
标签里(方便具名插槽操作)
(1)默认插槽
<slot>默认内容</slot>
插槽的双标签内可写默认内容;
如果外界如果没传内容,那么就以默认内容来显示;
如果以后外界传了内容,那么就以传递的内容在slot里显示;
(2)具名插槽
使用情况:当组件有多个地方不想写死是,所以就要准备多个具名插槽,使每处插入的内容不同。
具名插槽语法:
<slot name="名字"> 默认内容 </slot>
传递给特定插槽的内容:
<template v-slot:插槽名字>
要传递的内容
</template>
(3)作用域插槽
使用情况:默认情况下,组件内部的数据是无法给外界使用的。作用域插槽可以把当前作用域里的数据暴露出去,给别的组件使用。
语法:
1. 暴露(向外传递)数据
<slot name="插槽名字" :数据名1="数据1" :数据名2="数据2" />
2. 接收数据 若插槽无名字,则 v-slot="对象名"
<template v-slot:插槽名字="对象名">
对象名表示的对象接收传递过来的所有数据
</template>
2、插槽简写
总结: v-slot:简写成 #
(1)默认插槽
<template>
不写名字就是默认插槽
</template>
<template v-slot:default>
默认插槽的名字叫 default,简写 #default
</template>
<template v-slot="接收数据的对象名">
如果要拿到作用域里的数据,这时候不建议写#,写#要写 #default="接收数据的对象名"
</template>
(2)具名插槽
1、具名插槽接收数据
完整写法
<template v-slot:名字="obj">
</template>
简写写法
<template #名字="obj">
</template>
2、不需要接收数据的具名插槽
完整写法
<template v-slot:名字>
</template>
简写
<template #名字>
</template>
3、已废弃插槽的语法
废弃语法是在vue2.6里开始废弃,vue2.6时还能用,在vue3以后彻底不能用;
废弃语法有两个语法:
1.用来指定给哪个插槽用
slot="插槽名字" 等于 v-slot:"插槽名字"
2.用来拿到插槽内部的数据,都放到obj里
slot-scope="obj" 等于 v-slot="接收数据的对象名"
例:<template #body="obj">
废弃语法是 <template slot="body" slot-scope="obj">
十、Vue的生命周期
Vue的生命周期指的是vue对象(组件)从创建到销毁的一系列阶段。
生命周期钩子总结
4个阶段8个钩子
组件缓存会产生两个钩子(activated、deactivated),所以最终生命周期钩子共10个
阶段 | 钩子 | 特点 | 使用场景 |
---|---|---|---|
创建阶段 | beforeCreate | 创建前,是最早的钩子,但是还无法访问data中的数据和methods的方法 | |
created (常用) | 创建后,是最早能访问到数据的钩子 | 场景1:开发中,要页面一打开就要发请求拿数据,所以越早越好,但是发请求拿到数据后,还要把数据保存到vue的变量里,所以写到 场景2: eventBus要订阅兄弟组件传值 | |
渲染阶段 | beforeMount | 渲染前,还无法访问到真实DOM | |
mounted (常用) | 渲染后,可以访问到真实dom,是最早能访问到真实dom的钩子 | 有些插件例如 | |
更新阶段 | beforeUpdate | 更新前,指数据发生改变立即调用的钩子,但是此时dom还没重新更新 | |
updated | 更新后,指数据发生改变并且dom已经更新调用的钩子 | ||
销毁阶段 | beforeDestroy | 销毁前,还能访问到子组件 | 两个钩子任选其一 可以做一些回收工作: 1、停止定时器 2、如果销毁了,应该把监听兄弟组件传值给关了(取关)
|
destroyed | 销毁后,访问不到子组件了 |
销毁钩子停止定时器
<template>
<div class="son2">
<h3>我是son2.vue</h3>
<button >我要拿son1的数据 -- 相当于点了关注</button>
</div>
</template>
<script>
export default {
data() {
return {
timer: null, // 声明变量,用于开启关闭定时器
};
},
created() {
// 当这个组件一诞生就自动订阅了组件传值(相当于关注了公众号)
this.$bus.$on("son1Send", (data) => {
console.log("son1给数据了,数据是:", data);
});
// 开启定时器
this.timer = setInterval(() => {
console.log('我是son2里开的定时器')
}, 1000);
},
destroyed () { 或 beforeDestroy
console.log('son2已销毁')
// 移除定时器
clearInterval(this.timer)
// 在组件销毁时取关(避免内存泄露)
this.$bus.$off("son1Send")
}
};
</script>
进阶面试题:当一个组件里也有子组件时,那么父组件和子组件穿插的的生命周期是怎样的?
父beforeCreate->父created->父beforeMount->子的beforeCreate->子的created->子beforeMount->子的mounted->父的mounted->谁数据改变就调用谁的beforeUpdate和updated -> 如果此时销毁父组件,会调用父的 beforeDestroy -> 子的beforeDestroy->子destroyed->父destroyed
十二、
1、注册全局
全局组件、全局过滤器、全局指令都是在 main.js 里注册(写在new Vue上边)。
(1)全局组件
注册全局组件
Vue.component('组件的注册名称', 导入的组件名称)
例:全局组件panel
import panel from './components/panel'
Vue.component('panel', panel)
(2)全局过滤器
注册全局过滤器
Vue.filter('过滤器名', (原数据) => {
return 处理后的结果
})
例:全局过滤器formatTime
Vue.filter('formatTime', (val) => {
return '2022-03-30'
})
(3)全局指令
注册全局指令
Vue.directive('指令名', {
bind (el, obj) { },
update (el, obj) { }
})
简写:写成函数的形式
Vue.directive('指令名', (el, obj) => { })
例:全局指令v-color
// Vue.directive('color', {
// bind (el, obj) {
// el.style.color = obj.value
// },
// update (el, obj) {
// el.style.color = obj.value
// }
// })
全局指令v-color简写
Vue.directive('color', (el, obj) => {
el.style.color = obj.value
})
2、动态组件
作用:让某个区域里的组件可来回切换,你指定哪个组件,它就显示那个组件;
实现方法:
在需要切换组件的位置,写一个 component标签 ,数据变量等于哪个组件名,那么这个位置就会显示什么组件
<component :is="数据变量"/>
3、组件缓存
<keep-alive>
// 被缓存的组件
</keep-alive>
使用 v-if 隐藏组件时,会将组件销毁,因为v-if 是通过直接操作dom树的添加或移除来达到看到或者看不到的效果
组件缓存的作用:不想让组件销毁
,让其依然保留在内存中(即当组件被缓存时,它就不会被销毁);
注意:如果组件被缓存起来了,它的 销毁钩子
就不会被调用了,取而代之会多两个钩子,所以最终生命周期钩子10个
- activated: 当组件显示时调用(激活时)
- deactivated:当组件隐藏时调用
登入切换案例—动态组件、组件缓存练习
<template>
<div>
<button @click="comName = 'qrcode'">二维码登入</button>
<button @click="comName = 'sologinFomen'">表单登入</button>
<!-- 动态切换方法1: -->
<keep-alive>
<component :is="comName" />
<!-- 当点击第一个按钮时,comName = 'qrcode',就显示qrcode组件;第二个按钮也是如此 -->
</keep-alive>
<!-- <keep-alive> 组件缓存,当来回切换时,表单input的内容不会销毁 -->
<!-- 动态切换方法2:v-if和v-else-if -->
<!-- <qrcode v-if="comName == 'qrcode'" />
<sologinFomen v-else-if="comName == 'sologinFomen'" /> -->
</div>
</template>
<script>
// 导入切换时显示不同的两个组件
import sologinFomen from "./components/loginFome.vue";
import qrcode from "./components/qrcode.vue";
export default {
components: { sologinFomen, qrcode },
data() {
return { comName: qrcode }; // 设置comName的默认值
},
};
</script>
4、单页面应用
单页面应用(Single Page Application):整个网站的项目其实只有一个页面(整个网站只有一个 index.html
,用页面里内容的切换 实现 看起来的页面跳转效果,实际上页面并没有跳转,不会有刷新)
优点:
- 切换页面时速度快
- 可以减少服务器的请求
缺点:
- 默认情况下,第一次加载会慢一点
- 不利于SEO(搜索引擎优化)
- # 号后面的部分,专业术语为“Hash 地址”
- Hash 地址发生变化,不会导致浏览器页面的刷新
- Hash 地址发生变化,会形成浏览器历史记录
- vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的
十二、路由 (vue-router)
官方文档地址(给vue2用的路由文档):Vue Router-路由文档
路由:可以实现用一个路径对应显示一个组件;路由(核心插件)是实现单页面应用的关键所在。
1、路由的基本使用步骤
方法一:
(1)下载路由插件
npm i vue-router@3.x // 路由的3.x版本对应vue2,路由的4.x版本对应vue3
(2)在 main.js 中粘贴以下代码
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 1. 使用模块化机制编程时,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
import VueRouter from 'vue-router'
// 给Vue安装路由(给vue对象提供路由支持)
Vue.use(VueRouter)
// 2. 导入路由要显示的组件 注意:以后路由对应的组件一般都是放到 `src/views`文件夹里
// import 组件对象 from '组件路径'
import discover from './views/discover'
// 3. 准备路由规则:设置什么路径对应什么组件
const routes = [
// 路由重定向
// { path: '/a路径', redirect: '/b路径'},
// path这里就是路径;component这里就是对应的组件
{ path: '/a', component: discover },
// 访问了一个没有设置过的路径,都会跳转到 404 页面(notFound组件)
// { path: '*', component: notFound },
]
// 4. 创建 路由router 实例
const router = new VueRouter({
routes, // (缩写) 相当于 routes: routes
// mode: 'history' // 将hash模式转换成history模式
})
// 5. 把路由对象挂载到vue的根实例
// 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载
new Vue({
router,
render:h=>h(App)
}).$mount('#app')
(3)在组件中放置一个路由出口 注:路由出口就是路由要显示到的位置
<router-view /> // 用了路由,vue文件中就不必导入/注册组件了
方法二(规范):
(1)安装 vue-router
(2)创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:
// 1.导入Vue及VueRouter
import Vue from "vue";
import VueRouter from "vue-router";
// 2.将VueRouter安装为Vue的插件,给vue对象提供路由支持
Vue.use(VueRouter)
// 3.创建路由的实例对象
const router = new VueRouter()
// 4.导出路由实例对象
export default router
(3)将路由模块挂载到 vue 的根组件中
在 src/main.js 入口文件中,导入并挂载路由模块
(4)配置路由规则
在 src/router/index.js 路由模块中,在router的实例对象中通过 routes 数组声明路由的匹配规则
routes: [
{ path: 'hash地址', component:组件 }
{ path: '/home', component: home },
]
(5)声明路由链接和路由占位符
在组件vue文件中使用
1.路由链接
<router-link to="路径">导航名</router-link>
<router-link to="/about">关于</router-link>
2.路由占位符,将组件渲染到这里
<router-view></router-view>
2、使用a标签做路由跳转
<a href="#/a">组件discover</a> // 点击a标签显示地址对应的组件
注意:使用路由后,默认的路径前面都有 # ,所以路径应该这样写: #/路径
3、路由的模式
路由的模式 | 特点 |
---|---|
hash模式 (默认) | 网址上的路径前面要包含 # ,如 域名/#/my |
history模式 | 像正常网址一样,没有 # 号,如 域名/my ; 如果项目正式上线,想用 |
将hash模式转换成history模式
main.js文件中在实例化 router 对象时,加一个mode,写明模式名字
const router = new VueRouter({
routes,
mode: 'history'
})
4、router-link 路由链接,实现跳转(使用)
语法:
<router-link to="路径">文字</router-link>
例:
<router-link to="/discover">发现音乐</router-link>
router-link 的本质就是 a标签;
优点:
- 既支持hash模式,也支持history模式;如果是hash模式,它最终会自动在路径前面加 #
- 谁被点,谁会自动加两个类:router-link-exact-active 和 router-link-active,方便制作点谁谁高亮的效果(只要对 router-link-active 写一个高亮样式即可);
5、路由重定向
路由重定向:访问a路径,自动帮你跳转到b路经;可用于自动跳转到默认主页面;
语法:在 main.js的路由规则 里加如下一条
{ path: '/a路径', redirect: '/b路径'} // 访问a路径,自动帮你跳转到b路经
{ path: '', redirect: '/b路径'} // 空字符串就代表什么路径都没输入时,自动跳转到b路经
{ path: '/', redirect: '/b路径'} // 空字符串和'/' 表示的含义一样,即访问域名自动跳转到默认的主页面
如:
{ path: '/d', redirect: '/discover'} // 如果路径上输入的是 /d, 它会自动帮你跳转到 /discover
6、路由404页面
要实现的效果:访问了一个没有设置过的路径
,都会跳转到 404
页面
在 main.js的路由规则 的最后添加一条规则
const routes = [
.......
.......
{ path: '*', component: notFound },
]
注意: 1. 一般情况下404页面都是放在最后;
2. *代表除了上面以外的其他路径,就访问notFound组件(404页面);
3. 需要提前准备好 notFound组件并导入;
7、动态路由
作用、优点:提高路由规则的复用性。
语法:
{ path: '/路径/:动态参数名', component: 组件对象 }
例:
{ path: '/movie/:id', component: movie },
// 这里的:id就代表需要路径上在movie后面添加一个id参数;
// 可以访问/movie/1,/movie/2,/movie/3,...
// 访问这个路由时,必须携带id作为参数,不携带就访问不了;
8、获取动态参数
动态路由可以传参:访问一个路由的同时也把参数携带过去。
(1)$route.params.参数名
传递动态参数后,在这个组件内部拿到这个动态参数值 ($route记录了当前路由的信息)
语法:$route.params.参数名
注意:如果这段代码不是写在html,而是写在 js,那记得前面要加 this
如:
{{ $route.params.id }} // 参数名如果叫id,那就是 .id
(2)props: ['动态参数名']
使用 $route.params 获取动态参数比较麻烦,所以提供了通过 props
来接收参数。
步骤:
1. 在要访问的组件里,声明 props,动态参数名要跟规则里的动态参数名保持一致
props: ['动态参数名']
2. 在main.js的路由规则里,开启props接收参数的功能
{ path: '/路径/:参数名', component: movie, props: true },
9、路由嵌套
路由嵌套:一个路由里又包含了另一个路由。
- 包含另外路由的叫父路由,被包含的叫子路由;
- 包含所有的路由叫一级路由;一级路由里的子路由叫二级路由;
使用步骤:
a. 准备二级路由要对应的页面组件(也就是.vue文件);
b. 来到 main.js路由规则的地方,先做组件的导入;
c. 将二级路由的规则写到父路由的 children
里
const routes = [
.....
{
path: '/discover',
component: discover, // discover是一级路由的组件
children: [
{ path: 'topList', component: topList }, // topList、songList是二级路由的组件
{ path: 'songList', component: songList },
]
},
]
注意:1. 子路由的路径不加/
2. 不加/就代表需要先拼接父路由的路径再加自己的路径
3. 虽然加了/它还是二级路由,只不过地址上就不能拼接父路由路径了,不会访问到二级路由的组件
d. 在父路由对应的组件里,写路由链接和路由占位符
1.路由链接 注:路径要从父组件开始
<router-link to="/about/tab1">tab1</router-link>
2.路由占位符,二级导航要放在父组件哪里就写在哪里
<router-view></router-view>
10、声明式导航 & 编程式导航
(1)声明式导航
(2)编程式导航
(2.1)this.$router.push('hash地址')
作用:跳转到指定 hash 地址,并增加一条历史记录;
(2.2)this.$router.go(数值n)
作用:当数值为正、负数时,实现导航历史前进、后退
Vue人力资源中台项目:vue全系列课程/人力资源项目