教程链接
https://www.bilibili.com/video/BV1Zy4y1K7SH/
官方笔记
https://www.yuque.com/cessstudy/kak11d/hfeef2
课程笔记
el 与 data 的两种写法
el有2种写法
- 创建Vue实例对象的时候配置el属性
- 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
data有2种写法
目前哪种写法都可以,以后到组件时,data必须使用函数,否则会报错
- 对象式:
data: { }
- 函数式:
data() { return { } }
由Vue管理的函数,一定不要写箭头函数,否则 this 就不再是Vue实例了
MVVM模型 & 数据代理
MVVM模型
- M:模型
Model
,data中的数据 - V:视图
View
,模板代码 - VM:视图模型
ViewModel
,Vue实例
Object.defineproperty方法
Object.defineProperty(person, 'age', {
// value:18,
// enumerable:true, // 控制属性是否可以枚举,默认值是false
// writable:true, // 控制属性是否可以被修改,默认值是false
// configurable:true // 控制属性是否可以被删除,默认值是false
// 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
console.log('有人读取age属性了')
return number
},
// 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
console.log('有人修改了age属性,且值是', value)
number = value
}
})
数据代理
通过一个对象代理对另一个对象中属性的操作(读/写)
- Vue中的数据代理
通过vm对象来代理data对象中属性的操作(读/写) - Vue中数据代理的好处
更加方便的操作data中的数据 - 基本原理
- 通过
object.defineProperty()
把data对象中所有属性添加到vm上 - 为每一个添加到vm上的属性,都指定一个
getter
setter
- 在
getter
setter
内部去操作(读/写)data中对应的属
- 通过
Vue将data中的数据拷贝了一份到_data属性中,又将_data里面的属性提到Vue实例中(如name),通过defineProperty实现数据代理,这样通过geter/setter操作 name,进而操作_data中的 name。而_data又对data进行数据劫持,实现响应式
事件处理
事件的基本用法
- 使用v-on:xxx或@xxx绑定事件,其中 xxx 是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,不要用箭头函数,否则 this 就不是vm了
- methods中配置的函数,都是被 Vue所管理的函数,this 的指向是vm或组件实例对象
@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参
Vue中的事件修饰符
修饰符可以连续写,比如可以这么用:@click.prevent.stop="showInfo"
prevent
阻止默认事件(常用)stop
阻止事件冒泡(常用)once
事件只触发一次(常用)capture
使用事件的捕获模式self
只有event.target是当前操作的元素时才触发事件passive
事件的默认行为立即执行,无需等待事件回调执行完毕
键盘事件
键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用
- Vue中常用的按键别名
- 回车
enter
- 删除
delete
捕获“删除”和“退格”键 - 退出
esc
- 空格
space
- 换行
tab
特殊,必须配合keydown
去使用 - 上
up
- 下
down
- 左
left
- 右
right
- 回车
- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为
kebab-case
(多单词小写短横线写法) - 系统修饰键(用法特殊)
ctrl
alt
shift
meta
(meta就是win键)- 配合
keyup
使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
指定ctr+y
使用@keyup.ctr.y
- 配合
keydown
使用:正常触发事件
- 配合
- 也可以使用
keyCode
去指定具体的按键(不推荐) Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
计算属性 & 侦听属性
计算属性
- 定义:要用的属性不存在,需要通过已有属性计算得来
- 原理:底层借助了
Objcet.defineproperty()
方法提供的getter
和setter
- get函数什么时候执行?
- 初次读取时会执行一次
- 当依赖的数据发生改变时会被再次调用
- 优势:与
methods
实现相比,内部有缓存机制(复用),效率更高,调试方便 - 备注
- 计算属性最终会出现在
vm
上,直接读取使用即可 - 如果计算属性要被修改,那必须写
set
函数去响应修改,且set
中要引起计算时依赖的数据发生改变 - 如果计算属性确定不考虑修改,可以使用计算属性的简写形式
- 计算属性最终会出现在
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
firstName:'张',
lastName:'三',
x:'你好'
},
computed: {
//完整写法
// fullName: {
// get() {
// console.log('get被调用了')
// return this.firstName + '-' + this.lastName
// },
// set(value) {
// console.log('set', value)
// const arr = value.split('-')
// this.firstName = arr[0]
// this.lastName = arr[1]
// }
// }
// 简写
fullName() {
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
})
侦听属性基本用法
定事件的时候:@xxx=“yyy” yyy可以写一些简单的语句
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
}
})
watch
监视属性
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视,既可以监视data,也可以监视计算属性
- 配置项属性
immediate:false
,改为true
,则初始化时调用一次handler(newValue,oldValue)
- 监视有两种写法
- 创建Vue时传入
watch: {}
配置 - 通过
vm.$watch()
监视
- 创建Vue时传入
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
// 方式一
/* watch:{
isHot:{
immediate:true,
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
} */
})
// 方式二
vm.$watch('isHot', {
immediate: true, // 初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
})
深度侦听
- 深度侦听
- Vue中的
watch
默认不监测对象内部值的改变(一层) - 在
watch
中配置deep:true
可以监测对象内部值的改变(多层)
- Vue中的
- 注意
- Vue自身可以监测对象内部值的改变,但Vue提供的
watch
默认不可以 - 使用
watch
时根据监视数据的具体结构,决定是否采用深度监视
- Vue自身可以监测对象内部值的改变,但Vue提供的
computed
和watch
之间的区别computed
能完成的功能,watch
都可以完成watch
能完成的功能,computed
不一定能完成,例如watch
可以进行异步操作
- 两个重要的小原则
- 所有被Vue管理的函数,最好写成普通函数,这样
this
的指向才是vm
或组件实例对象 - 所有不被Vue所管理的函数(定时器的回调函数、
ajax
的回调函数等、Promise
的回调函数),最好写成箭头函数,这样this
的指向才是vm
或组件实例对象
- 所有被Vue管理的函数,最好写成普通函数,这样
绑定样式 条件渲染
绑定样式
- 写法:
:class="xxx"
,xxx
可以是字符串、数组、对象 :style="[a,b]"
其中a、b是样式对象:style="{fontSize: xxx}"
其中xxx
是动态值- 字符串写法适用于
:
类名不确定,要动态获取 - 数组写法适用于
:
要绑定多个样式,个数不确定,名字也不确定 - 对象写法适用于
:
要绑定多个样式,个数确定,名字也确定,但不确定用不用
- 字符串写法适用于
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div><br/><br/>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div><br/><br/>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div><br/><br/>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div><br/><br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
条件渲染
v-if
- 写法 跟
if else
语法类似
v-if=“表达式”
v-else-if=“表达式”
v-else - 适用于:切换频率较低的场景,因为不展示的DOM元素直接被移除
- 注意:
v-if
可以和v-else-if
v-else
一起使用,但要求结构不能被打断
v-show
- 写法:
v-show="表达式"
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
display: none
- 备注:使用
v-if
的时,元素可能无法获取到,而使用v-show
一定可以获取到
template
标签不影响结构,页面html中不会有此标签,但只能配合 v-if
,不能配合 v-show
列表渲染 数据监视
列表渲染
v-for指令
- 用于展示列表数据
- 语法:
<li v-for="(item, index) of items" :key="index">
,这里key可以是index,更好的是遍历对象的唯一标识 - 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
key 的作用与原理
面试题:react vue
中的 key
有什么作用?(key的内部原理)
- 虚拟
DOM
中key
的作用:key
是虚拟DOM
中对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM
,随后Vue进行新虚拟DOM
与旧虚拟DOM
的差异比较,比较规则如下 - 对比规则
- 旧虚拟
DOM
中找到了与新虚拟DOM
相同的key
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟
DOM
中未找到与新虚拟DOM
相同key
创建新的真实DOM
,随后渲染到到页面
- 旧虚拟
- 用
index
作为key
可能会引发的问题- 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实
DOM
更新 ==> 界面效果没问题,但效率低 - 若结构中还包含输入类的
DOM
:会产生错误DOM
更新 ==> 界面有问题
- 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实
- 开发中如何选择
key
?- 最好使用每条数据的唯一标识作为
key
,比如 id、手机号、身份证号、学号等唯一值 - 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用
index
作为key
是没有问题的
- 最好使用每条数据的唯一标识作为
数据监视
- vue会监视data中所有层次的数据
- 如何监测对象中的数据?
通过setter实现监视,且要在new Vue()时就传入要监测的数据- 对象创建后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
- 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
- 在Vue修改数组中的某个元素一定要用如下方法
push()
pop()
unshift()
shift()
splice()
sort()
reverse()
这几个方法被Vue重写了
Vue.set()
或vm.$set()
特别注意:Vue.set()
和vm.$set()
不能给vm
或vm
的根数据对象(data等)
收集表单数据 过滤器
收集表单数据
- 收集表单数据
- 若
<input type="text"/>
,则v-mode
l收集的是value
值,用户输入的内容就是value
值 - 若
<input type="radio"/>
,则v-model
收集的是value
值,且要给标签配置value
属性 - 若
<input type="checkbox"/>
- 没有配置
value
属性,那么收集的是checked
属性(勾选 or 未勾选,是布尔值) - 配置了
value
属性v-model
的初始值是非数组,那么收集的就是checked
(勾选 or 未勾选,是布尔值)v-model
的初始值是数组,那么收集的就是value
组成的数组
- 没有配置
- 若
v-model
的三个修饰符lazy
失去焦点后再收集数据number
输入字符串转为有效的数字trim
输入首尾空格过滤
过滤器(Vue3 已经移除)
- 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
- 注册过滤器:
Vue.filter(name, callback)
全局过滤器new Vue {filters: {}}
局部过滤器
- 使用过滤器:
{{ xxx | 过滤器名}}
或v-bind:属性 = "xxx | 过滤器名"
- 备注:
- 过滤器可以接收额外参数,多个过滤器也可以串联
{{ xxx | 过滤器名(参数2)}}
或v-bind:属性 = "xxx | 过滤器名(参数2)"
。接收时管道参数默认在第一个,其他参数第二个依次类推。 - 并没有改变原本的数据,而是产生新的对应的数据
- 过滤器可以接收额外参数,多个过滤器也可以串联
- 处理时间的库
moment
体积较大dayjs
轻量级
new Vue({
el:'#root',
data:{
time:1626750147900,
},
// 局部过滤器
filters:{
timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
return dayjs(value).format(str)
}
}
})
内置指令 自定义指令
内置指令
之前学过的指令
v-bind
单向绑定解析表达式,可简写为:v-model
双向数据绑定v-for
遍历数组 / 对象 / 字符串v-on
绑定事件监听,可简写为@v-show
条件渲染 (动态控制节点是否展示)v-if
条件渲染(动态控制节点是否存存在)v-else-if
条件渲染(动态控制节点是否存存在)v-else
条件渲染(动态控制节点是否存存在)
v-text
指令
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:
v-text
会替换掉节点中的内容,{{xxx}}
则不会,更灵活
v-html
指令
- 作用:向指定节点中渲染包含html结构的内容
- 与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xxx}}
则不会v-html
可以识别html结构
- 严重注意
v-html
有安全性问题!!!- 在网站上动态渲染任意html是非常危险的,容易导致 XSS 攻击
- 一定要在可信的内容上使用
v-html
,永远不要用在用户提交的内容上!!!
v-cloak
指令
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉
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
所在节点在初次动态渲染后,就视为静态内容了- 以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能
v-pre
指令
- 跳过
v-pre
所在节点的编译过程 - 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
自定义指令 directives
- 局部指令
new Vue({
directives:{
指令名:配置对象
}
})
new Vue({
directives:{
指令名:回调函数
}
})
- 全局指令
Vue.directive(指令名, 配置对象)
或
Vue.directive(指令名, 回调函数)
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding) { // element就是DOM元素,binding就是要绑定的
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
}
})
配置对象中常用的3个回调函数
bind(element, binding)
指令与元素成功绑定时调用inserted(element, binding)
指令所在元素被插入页面时调用update(element, binding)
指令所在模板结构被重新解析时调用element
就是DOM元素,binding
就是要绑定的对象,它包含以下属性:namevalueoldValueexpressionargmodifiers
备注
- 指令定义时不加
v-
,但使用时要加v-
- 指令名如果是多个单词,要使用
kebab-case
命名方式,不要用camelCase
命名
new Vue({
el: '#root',
data: {
n:1
},
directives: {
'big-number'(element,binding) {
element.innerText = binding.value * 10
}
}
})
directives: {
// big函数何时会被调用?
// 1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时
// <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2>
/* 'big-number'(element,binding){
// console.log('big')
element.innerText = binding.value * 10
}, */
big(element, binding) {
console.log('big', this) // 🔴注意此处的 this 是 window
// console.log('big')
element.innerText = binding.value * 10
},
fbind: {
// 指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
}
}
}
Vue生命周期
生命周期
- 又名生命周期回调函数、生命周期函数、生命周期钩子
- 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的 this 指向是 vm 或组件实例对象
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
a: false,
opacity: 1
},
methods: {
},
// 🔴Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted
mounted() {
console.log('mounted', this)
setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
}, 16)
},
})
总结
常用的生命周期钩子
mounted
发送ajax
请求、启动定时器、绑定自定义事件、订阅消息等初始化操作beforeDestroy
清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在
beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程了
beforeCreate() {console.log('beforeCreate')},
created() {console.log('created')},
beforeMount() {console.log('beforeMount')},
mounted() {console.log('mounted')},
beforeUpdate() {console.log('beforeUpdate')},
updated() {console.log('updated')},
beforeDestroy() {console.log('beforeDestroy')},
destroyed() {console.log('destroyed')},
组件化编程
模块与组件、模块化与组件化
- 模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
- 为什么:js 文件很多很复杂
- 作用:复用、简化 js 的编写,提高 js 运行效率
- 组件
- 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
- 为什么:一个界面的功能很复杂
- 作用:复用编码,简化项目编码,提高运行效率
- 模块化
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用 - 组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
非单文件组件
- 非单文件组件:一个文件中包含有 n 个组件
- 单文件组件:一个文件中只包含有 1 个组件
基本使用
Vue中使用组件的三大步骤
- 定义组件
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的options
几乎一样,但也有点区别el
不要写,因为最终所有的组件都要经过一个vm
的管理,由vm
中的el
才决定服务哪个容器data
必须写成函数,避免组件被复用时,数据存在引用关系
- 注册组件
- 局部注册:
new Vue()
的时候options
传入components
选项 - 全局注册:
Vue.component('组件名',组件)
- 局部注册:
- 使用组件
编写组件标签如<school></school>
<title>基本使用</title>
<script type="text/javascript" src="../js/vue.js"></script>
<div id="root">
<h2>{{msg}}</h2><hr>
<!-- 第三步:编写组件标签 -->
<school></school><hr>
<student></student><hr>
<hello></hello><hr>
</div>
<div id="root2">
<hello></hello>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//第一步:创建school组件
const school = Vue.extend({
// el:'#root', //组件定义时,一定不要写el配置项,
// 因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
template: `
<div class="demo">
<h3>学校名称:{{schoolName}}</h3>
<h3>学校地址:{{address}}</h3>
<button @click="showName">点我提示学校名</button>
</div>
`,
data() {
return {
schoolName: '尚硅谷',
address: '北京昌平'
}
},
methods: {
showName() {
alert(this.schoolName)
}
},
})
//第一步:创建student组件
const student = Vue.extend({
template: `
<div>
<h3>学生姓名:{{studentName}}</h3>
<h3>学生年龄:{{age}}</h3>
</div>
`,
data() {
return {
studentName: '张三',
age: 18
}
}
})
//第一步:创建hello组件
const hello = Vue.extend({
template: `
<div>
<h3>你好啊!{{name}}</h3>
</div>
`,
data() {
return {
name: 'cess'
}
}
})
//第二步:全局注册组件
Vue.component('hello', hello)
//创建vm
new Vue({
el: '#root',
data: {
msg: '你好啊!'
},
//第二步:注册组件(局部注册)
components: {
school,
student
}
})
new Vue({
el: '#root2',
})
</script>
组件注意事项
关于组件名
- 一个单词组成
- 第一种写法(首字母小写):
school
- 第二种写法(首字母大写):
School
- 第一种写法(首字母小写):
- 多个单词组成
- 第一种写法(
kebab-case
命名):my-school
- 第二种写法(
CamelCase
命名):MySchool
(需要Vue脚手架支持)
- 第一种写法(
- 备注
- 组件名尽可能回避HTML中已有的元素名称,例如:
h2
、H2
都不行 - 可以使用
name
配置项指定组件在开发者工具中呈现的名字
- 组件名尽可能回避HTML中已有的元素名称,例如:
关于组件标签
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
(需要Vue脚手架支持) - 备注:不使用脚手架时,
<school/>
会导致后续组件不能渲染 - 一个简写方式:
const school = Vue.extend(options)
可简写为const school = options
,因为父组件components
引入的时候会自动创建
组件的嵌套
Vue.config.productionTip = false
//定义student组件
const student = Vue.extend({
name: 'student',
template: `
<div>
<h4>学生姓名:{{name}}</h4>
<h4>学生年龄:{{age}}</h4>
</div>
`,
data() {return {name: '尚硅谷',age: 18}}
})
//定义school组件
const school = Vue.extend({
name: 'school',
template: `
<div>
<h3>学校名称:{{name}}</h3>
<h3>学校地址:{{address}}</h3>
<student></student>
</div>
`,
data() {return {name: '尚硅谷',address: '北京'}},
//注册组件(局部)
components: { student }
})
//定义hello组件
const hello = Vue.extend({
template: `<h3>{{msg}}</h3>`,
data() {return {msg: '欢迎来到尚硅谷学习!'}}
})
//定义app组件
const app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div>
`,
components: { school, hello }
})
//创建vm
new Vue({
el: '#root',
template: '<app></app>',
//注册组件(局部)
components: { app }
})
VueComponent
school
组件本质是一个名为VueComponent
的构造函数,且不是程序员定义的,而是Vue.extend()
生成的- 我们只需要写
<school/>
或<school></school>
,Vue 解析时会帮我们创建school
组件的实例对象,即Vue帮我们执行的new VueComponent(options)
- 每次调用
Vue.extend
,返回的都是一个全新的VueComponent
,即不同组件是不同的对象 - 关于
this
指向- 组件配置中
data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this
均是VueComponent
实例对象 new Vue(options)
配置中:data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this
均是 Vue 实例对象
- 组件配置中
VueComponent
的实例对象,以后简称vc
(组件实例对象)Vue 的实例对象,以后简称vm
一个重要的内置关系
- 一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 为什么要有这个关系:让组件实例对象
vc
可以访问到 Vue原型上的属性、方法
单文件组件
School.vue
学校 组件Student.vue
学生 组件App.vue
App 组件,管理其他组件main.js
创建vmindex.html
定义#root 承载vm
Vue CLI 初始化脚手架
初始化脚手架
说明
- Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
- 最新的版本是 4.x
- 文档 Vue CLI
具体步骤
- 如果下载缓慢请配置npm淘宝镜像
npm config set registry http://registry.npm.taobao.org
- 全局安装
@vue/cli npm install -g @vue/cli
- 切换到创建项目的目录,使用命令创建项目
vue create xxx
- 选择使用vue的版本
- 启动项目
npm run serve
- 打包项目
npm run build
- 暂停项目
Ctrl+C
Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行
vue inspect > output.js
脚手架文件结构
. 文件目录
├── 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: 包版本控制文件
render 函数
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
// render函数功能:将App组件放入容器中
// 简写形式
render: h => h(App),
// 完整形式
// render(createElement){
// return createElement(App)
// }
})
关于不同版本的函数
vue.js
与vue.runtime.xxx.js
的区别vue.js
是完整版的Vue,包含:核心功能+模板解析器vue.runtime.xxx.js
是运行版的Vue,只包含核心功能,没有模板解析器
esm
就是ES6 module
- 因为
vue.runtime.xxx.js
没有模板解析器,所以不能使用template
配置项,需要使用render
函数接收到的createElement
函数去指定具体内容
vue.config.js 配置文件
vue inspect > output.js
可以查看到Vue脚手架的默认配置
使用 vue.config.js
可以对脚手架进行个性化定制,和 package.json
同级目录,详见 配置参考 | Vue CLI
module.exports = {
pages: {
index: {
entry: 'src/index/main.js' // 入口
}
},
lintOnSave: false // 关闭语法检查
}
tips:每次改完要重启 npm run serve
Vue CLI ref props mixin plugin scoped
ref 属性
ref
被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象
vc
- 使用方式
- 打标识:
<h1 ref="xxx"></h1>或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>
<script>
import School from './components/School'
export default {
name:'App',
components:{ School },
data() {
return {
msg:'欢迎学习Vue!'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) // 真实DOM元素
console.log(this.$refs.btn) // 真实DOM元素
console.log(this.$refs.sch) // School组件的实例对象(vc)
}
},
}
</script>
props 配置项
props让组件接收外部传过来的数据
- 传递数据
<Demo name="xxx" :age="18"/>
这里age
前加:,通过v-bind
使得里面的18
是数字 - 接收数据
第一种方式(只接收)props:['name', 'age']
第二种方式(限制类型)props:{name:String, age:Number}
第三种方式(限制类型、限制必要性、指定默认值)
props: {
name: {
type: String, // 类型
required: true,// 必要性
default: 'cess'// 默认值
}
}
备注: props
是只读的,Vue底层会监测你对 props
的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制 props
的内容到data中,然后去修改data中的数据
src/App.vue
<template>
<div>
<Student name="李四" sex="女" :age="18"/>
<Student name="王五" sex="男" :age="18"/>
</div>
</template>
src/components/Student.vue
props: {
name: {
type: String, //name的类型是字符串
required: true, //name是必要的
},
age: {
type: Number,
default: 99, //默认值
},
sex: {
type: String,
required: true,
},
},
mixin 混入
- 功能:可以把多个组件共用的配置提取成一个混入对象
- 使用方式
- 定义混入
const mixin = {
data() {....},
methods: {....}
....
}
- 使用混入
- 全局混入
Vue.mixin(xxx)
- 局部混入
mixins:[xxx]
- 全局混入
备注
- 组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data () {
return {
message: 'goodbye',
bar: 'def'
}
},
created () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
- 同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
var mixin = {
created () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
- 如果在
main.js
创建Vue实例对象之前进行mixin
,则会在所有组件中都会进行配置
Vue.config.productionTip = false
Vue.mixin(...) // 混合1
Vue.mixin(...) // 混合2
new Vue({
el: '#root',
render: h => h(App)
})
plugin
插件
- 功能:用于增强Vue,在
install
定义的过滤器、指令、混入、添加的方法等 全局vm
vc
都能用 - 本质:包含
install
方法的一个对象,install
的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据 - 定义插件(见下
src/plugin.js
) - 使用插件:
Vue.use()
- 定义
src/plugin.js
export default {
install(Vue,x,y,z){
//x,y,z时Vue.use(plugins,1,2,3)传的1,2,3
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice', function(value){return value.slice(0,4)})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){element.value = binding.value},
//指令所在元素被插入页面时
inserted(element,binding){element.focus()},
//指令所在的模板被重新解析时
update(element,binding){element.value = binding.value}
})
//定义混入
Vue.mixin({
data() {return {x:100,y:200}},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
- 引入
src/main.js
import Vue from 'vue'
import App from './App.vue'
import plugins from './plugins' // 引入插件
Vue.config.productionTip = false
Vue.use(plugins,1,2,3) // 应用(使用)插件
new Vue({
el:'#app',
render: h => h(App)
})
- 应用
<template>
<div>
<h2>学校名称:{{ name | mySlice }}</h2>
<h2>学校地址:{{ address }}</h2>
<input type="text" v-fbind:value="name">
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷atguigu',
address:'北京',
}
},
methods: {
test(){
this.hello() // 调用vue原型上的方法:vm.__proto__.prototype.hello()
}
},
}
</script>
scoped
样式
- 作用:让样式在局部生效,防止冲突
- 写法:
<style scoped>
Vue中的webpack并没有安装最新版,导致有些插件也不能默认安装最新版,如
npm i less-loader@7
,而不是最新版
<style lang="less" scoped>
.demo {
background-color: pink;
.atguigu {
font-size: 40px;
}
}
</style>
Vue CLI Todo-List案例
组件化编码流程
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
- 一个组件在用:放在组件自身即可
- 一些组件在用:放在他们共同的父组件上(状态提升)
- 实现交互:从绑定事件开始
props
适用于
- 父组件 ==> 子组件 通信
- 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)
使用 v-model
时要切记: v-model
绑定的值不能是 props
传过来的值,因为 props
是不可以修改的
props
传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做
推荐使用
nanoid
代替手动生成id
Vue CLI 本地存储 自定义事件
WebStorage(js 本地存储)
存储内容大小一般支持 5MB 左右(不同浏览器可能还不一样)
浏览器端通过 Window.sessionStorage
和 Window.localStorage
属性来实现本地存储机制
相关API
xxxStorage.setItem('key', 'value')
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值xxxStorage.getItem('key')
该方法接受一个键名作为参数,返回键名对应的值xxxStorage.removeItem('key')
该方法接受一个键名作为参数,并把该键名从存储中删除xxxStorage.clear()
该方法会清空存储中的所有数据
备注
SessionStorage
存储的内容会随着浏览器窗口关闭而消失LocalStorage
存储的内容,需要手动清除才会消失xxxStorage.getItem(xxx)
如果xxx
对应的value
获取不到,那么getItem()
的返回值是null
JSON.parse(null)
的结果依然是null
组件的自定义事件
- 一种组件间通信的方式,适用于:子组件 ===> 父组件
- 使用场景:子组件想给父组件传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)
- 绑定自定义事件
- 第一种方式,在父组件DOM中
<Demo @事件名="方法"/>
或<Demo v-on:事件名="方法"/>
- 第二种方式,在父组件
mounted
中this.$refs.demo.$on('事件名',方法)
方法可以写定义在methods
中的方法名;也可以直接写函数体,但是一定要写箭头函数,否者this
指向有问题 - 若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法
- 第一种方式,在父组件DOM中
<Demo ref="demo"/>
......
mounted(){
this.$refs.demo.$on('atguigu',this.test)
}
- 触发自定义事件
this.$emit('事件名',数据)
- 解绑自定义事件
- 解绑一个
this.$off('事件名')
- 解绑多个
this.$off(['事件1', '事件2'])
- 解绑全部
this.$off()
- 解绑一个
- 组件上也可以绑定原生DOM事件,需要使用
native
修饰符@click.native="show"
- 上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加
native
,加了后就将此事件给组件的根元素 - 注意:通过
this.$refs.xxx.$on('事件名',回调函数)
绑定自定义事件时,回调函数要么配置在methods
中,要么用箭头函数,否则this
指向会出问题
Vue CLI 全局事件总线 消息的订阅与发布
全局事件总线(GlobalEventBus)
- 定义全局事件总线
new Vue({
...
beforeCreate() {
Vue.prototype.$bus = this // 安装全局事件总线,$bus 就是当前应用的 vm
},
...
})
- 使用事件总线
- 接收数据:A组件想接收数据,则在A组件中给
$bus
绑定自定义事件,事件的回调留在A组件自身 - 提供数据:
this.$bus.$emit('xxx',data)
- 接收数据:A组件想接收数据,则在A组件中给
export default {
methods(){
demo(data){...}
}
...
mounted() {
this.$bus.$on('xxx',this.demo)
}
}
- 最好在
beforeDestroy
钩子中,用$off()
去解绑当前组件所用到的事件
消息的订阅与发布(pubsub)
消息订阅与发布(pubsub)消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
使用步骤
- 安装
pubsub:npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default {
methods: {
demo(msgName, data) {...}
}
...
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo)
}
}
- 提供数据:
pubsub.publish('xxx',data)
- 最好在
beforeDestroy
钩子中,使用pubsub.unsubscribe(pid)
取消订阅
Vue CLI $nextTick 过渡与动画
$nextTick
这是一个生命周期钩子
this.$nextTick
(回调函数)在下一次DOM更新结束后执行其指定的回调
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在 nextTick
所指定的回调函数中执行
过渡与动画
Vue封装的过度与动画:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
写法
- 准备好样式
- 元素进入的样式
v-enter
进入的起点v-enter-active
进入过程中v-enter-to
进入的终点
- 元素离开的样式
v-leave
离开的起点v-leave-active
离开过程中v-leave-to
离开的终点
- 元素进入的样式
- 使用
<transition>
包裹要过度的元素,并配置name
属性,此时需要将上面样式名的v
换为name
- 要让页面一开始就显示动画,需要添加
appear
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
<style>
.hello-enter-active{
animation: hello 0.5s linear;
}
.hello-leave-active{
animation: hello 0.5s linear reverse;
}
@keyframes hello {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
- 备注:若有多个元素需要过度,则需要使用
<transition-group>
,且每个元素都要指定key值
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
- 可以不用
@keyframes
直接利用Vue提供的方式定义过渡动画
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave {
transform: translateX(0);
}
- 第三方动画库
Animate.css
<transition-group appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp">
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
Vue中的Ajax 配置代理 slot插槽
Vue脚手架配置代理
本案例需要下载 axios
库 npm install axios
配置参考文档 Vue-Cli devServer.proxy
vue.config.js
是一个可选的配置文件,如果项目的 (和 package.json
同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载。你也可以使用 package.json
中的 vue
字段,但是注意这种写法需要你严格遵照 JSON
的格式来写
方法一
在 vue.config.js
中添加如下配置
module.exports = {
devServer:{
proxy:"http://localhost:5000"
}
}
- 优点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,才会将请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则
module.exports = {
devServer: {
proxy: {
'/api1': { // 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000', // 代理目标的基础路径
pathRewrite: {'^/api1':''}, // 代理往后端服务器的请求去掉 /api1 前缀
ws: true, // WebSocket
changeOrigin: true, // 是否改变来源
},
'/api2': {
target: 'http://localhost:5001',
pathRewrite: {'^/api2': ''},
changeOrigin: true
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
slot 插槽
<slot>
插槽:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件
分类:默认插槽、具名插槽、作用域插槽
使用方式
- 默认插槽
<Category>
<div>html结构1</div>
</Category>
子组件中:Category
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
- 具名插槽
父组件指明放入子组件的哪个插槽slot="footer"
,如果是template
可以写成v-slot:footer
父组件中:
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
- 作用域插槽
scope
用于父组件往子组件插槽放的html结构接收子组件的数据
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
必须要用 <template>
来包裹定义变量
scope="scopeData"
slot-scope="scopeData"
(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
注意:关于样式,既可以写在父组件中,解析后放入子组件插槽;也可以放在子组件中,传给子组件再解析
Vuex
理解 Vuex
Vuex 是什么
- 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- Vuex Github地址
什么时候使用 Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
Vuex 工作原理图
搭建 Vuex 环境
- 下载安装
vuex
npm i vuex
- 创建
src/store/index.js
该文件用于创建Vuex
中最为核心的store
import Vue from 'vue'
import Vuex from 'vuex' // 引入Vuex
Vue.use(Vuex) // 应用Vuex插件
const actions = {} // 准备actions——用于响应组件中的动作
const mutations = {} // 准备mutations——用于操作数据(state)
const state = {} // 准备state——用于存储数据
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
- 在
src/main.js
中创建vm
时传入store
配置项
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 引入store
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App),
store, // 配置项添加store
beforeCreate() {
Vue.prototype.$bus = this
}
})
getters 配置项
- 概念:当
state
中的数据需要经过加工后再使用时,可以使用getters
加工,相当于全局计算属性 - 在
store.js
中追加getters
配置
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
// 创建并暴露store
export default new Vuex.Store({
......
getters
})
- 组件中读取数据
$store.getters.bigSum
<h3>当前求和的10倍为:{{ $store.getters.bigSum }}</h3>
四个 map 方法的使用
mapState
方法:用于帮助映射state
中的数据为计算属性
computed: {
// 借助mapState生成计算属性:sum、school、subject(对象写法一)
...mapState({sum:'sum',school:'school',subject:'subject'}),
// 借助mapState生成计算属性:sum、school、subject(数组写法二)
...mapState(['sum','school','subject']),
},
mapGetters
方法:用于帮助映射getters
中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法一)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法二)
...mapGetters(['bigSum'])
},
mapActions
方法:用于帮助生成与actions
对话的方法,即包含$store.dispatch(xxx)
的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
mapMutations
方法:用于帮助生成与mutations
对话的方法,即包含$store.commit(xxx)
的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
注意: mapActions
与 mapMutations
使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
模块化+命名空间
- 目的:让代码更好维护,让多种数据分类更加明确
- 修改
store.js
为了解决不同模块命名冲突的问题,将不同模块的namespaced: true
,之后在不同页面中引入getteractionsmutations
时,需要加上所属的模块名
const countAbout = {
namespaced: true, // 开启命名空间
state: {x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){ return state.sum * 10 }
}
}
const personAbout = {
namespaced: true, // 开启命名空间
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: { // 分包
countAbout,
personAbout
}
})
- 开启命名空间后,组件中读取
state
数据
// 方式一:自己直接读取
this.$store.state.personAbout.list
// 方式二:借助mapState读取modules属性,用countAbout.sum
...mapState('countAbout'),
// 方式三:借助mapState读取,用 sum
...mapState('countAbout',['sum','school','subject']),
- 开启命名空间后,组件中读取
getters
数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
- 开启命名空间后,组件中调用
dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
- 开启命名空间后,组件中调用
commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
Vue Router 相关理解 基本路由 多级路由
相关理解
vue-router
的理解- vue的一个插件库,专门用来实现SPA应用
- 对SPA应用的理解
- 单页Web应用(
single page web application,SPA
) - 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过
ajax
请求获取
- 单页Web应用(
路由的理解
- 什么是路由?
- 一个路由就是一组映射关系(
key - value
) key
为路径,value
可能是function
或componen
- 一个路由就是一组映射关系(
- 路由分类
- 后端路由
- 理解:
value
是function
,用于处理客户端提交的请求 - 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
- 理解:
- 前端路由
- 理解:
value
是component
,用于展示页面内容 - 工作过程:当浏览器的路径改变时,对应的组件就会显示
- 理解:
- 后端路由
基本路由
- 安装
vue-router
,命令npm i vue-router
vue3 ===> vue-router@4
vue2 ===> vue-router@3 - 应用插件
Vue.use(VueRouter)
- 编写
router
配置项
import VueRouter from 'vue-router' // 引入VueRouter
import About from '../components/About' // 路由组件
import Home from '../components/Home' // 路由组件
// 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
- 实现切换
<router-link></router-link>
浏览器会被替换为a
标签
active-class
可配置高亮样式
<router-link active-class="active" to="/about">About</router-link>
- 指定展示位
<router-view></router-view>
几个注意事项
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹
比如上一节的案例就可以修改为
src/pages/Home.vue
src/pages/About.vue
src/router/index.js
src/components/Banner.vue
src/App.vue
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个
router
,可以通过组件的$router
属性获取到
多级路由
- 配置路由规则,使用
children
配置项
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ // 通过children配置子级路由
{
path:'news', // 此处一定不要带斜杠,写成 /news
component:News
},
{
path:'message', // 此处一定不要写成 /message
component:Message
}
]
}
]
- 跳转(要写完整路径)
<router-link to="/home/news">News</router-link>
路由的 query 参数
- to 的字符串写法
- to 的对象写法
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法(推荐) -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id: m.id,
title: m.title
}
}"
>跳转</router-link>
接收参数
$route.query.id
$route.query.title
命名路由
作用:可以简化路由的跳转
如何使用
- 给路由命名
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' // 给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
- 简化跳转
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
路由的 params 参数
- 配置路由,声明接收
params
参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // 🔴使用占位符声明接收params参数
component:Detail
}
]
}
]
}
- 传递参数
特别注意:路由携带params
参数时,若使用to
的对象写法,则不能使用path
配置项,必须使用name
配置!
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
- 接收参数
$route.params.id
$route.params.title
路由的 props 配置
props
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return {
id: $route.query.id,
title: $route.query.title
}
}
// 或者 结构
props({query}){
return {
id: query.id,
title: query.title
}
}
}
Vue Router replace 编程式导航 缓存路由组件
路由跳转的 replace 方法
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:
push
和replace
push
是追加历史记录replace
是替换当前记录,路由跳转时候默认为push
方式
- 开启replace模式
<router-link :replace="true" ...>News</router-link>
简写<router-link replace ...>News</router-link>
总结:浏览记录本质是一个栈,默认 push
,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为 replace
就是不追加,而将栈顶地址替换
编程式路由导航(不用 <router-link>
)
作用:不借助 <router-link>
实现路由跳转,让路由跳转更加灵活
this.$router.push({})
内传的对象与<router-link>
中的to相同this.$router.replace({})
this.$router.forward()
前进this.$router.back()
后退this.$router.go(n)
可前进也可后退,n
为正数前进n
,为负数后退
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁
<keep-alive include="News"><router-view></router-view></keep-alive>
<keep-alive :include="['News', 'Message']"><router-view></router-view></keep-alive>
// 缓存一个路由组件
<keep-alive include="News"> // include中写想要缓存的组件名,不写表示全部缓存
<router-view></router-view>
</keep-alive>
// 缓存多个路由组件
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
activated deactivated
activated
和 deactivated
是路由组件所独有的两个钩子,用于捕获路由组件的激活状态
具体使用
activated
路由组件被激活时触发deactivated
路由组件失活时触发
路由守卫
作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
- 全局守卫
meta
路由源信息
// 全局前置守卫:初始化时、每次路由切换前执行
router.beforeEach((to,from,next) => {
console.log('beforeEach',to,from)
if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ // 权限控制的具体规则
next() // 放行
}else{
alert('暂无权限查看')
}
}else{
next() // 放行
}
})
// 全局后置守卫:初始化时、每次路由切换后执行
router.afterEach((to,from) => {
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
- 独享守卫
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
}
}
- 组件内守卫
beforeRouteEnter (to, from, next) {... next()},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {... next()},
meta 配置信息
在定义路由的时候可以在 meta
选项中填入配置信息,后面可以直接通过 to.meta.auth
获取
{
name:'xiangqing',
path:'detail',
component:Detail,
meta:{auth:true},
}
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.auth || '硅谷系统'
})
路由器的两种工作模式
- 对于一个
url
来说,什么是hash
值?
#
及其后面的内容就是hash
值 hash
值不会包含在HTTP请求中,即:hash
值不会带给服务器hash
模式- 地址中永远带着
#
号,不美观 - 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
- 地址中永远带着
history
模式- 地址干净,美观
- 兼容性和
hash
模式相比略差 - 应用部署上线时需要后端人员支持,解决刷新页面服务端
404
的问题。也就时判断页面时前端路由还是后端页面。
例如在nodejs
中使用connect-history-api-fallback
库
Vue UI 组件库
移动端常用UI组件库
- Vant
- Cube UI
- Mint UI
- https://nutui.jd.com/#/
PC端常用UI组件库
- Element UI
演示视频 - IView UI