初识Vue2.0
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- vue容器里的代码依然符合html规范,其中混入一些vue语法例如mustache语法的双花括号,mustache语法里面要写js表达式,且里面会自动读取到data中的所有属性。
- vue容器里的代码被称为Vue模板
<div id="app"></div>
<script>
new Vue({
el:'#app',
data:{name:'Ming'}
})
</script>
new Vue时传入一个配置对象,值有比如:el用于指定当前vue实例;data用于储存数据
Vue实例和容器是一一对应的,真是开发中只有一个Vue实例,并且会配合着组件一起使用
Vue模板语法
vue模板语法有两大类。
- 插值语法(mustache语法):写法
{{xxx}}
,xxx里写的是js表达式,且可以直接督导data中的所有属性。 - 指令语法:写法例如
v-bind:href="xxx"
,可以简写成:href="xxx"
,xxx里写的也是js表达式,可以直接读取到data中的所有属性。
- 插值语法通常用于标签体内容(#text的内容),指令语法可用于标签属性,标签体内容,绑定事件等
数据绑定
- 单向数据绑定,例如v-bind绑定表单input的value值 ,单向指data中的值绑定到视图中,但视图中修改不会影响到data
<input :value="name">
- 双向数据绑定,v-model,通常用于输入类元素上例如表单的input,通常用于绑定input的value值。v-model默认收集的就是value值,所以v-model:value 可以简写为 v-model,新版vue已不支持v-model:value写法,直接写v-model即可
<input v-model:value="name">
绑定属性修饰符
v-bind 还可以用于传值,此外给组件传值还有一个 sync 属性,例如 :money.sync="money" (冒号后面的跟引号里面的一定要一样),代表父组件给子组件传递 props:['money'],并且给子组件绑定一个自定义事件名叫(update:money),事件的回调是子组件 $emit('update:qian',huidiao) 里的第二个参数huidiao。
<zujian :money.sync="money"/>
data(){
return{ money:10000 }
}
//子组件内:
$emit('update:money',money-100)
props:['money']
利用属性修饰符sync可以实现子组件修改父组件传过来的props(修改props)
el与data的两种写法
el的两种写法
- new Vue时候配置el属性。
- 先创建Vue实例vm,随后再通过vm.$mount('#root')指定el的值。
vue3里已不支持第一种写法,用的是第二种的改进版
data的两种写法
- 对象式
- 函数式
学习阶段可以用第一种写法,但只要涉及到组件,data就必须使用函数式。
重要原则
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this指向就不再是Vue实例
MVVM模型
model-view-viewmodel
- vue2文档中说因为设计时受mvvm启发,所以vue实例通常用vm来命名。但vue3已移除这句话,用
Counter。 - vm:
const vm = new Vue()
data中的所有属性,最后都会出现在了vm身上,vm身上所有属性以及vue原型上的所有属性,再vue模板中都可以直接使用
Vue数据代理
回顾Object.defineProperty
参数一:给谁添加属性
参数二:添加属性的属性名
参数三:配置项,是一个对象,其中包括value。
此方法定义的属性默认是不可枚举/遍历的,可在配置项里修改enumerable:true
开启遍历。
定于的属性默认是不可以被修改的,可在配置项修改 writable:true
开启修改
定义的属性默认是不可以被删除的,可在配置项修改 configurable:true
开启删除。
配置项get函数(getter)当有人读取参数二的属性时,getter就会被调用,且返回值就是这个属性的值
配置项set函数(setter)当有人修改参数二的属性时,setter就会被调用,且会收到给setter传参的值
let person = {
name:'Ming',
sex:'male'
}
let number = 18
Object.defineProperty(person,'age',{
get(){return number}
set(value){number = value//此时修改person的age就会修改number}
})
数据代理
通过一个对象 代理 对另一个对象中的属性的操作
vue中的数据代理
vm._data
里面的内容,就是配置对象里面的data,只不过内部做了一些数据劫持,使之可以实现响应式更新视图。内部的传值用的就是Object.defineProperty这个api
Vue事件处理
给元素绑定事件,用 v-on:***="***"
指令,或者简写形式 @***="***"
例如
<button v-on:click="show"></button>
然后再vue实例里面的 methods 里面写上方法就可以了,最终会在vm身上,想要获取事件对象只需要在写函数的时候传入一个参数
const Counter = {
methods:{
show(event){
console.log(event)
}
}
}
Vue.createApp(Counter).mount('#app')
如果在给元素绑定事件的时候想要传入一个参数怎么办?想要传入一个参数并且获得事件对象,就需要一个$event
做占位符,$event
占位符和传的参数的位置没有要求,只需要在写函数的时候对应拿取就行
<button @click="show($event,88)"></button>
<script>
const Counter = {
methods:{
show(event,value){
console.log(event)
console.log(value)
}
}
}
Vue.createApp(Counter).mount('#app')
</script>
基本使用注意点:
- 使用 v-on:xxx 或 @xxx 可以绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,vue会处理其绑到vm上
- methods中配置的函数,不要用箭头函数,否则this指向的就不是vm
- methos中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象
- @click="demo" 和 @click="demo($event)" 的效果一致,但后者可以传参
事件修饰符
<a href="http://www.baidu.com" @click="show">点我跳转</a>
像如上代码,如果想要a标签点击触发show事件,但不想进行a标签默认的跳转行为。可以使用 e.preventDeafault()
来阻止。
但是,vue里面有事件修饰符,用于在事件触发时候添加一些其他动作。
例如 prevent
修饰符,用法写在v-on指令后面用点接起来就可以。事件修饰符可以链式书写
<a href="http://www.baidu.com" @click.prevent.stop="show">点我跳转</a>
vue时间修饰符 | 意义 |
---|---|
prevent | 阻止默认事件(常用) |
stop | 阻止事件冒泡(常用) |
once | 事件只触发一次(常用) |
capture | 使用事件的捕获模式 |
self | 只有event.target是当前操作的元素时才触发事件 |
passive | 事件的默认行为立即执行,无需等待事件回调执行完毕 |
键盘事件
在原生js里,通常用e.keycode判断哪个键按下了。
vue里可以给使事件后面添加别名后缀方式使用。例如 @keyup.enter
vue常用案件别名 | 按键 |
---|---|
回车 | enter |
删除 | delete(删除和退格同时捕获) |
退出 | esc |
空格 | space |
换行 | tab(特殊:必须配合keydown使用) |
上 | up |
下 | down |
左 | left |
右 | right |
vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-cace短横线命名法。例如
@keydown.caps-lock
-
类似tab的系统用法特殊的修饰键:ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。可以链式书写
@keyup.ctrl.y
- 配合keydown使用:正常出发事件
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。可以链式书写
也可以使用keycode去指定具体的按键(mdn文档提示未来可能弃用)
Vue.config.keyCodes.自定义按键名 = 键码
,可以去定制按键
Vue计算属性 computed
computed里面写的属性的值基本上都是一个对象。通常称为计算属性。
计算属性里面通常写get()和set()属性,类似Object.defineProperty()一样使用
const Counter = {
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
fullName: {
get() {
return this.firstName + this.lastName
}
}
}
}
app = Vue.createApp(Counter).mount('#app')
- get什么时候调用?
- 初次读取该值时
- 所依赖的数据发生变化时
- set什么时候调用?
当该值被修改时
计算属性小结
- 定义:要用的属性不存在,当可以通过已有属性计算得来
- 原理:底层借助了Object.defineProperty()的getter和setter
- 优势:与methods实现相比,内部有缓存机制,效率更高,调试方便
- 备注:计算属性最终会出现在vm上,直接读取使用即可。
如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起所依赖的数据改动。例如上面的案例
computed: {
fullName: {
get() {
return this.firstName + this.lastName
}
set(value){
let v = value
this.firstName = v[0]
this.lastName = v[1]
}
}
}
计算属性简写
通常,用到计算属性基本上是只用到getter的,如果只用到getter,那么可以使用简写形式
computed: {
fullName() {
return this.firstName + this.lastName
}
}
}
虽然看起来像个方法,但他实际上是个属性,使用的时候不要使用调用符号。
至此,我们学到的vue上的东西有:data数据、methods方法、computed计算属性。数据和计算属性都是直接读即可读到
监视属性变化watch
- 配置属性watch可以监视vue实例里一个值的变化,可以是普通data值,也可以是计算属性值。监听的值发生变化就执行handler。
- watch的属性作为键,值是一个对象形式的配置对象,里面有基础配置属性handler函数,该函数自动接收两个参数,一个是新值一个是旧值。
- handler什么时候调用?当监视的那个键的值发生变化的时候
const Counter = {
data() {
return {
isHot:true
}
},
watch:{
isHot:{
handler(newValue,oldValue){
console.log('isHot被修改了')
}
}
}
}
app = Vue.createApp(Counter).mount('#app')
- 还有其他配置项,
immediate:false
,该配置项用于是否开启 初始化时让handler调用一下。 - 监视的属性必须存在才能被监视
- 监视的两种写法:
- 创建vue实例时候传入watch配置
- 通过vue实例的watch('isHot',{})` ,参数一是监视那个值,参数2是配置项
深度监视
监视多级结构中所有属性的变化,使用配置项 deep:true
。
- vue中的watch默认不监测对象内部值的改变(一层)(当监测的值是对象时)
- 配置
deep:true
可以监测对象内部值改变(多层)(当监测的值是对象时)
其他知识点:- vue自身可以监测到对象内部值的改变,但vue提供的watch默认不可以
- 使用watch时根据数据的具体结构,来决定是否开启deep配置项。像上面提到的当监测的值是对象时
监视的简写
当不需要其他配置项只需要handler函数的时候,可以使用简写
const Counter = {
data() {
return {
isHot:true
}
},
watch:{
isHot(newValue,oldValue){
console.log('isHot被修改了')
}
}
}
app = Vue.createApp(Counter).mount('#app')
watch和computed区别
- computed能完成的功能,watch都可以完成。
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
绑定class样式
- 字符串写法:适用于:样式的类名不确定,需要动态指定
<div class="basic" :class="mood" @click="changeMood"></div>
<script>
const Counter = {
data(){
return {
mood = 'style1'
}
}
methods:{
changeMood(){
this.mood = 'style2'
}
}
}
app = Vue.createApp(Counter).mount('#app')
</script>
- 数组写法,适用于:要绑定的样式个数不确定、名字也不确定。因为可以使用数组的方法删除或添加
<div class="basic" :class="classArr" @click="changeMood"></div>
<script>
const Counter = {
data(){
return {
classArr:['style1','style2','style3']
}
}
methods:{
changeMood(){
this.classArr.shift()
}
}
}
app = Vue.createApp(Counter).mount('#app')
</script>
- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用
<div class="basic" :class="classObj" @click="changeMood"></div>
<script>
const Counter = {
data(){
return {
classObj:{
style1:true,
style2:true
}
}
}
methods:{
changeMood(){
this.classObj.style1 = false
}
}
}
app = Vue.createApp(Counter).mount('#app')
</script>
绑定style样式
注意:使用style绑定样式,key值要用小驼峰写法
- 对象写法
<div class="basic" :style="styleObj" @click="changeMood"></div>
<script>
const Counter = {
data(){
return {
styleObj:{
backgroundColor:'green',
fontSize:'40px'
}
}
}
}
app = Vue.createApp(Counter).mount('#app')
</script>
- 数组写法
:style="[a,b]"其中a、b是样式对象
scoped样式
多个组件写style样式,如果不携带scoped属性,那么会全部由app.vue组合到一起,如果命名冲突后面的就会覆盖前面的
<style scoped>
</style>
作用:让样式只在局部组件中生效,防止冲突。一般不会在app组件使用
条件渲染 v-show和v-if
v-show的原理是display:none
,接的值是一个布尔值,也可以是一个表达式。v-show="3===1"
v-if的原理是把整个元素节点移除掉,dom节点资源开销币v-show大
v-else-if的原理与普通if else一样,v-if判断成功后,v-else-if就不判断了
v-else就是v-if和v-else-if都不成立的时候,v-else就生效
v-else和v-if和v-else-if需要成组使用,也就是使用这三个命令的节点需要连在一起,否则只有v-if生效,然后后续的报错
其他知识点:template标签和v-if搭配使用,template元素在渲染的时候会脱掉这个标签
循环渲染/列表渲染 v-for
类似原生的 for-in 循环,可用于遍历可迭代对象和对象,还可以遍历指定次数。
语法:v-for="(item,index) in xxx" :key="yyy"
,当只需要遍历值不需要索引的时候可以只写一个参数
<ul>
<li v-for="value in arr" :key="value.id"></li>
<li v-for="(objItem,index) in carObj" :key="item.id"></li>
<li v-for="(number,index) in 5" :key="value.id"></li>
//v-for循环对象时有三个参数,item,key和index
<li v-for="(item,key,index) in obj" :key="index"></li>
</ul>
key有什么作用
- 虚拟DOM中key的作用
key是虚拟DOM对象的标识!!!当数据发生变化时,Vur会根据新数据生成新的虚拟DOM!!!
随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,也就是根据diff算法去进行页面渲染。 - 如果不写key,则Vue默认根据index来对key进行赋值。如果用index作为key会引发一下问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新,影响效率
- 如果结构中还包含输入类DOM,会产生错误DOM更新进而影响界面。
- 所以,开发中使用每条数据的唯一标识作为key,如id、手机号、身份证号等唯一值。
Vue2监测数据改变的原理
Vue.set() 和 vm.set 还有 $delete,参数是一样的
原理总结:
- vue会监视data中所有层次的数据
- 如何监测对象中的数据?通过setter实现监视,且要在new Vue时旧传入要监测的数据。
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API:
Vue.set(TARGET,PropertyName/INDEX,VALUE)
或vm.$set(TARGET,PropertyName/INDEX,VALUE)
- 如何监测数组中的数据?
- 通过包裹数组更新元素的方法(vue重写的常用原生数组方法):push、pop、shift、unshift、splice、sort、reverse。本质是调用原生对应的方法对数组进行更新后重新解析模板,调用reactiveSetter等进行界面更新。
- 使用
Vue.set(TARGET,PropertyName/INDEX,VALUE)
或vm.$set(TARGET,PropertyName/INDEX,VALUE)
- 特别注意:Vue3已移除那两个api,Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性
收集表单数据
vue页面收集表单数据,通常使用v-model指令,以及给form表单用v-on绑定事件并阻止默认跳转提交 <form @submit.prevent="demo">
- 若
<input type="text"/>
,则v-model手机的是value值,用户输入的就是value值 - 若
<input type="radio"/>
,则v-model收集的是value值,且要给标签配置value属性 - 若
<input type="checkbox">
- 没有配置input的value属性,那么收集的就是checked的布尔值
- 配置input的value属性:
- v-model的初始值是非数组,那么收集的就是checked的布尔值(不推荐这样用)
- v-model的初始值是数组,那么收集的就是value组成的数组
v-model的三个修饰符
修饰符 | 作用 |
---|---|
number | 输入字符串转为有效的数字 |
lazy | 失去焦点再收集数据 |
trim | 输入首尾空格过滤 |
Vue2过滤器filters
<div id="app">
<h1>现在的时间是:{{time | timeFomater}}</h1>
<h1>现在的时间是:{{time | timeFomater()}}</h1>
</div>
<script>
const Counter = {
data() {
return {
time:Date.now()
}
},
filters:{
timeFomater(value){
return dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
}
}
}
let app = Vue.createApp(Counter).mount('#app')
</script>
- vue会把,需要过滤器处理的那个参数,当作函数的参数传入过滤器函数里。
- 如果像第二个h1那样写,传入小括号的第一个参数依然是需要过滤器处理的那个参数,第二个则是自定义传的参数.
- filters过滤器可以进行多层过滤
<h1>现在的时间是:{{time | timeFomater() | myslice}}</h1>
- 过滤器可以搭配v-bind使用,但不能搭配v-model
现在的时间是:<input :x="time | timeFomater"></input>
全局过滤器 Vue.filter
Vue.filter('mySlice',function(value){return value.slice(0,4)})
Vue其他内置指令
整理之前学的指令
指令 | 说明 |
---|---|
v-bind | 单项绑定解析表达式,可简写为 :xxx |
v-model | 双向数据绑定 |
v-for | 遍历数组、对象、字符串 |
v-on | 绑定事件监听,可简写为@xxx |
v-show | 条件渲染是否展示 |
v-if v-else-if v-else | 条件渲染 |
v-text
类似innerText,v-text里的内容会覆盖标签内所有内容,且不会编译里面的标签括号
<div id="app">
<h1 v-text="time"></h1>
</div>
<script>
const Counter = {
data() {
return {
time: '<h2>'+Date.now()+'</h2>'
}
}
}
let app = Vue.createApp(Counter).mount('#app')
</script>
v-html
类似innerHtml,可以解析结构。所以v-html对比v-text有安全性问题。一定要在可信的内容上使用v-html,永远不要在用户提交的内容上使用
v-cloak
v-cloak指令没有值。
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉元素上的v-cloak属性。
- 使用css的
display:none
可以解决网速慢时展示出 {{xxx}} 的问题
<div v-clock>{{name}}</div>
<style>
[v-cloak]{
display: none;
}
</style>
v-once
v-once指令没有值。事件修饰符也有个once别搞混了,用法不一样
- 使用该指令的元素内容,只会进行初次传值渲染。v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre
- 跳过其所在节点的vue编译过程
- 可利用他跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
自定义指令 directives
- vue里使用directives来自定义指令,指令是写成一个函数形式或对象形式。
- 每个函数接收两个参数,参数一:该指令绑定的对象;参数二:该指令书写时后面引号接的东西(信息对象),通常用他的value
<div id="app">
<h1>当前值是{{num}}</h1>
<h2 v-big="num"></h2>
<button @click="num++">点击增加原始值</button>
</div>
<script>
const Counter = {
data() {
return {
num: 1
}
},
directives: {
big(element, binding) {
element.innerText = `放大十倍后的值是${binding.value * 10}`
}
}
}
let app = Vue.createApp(Counter).mount('#app')
</script>
- 书写的函数型指令何时会被执行?
- 指令与元素成功绑定时(一上来)
- 指令所在的模板被重新解析时。(例如使用到的data里的值被修改了)
对象形式自定义指令
- 函数形式是简写形式,实际上对象形式才是自定义指令的本体。
- 其中包括三个阶段:bind、inserted、update。这三个东西被称为自定义指令的配置。
- bind是指令与元素成功绑定时。inserted是所在元素被插入页面时。update是指令所在的模板被重新解析时。参数都是element和binding。有很多操作时只有在inserted之后才有效的,这点要记住
directives: {
fbind: {
bind(element,binding){
},
inserted(element,binding){
element.focus()
},
update(element,binding){
}
}
}
自定义指令注意点
- 指令定义时不加v-,但使用时要加 v-
- 指令名不能使用驼峰命名法,vue会默认全部转成小写。真要多个单词写用斜杠接起来。例如 v-focus-bind。在写指令的时候指令对象名用引号包起来。
'v-focus-bind'(element,binding){}
- 全局指令书写形式
Vue.directive(指令名,配置对象)
或Vue.directive(指令名,回调函数)
Vue生命周期
- 是什么:vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容时程序员根据需求编写的
- 生命周期函数中的this指向是vm或组件实例对象
完整生命周期,8个(4对)
- beforeCreate
- created
- beforeMount
- mounted(常用;发送ajax请求,启动定时器,绑定自定义事件,订阅消息等初始化操作)
- beforeUpdate
- updated
- beforeDestroy(常用;清除定时器,解绑自定义事件,取消订阅等收尾工作)
- destroyed
[图片上传失败...(image-db9fe8-1660811061785)]
Vue2组件
<body>
<div id="app">
<school></school>
<student></student>
<student></student>
</div>
<script>
const school = Vue.extend({
template: `
<div>
<h2>学校的名字是{{name}}</h2>
<h2>学校的地址是{{address}}</h2>
</div>
`,
data() {
return {
name: '家里蹲大学',
address: '家里'
}
},
})
const student = Vue.extend({
template: `
<div>
<h2>学生的名字是{{name}}</h2>
<h2>学生的年龄是{{age}}</h2>
<button @click="addage">点我增加年龄</button>
</div>
`,
data() {
return {
name: 'zhangsan',
age: 18
}
},
methods: {
addage() {
this.age++
}
},
})
const vm = new Vue({
el: '#app',
components: {
school,
student
}
})
</script>
</body>
- 使用组件三大步骤1.创建/定义组件、2.注册组件、3.使用组件(写组件标签)
- 如何定义一个组件?
使用Vue.extend(OPTIONS)创建,其中OPTIONS和new Vue(OPTIONS)时传入那个配置对象几乎一样。但区别如下- el不要写,因为最终所有的组件都要经过一个vm管理,由vm中的el决定服务那个容器
- data必须写成函数,避免组件复用时候,多个组件互相篡改数据
- 如何注册组件?
- 局部注册:靠 new Vue 时候写的 components 配置项
- 全局注册: 靠 Vue.components('组件名',组件)
- 组件书写结构时候用 template 项来写,template里面必须要有且只有一个父标签
- 使用组件: <zujian></zujian>
关于组件的注意点
- 关于组件名:
- 一个单词组成:
- 第一种写法(首字母小写):zujian
- 第二种写法(首字母大写):Zujian
- 多个单词组成:
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
- 备注:
- 组件名要回避HTML已有的元素标签名
- 可以使用name配置项指定组件在开发者工具中显示的名字
- 一个单词组成:
- 关于组件标签:
- 第一种写法:<zujian></zujian>
- 第二种写法:<zujian/>
- 备注:不使用脚手架时,第二种写法会导致后续组件不能渲染
- 一个简写方式:
const zujian = Vue.extend(OPTIONS)
可简写为const zujian = OPTIONS
关于VueComponent
- Vue组件本质是一个名为VueComponent的构造函数,是由Vue.extend生成的
- 我们在使用组件的时候(写上组件标签时)Vue解析时会帮我们创建组件的实例对象(即执行new VueComponent)
- new调用构造函数,每次调用返回的都是一个全新的VueComponent
- 关于组件的this指向
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是vc
- new Vue中的data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是vm,注意区别
vue组件与vue的重要关系
Vue组件实例.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象可以访问到Vue原型上的属性和方法
Vue脚手架
安装
npm i -g @vue/cli
# OR
yarn global add @vue/cli
创建一个项目:
vue create my-project
# OR
vue ui
脚手架文件结构:
|-- undefined
|-- .gitignore:git忽略文件配置
|-- babel.config.js:babel配置文件
|-- jsconfig.json
|-- package-lock.json:包版本控制文件
|-- package.json:应用包配置文件
|-- README.md:应用描述文件
|-- vue.config.js:vue配置文件
|-- dist:打包后文件
|-- public:静态资源文件夹,webpack会原封不动的打包文件
| |-- favicon.ico:页标签图标
| |-- index.html:主页面
|-- src
|-- App.vue:汇总所有组件
|-- main.js:入口文件
|-- assets:静态资源文件夹,webpack会模块化打包
| |-- logo.png
|-- components:组件文件夹
|-- HelloWorld.vue
关于不同版本的Vue
- vue.js 与 vue.runtime.xxx.js 的区别
- vue.js 是完整版的vue,包含核心功能和模板解析器
- vue.runtime.xxx.js 是运行版的vue,只包含核心功能,没有模板解析器,所以需要render函数
- 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
vue.config.js配置文件
- 使用
vue inspect > output.js
可以查看到vue脚手架的默认配置 - 使用 vue.config.js 可以对脚手架进行个性化定制
- 新手通常把eslint关闭
lintOnSave:false
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者),然后用
this.$refs.***
获取
<template>
<div>
<h1 ref="biaoti"></h1>
<School ref="sch"></School>
</div>
</template>
<script>
export default {
mounted(){
console.log(this.$refs.biaoti)
console.log(this.$refs.sch)
}
}
</script>
- 应用在html标签上获取的是真实DOM元素,应用在逐渐标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx"></h1>
或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
组件传值接值 和 props配置项
传值
- 方式:在使用组件的时候携带标签属性,为一个个名值对形式
<Student name="张三" sex="女" age="18"></Student>
- 用props接收过来的值会在组件的vc身上存着,在开发者工具里的props可以看到
props配置项
- 功能:让组件接收外部传过来的数据,默认类型是string类型,如要修改例如可用v-bind改为number。
- 方式一:数组形式(简写形式),顺序不重要,对上号就行
<script>
export default{
props:['name','sex','age']
}
</script>
- 方式二:接收时只对数据进行类型限制
<script>
export default{
props:{
name:String,
age:Number,
sex:String
}
}
</script>
- 方式三:接收时对每个数据进行严格设置,可以配置type、default、required。通常default、required不会同时使用,逻辑问题
<script>
export default{
props:{
name:{
type:String,
required:true
},
age:{
type:String,
default:99
},
sex:{
type:String,
required:false,
default:'male'
}
}
}
</script>
props注意点
- vue不允许修改props接收过来的值,否则发出警告。如要修改,先把props的值存在data之后再修改
<Student name="张三" sex="女" age="18"></Student>
<script>
export default{
props:[name,age,sex],
data(){
return{
uname:this.name
}
}
}
</script>
- 如果使用的组件上的data有与props同名的,优先使用props的,即父组件传过来的那个值。
- 传值时候不要使用vue预留的指令当作传值的名
- 数组方式传入的时候注意变量要用引号包起来
mixin混入
new Vue({
mixin:[xxx,xxx]
})
- 功能:可以把多个组件公用的配置提取成一个混入对象,提取的文件是一个js格式的文件
- 第一步:定义混合,写一个js文件,然后里面写入配置项
const hun1 = {
data(){...},
methods:{...}
...
}
- 第二步,使用局部混入或全局混入。局部混入写在局部组件的vue配置项里
new Vue({
mixins:[xxx,xxx]
})
全局混入一般写在main.js里
Vue.mixin(xxx)
- 注意点:混入项和组件自带项冲突时,数据以组件自带的data为准。如果两者都有生命周期钩子,则先执行混入项的生命周期钩子。
vue插件
vue插件是一个包含install方法的一个对象,install的第一个参数是Vue构造函数,第二个第三个第n个是插件使用者传递的数据。
定义完Vue插件之后在main.js使用 Vue.use(插件)
即可。
//定义一个plugins.js
export default{
install(Vue){
Vue.directive(...)
Vue.mixin({...})
Vue.prototype.hello=...
}
}
在main.js里使用
import plg1 from './plugins.js'
Vue.use(plg1)
给组件触发自定义事件 this.$emit
this.$emit(eventName,params)
触发自定义事件的两个参数,参数1是触发的事件名,参数2是传过去的参数。
this.$on('eventName',params)或者v-on:eventName="fn" fn(params)
类似这样接收参数
<School v-on:zidingyi="demo"></School>
methods:{
demo(value){console.log("demo被调用了,得到的值是"+value)}
}
以上案例,在使用组件的时候,绑定了一个自定义事件“zidingyi”,接下来,去那个组件里面使用this.$emit('zidingyi',this.name)
触发该事件,就可以在子组件把数据传输给父组件。
<template>
<div>
<h2>学校名字是{{ name }}</h2>
<h2>学校地址{{ address }}</h2>
<button @click="sendSchName">点我发送学校名字给app</button>
</div>
</template>
<script>
export default {
data() {
return {
name: "家里蹲大学",
address: "家里",
};
},
methods: {
sendSchName() {
this.$emit("zidingyi", this.name);
},
},
};
</script>
这个时候在页面使用button就会在控制台输出 demo被调用了,得到的值是家里蹲大学
通过ref给组件绑定自定义事件
<Student ref="student"></Student>
methods:{
demo(value){console.log("demo被调用了,得到的值是"+value)}
}
mounted(){
this.$refs.student.$on('zidingyi',this.demo)
}
通过ref给逐渐绑定自定义事件灵活性更高,可以在异步给组件绑定自定义事件
this.$off() 解绑自定义事件
<template>
<div>
<h2>学校名字是{{ name }}</h2>
<h2>学校地址{{ address }}</h2>
<button @click="sendSchName">点我发送学校名字给app</button>
<button @click="unset">点我解绑自定义事件</button>
</div>
</template>
<script>
export default {
data() {
return {
name: "家里蹲大学",
address: "家里",
};
},
methods: {
sendSchName() {
this.$emit("zidingyi", this.name);
},
unset(){
this.$off("zidingyi")
}
},
};
</script>
-
this.$off()
可以传递一个字符串为参数,如果需要解绑多个自定义事件,则需要写成一个数组形式this.$off(["zidingyi","chufa"])
- 如果
this.$off()
不传递任何参数,则解绑该组件身上所有的自定义事件 - 如果组件进行了destroyed 生命周期,那么身上绑定的自定义事件也会解绑,这是生命周期自身的特性
自定义事件总结
- 作用:子组件给父组件传内容,使用场景:子组件给父组件传数据,在父组件中给B绑定自定义事件。
- 通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向的是绑定者。 - 组件上也可以绑定原生DOM事件,需要使用native修饰符,本质是给组件的根标签绑定原生DOM事件,点任何子标签都会冒泡到根标签。
- 给原生DOM绑定自定义事件是没有意义的,因为没有办法触发emit('zidingyi',参数)"
全局事件总线(GlobalEventBus)
是一种组件间通信的方式,适用于任意组件间通信
安装全局事件总线
在创建vue实例的时候在beforeCreate钩子里面给vue原型挂上on$off等方法
new Vue({
......
beforeCreate(){
Vue.prototype.$bus = this
},
......
})
使用事件总线
谁需要数据的,就先往谁身上先写一个方法,然后把这个方法,挂在一个自定义事件上
methods(){
reqData(data){
//data为事件触发者传过来的参数
}
}
mounted(){
this.$bus.$on('xxxreqData',this.reqData)
}
谁发送数据的,就在这个组件通过这个自定义事件把数据发送过去
this.$bus.$emit('xxxreqData',DATA)
就是根据这个挂在$bus上的自定义事件来实现通信
消息订阅与发布,pubsub-js库
也是一种组件间同行的方式,可以适用于任意组件间通信。
使用方法
- 安装pubsubjs
npm i pubsub-js
- 在使用的组件里面引入pubsub
import pubsub from 'pubsub-js'
- 使用pubsub的api
subscribe
去订阅一个自定义消息名的消息,第一个参数是消息名,第二个参数是这个消息里的数据。注意这个api里面的this是undefined。并且注意,作为subscribe的回调的形参一默认是消息名字,形参二后面的才是传输的数据。
mounted(){
this.pid = pubsub.subscribe('MSGNAME',(MSGNAME,data)=>{data...})
}
- 在提供数据的组件使用
publish
这个api去发布消息,第一个参数是自定义消息名,第二个参数是数据。
pubsub.publish('MSGNAME',data)
- 最好在beforeDestroy钩子中使用
unsubscribe(pid)
这个api去取消订阅,参数是使用订阅时声明的id变量
pubsub.unsubscribe(pid)
$nextTick
this.$nextTick(回调函数)
this.$nextTick(()=>{
//...更新dom后要进行的操作
})
- 作用:在这一轮DOM更新结束后的时刻执行参数里面的回调函数
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
nextTick,在Vue里,我们在更新数据之后,DOM会在这一轮宏任务之后微任务里进行更新,倘若这一次宏任务中进行了一些读取DOM的操作,则会发现读取的是宏任务后的DOM信息,因为还没到vue更新DOM的微任务。如果想要在更新DOM的微任务以后对DOM进行一些操作,则把这些操作写在$nextTick的回调函数里面
Vue与CSS3
Vue动画
CSS3的keyframe动画,搭配Vue的 <transition></transition>
标签,以及v-enter-active
和 v-leave-active
类名
<template>
<div>
<button @click="a++"></button>
<transition name="anima">
<h1 class="biaoti" v-show="a % 2 == 0">你好啊</h1>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
a: 2,
};
},
};
</script>
<style scoped>
.anima-enter-active {
animation: animaName 1s;
}
.anima-leave-active {
animation: animaName 1s reverse;
}
@keyframes animaName {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
</style>
Vue过渡
实际上vue给一套transition标签内置了六个class,分别是以下六个,如果只是动画,只使用active那两个就可以了
.v-enter | .v-enter-active | .v-enter-to | .v-leave | .v-leave-active | .v-leave-to |
---|---|---|---|---|---|
<template>
<div>
<button @click="a++"></button>
<transition name="anima">
<h1 class="biaoti" v-show="a % 2 == 0">你好啊</h1>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
a: 2,
};
},
};
</script>
<style scoped>
.anima-enter,
.anima-leave-to {
transform: translateX(-100%);
}
.anima-leave-active,
.anima-enter-active {
transition: 1s;
}
.anima-leave,
.anima-enter-to {
transform: translateX(0);
}
</style>
transition-group 多个过渡、动画标签
一个transition标签里面只能套一个子元素,让一个子元素给vue自动形成六个标签。如果需要套多个子元素,则需要用到<transition-group></transition-group>
,注意,如果使用transition-group,那么每一个子元素都要给key值
<transition-group name="anima">
<h1 v-show="a % 2 == 0" key="1">你好啊</h1>
<h1 v-show="a % 2 == 0" key="2">我不好</h1>
</transition-group>
transition和transition-group标签上的属性
属性名 | 功能 |
---|---|
name="xxx" | 自动生成动画的类名的前缀,第三方动画库也会用到 |
appear | 一开始就执行一次动画 |
enter-active-class | 第三方动画库用到 |
leave-active-class | 第三方动画库用到 |
Vue配置代理devServer.proxy 解决跨域
module.exports = defineConfig({
devServer: {
proxy: 'http://192.168.1.10:5000'
}
})
在vue.config.js文件中写入以上内容即可实现简单代理解决跨域问题,请求的时候请求本机地址即可。
但如果本地public资源有同名请求则冲突。且只能配置一台代理
module.exports = defineConfig({
devServer: {
proxy: {
''/student': {
target: 'http://192.168.1.10:5000',
pathRewrite: { '^/student': '' },
},
'/car': {
target: 'http://192.168.1.10:5001',
pathRewrite: { '^/car': '' },
},
}
}
})
以上方式可以开启多个代理服务器,proxy写成一个对象形式,里面的值也写成一个对象的形式。
对象的键名是路由,为该代理请求的后缀,如果不写pathRewrite配置项,请求资源时也会带上此路由。
ws:true
用于支持websocket,changeOrigin:true
用于控制请求头中的host值,这两如果不写,默认值也是true
slot插槽
默认插槽 slot标签
在组件里面使用slot标签进行插槽占位
<template>
<div>
<h2>学校名字是{{ name }}</h2>
<slot></slot>
</div>
</template>
在使用组件的地方在双组件标签里面写上其他内容,那么这些内容就会使用插槽占的位置,比如这个例子,img标签就会使用slot的位置
<School>
<img src="https://xxx.jpg" />
</School>
具名插槽 带有name属性的slot标签
在组件里面使用slot标签进行插槽占位,并带上name属性
<template>
<div>
<h2>学校名字是{{ name }}</h2>
<slot name="center"></slot>
<slot name="footer"></slot>
</div>
</template>
在vue2里用带有slot属性的标签即可为插槽传入内容(已弃用),请使用 v-slot,但注意,v-slot只能用在template标签上,这点与slot不一样,只有一种特殊情况就是只使用默认插槽,就可以把v-slot直接用在组件上
<div slot="center">
...旧写法已弃用
<div>
<template v-slot:center>
...新标准
<template>
vue3里需要使用v-slot指令和template标签,甚至使用v-slot的简写形式井号#
<template v-slot:footer>
</template>
或
<template #footer>
</template>
默认插槽即不具名插槽也是有name的,name的值为default
作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(数据在子组件,父组件写结构)
//子组件中,使用slot传输data数据
<template>
<div>
<slot :info="info"></slot>
</div>
</template>
<script>
export default{
data(){return{
info:['name:Ming','age:18']
}}
}
</script>
在父组件中,使用template标签搭配scope(或slot-scope)属性拿到组件传过来的数据,注意拿过来的是一个对象,可用解构赋值
<Student>
<template scope="data">
<ul>
<li v-for="d in data.info" :key="d">{{d}}<li>
</ul>
</template>
<Student>
在vue3中,又要起name,又要使用scope,可使用v-slot:NAME="SCOPENAME"
,vue3中只能使用v-slot指令
<template v-slot:default = "slotProps">
{{slotProps}}
</template>
Vuex
全局事件总线可以很好完成读的功能,但多个组件间如果要修改数据就很麻烦。
Vuex是专门在Vue中实现集中式状态(数据)管理的一个Vue插件,在一个vue应用中进行各组件的数据操作很有用。
Vuex业务逻辑图:[图片上传失败...(image-a2f5f6-1660811061785)]
什么时候用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
搭建Vuex环境
先在项目里安装Vuex,npm i vuex@3
,注意:Vue2只能用Vuex3,Vuex4只能用于Vue3,默认安装的是最高版本。
- 创建目录及文件
src/store/index.js
//引入Vue核心库,因为使用Vuex需要在new Vuex.Store之前执行Vue.use(Vuex)否则报错
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//准备三个对象接收api的赋值,state保存具体数据,actions相应组件中用户的动作,mutations修改state中的数据
const state = {}
const actions = {}
const mutations = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
- 在main.js中引入store,并在创建vm的时候放入
import store from './store'
new Vue({
el:'#app',
render:h=>h(App),
store
})
state
需要Vuex管理的状态(数据)都放在store里面的state对象里,比如说需要Vuex管理一个sum
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
sum:0 //此处放置需要vuex管理的数据
}
const actions = {}
const mutations = {}
export default new Vuex.Store({
actions,
mutations,
state
})
如何读取store里面的数据?在需要的组件里面使用 this.$store.state.***
即可
dispatch申请修改store里的state,或直接commit
如果在组件中想要修改store里的state,需在组件中调用 this.$store.dispatch()
这个api,参数一是要执行的action,参数二是传递过去的实参。
export default {
...
this.$store.dispatch('提交的行为名称',需要传递的实参)
...
}
如果不需要一些复杂的处理,想直接修改state的,也可以在组件中直接使用commit api
export default {
...
this.$store.commit('要执行的mutations名称',需要传递的实参)
...
}
action和commit
一般修改state里的东西的行为,都会写在action对象里,书写格式是键值对格式 const action={jia:fuction(){}}
,一般用es6简写模式
const actions = {
jia(context,value){
context.commit('mutations里的方法名',要传递的参数)
}
}
- 里面的函数会接收到两个以上参数,
第一个参数很重要,是一个mini版的store,官方文档称为context。
第二个及后面的参数,就是调用了dispatch的人传递过来的参数。 - 根据vuex流程图可知action里面的行为最终一定会commit到mutation里,用的就是context里的commit
关于context
action里的函数写的第一个形参context里面除了有commit,还有dispatch和state。
- 为什么有dispatch?方便多个action链式调用。
- 为什么有state?直接修改行不行?如果在action里修改,低版本开发者工具监测不到,action里用state主要用于逻辑判断
mutations
在一般开发中,mutations里的方法名通常是actions中对应提交的方法的全大写。mutations里的函数会接收到两个参数。
第一个参数是经过数据代理后的state,可以拿到经过getter和setter包装的数据。
第二个参数是commit传递过来的数据。不管是组件直接commit过来的还是actions commit过来的
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
sum:0 //此处放置需要vuex管理的数据
}
const actions = {
jia(context,value){
context.commit('JIA',value)
}
}
const mutations = {
JIA(state,value){
state.sum += value
}
}
export default new Vuex.Store({
actions,
mutations,
state
})
actions和mutations
mutations不要进行异步操作,一般异步操作都在actions里进行。
或者说,一般在actions里面进行数据修改前的逻辑行为和异步行为,mutations里直接进行数据处理
const actions = {
jiaWait(context,value){
setTimeout(()=>{
context.commit('JIAWAIT',value)
},500)
}
}
const mutations = {
JIAWAIT(state,value){
state.sum += value
}
}
$store.getters
但state中的数据需要经过加工后再使用时,可以使用getters加工,类似于computed。
使用 getters 需要在 store 中最佳 getters配置,getters中的函数会收到一个参数,它就是state
...
const getters = {
bigSum(state){
return state.sum * 10
}
}
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
组件中读取getters的东西 $store.getters.***
mapState和mapGetters
如果组件内想要读取state和getters的数据,要写一大串 $store.state.***
,this.$store.getters.***
,如果不想写一大串,则需要自己在计算属性一个个写返回值。
...
computed:{
he(){
return this.$store.state.sum
}
}
...
这个时候就需要vuex里面自带的 mapState 和 mapGetters 了,需要用到的组件需要从vuex中引入这两个方法
import {mapState,mapGetters} from vuex
这两个函数可以接收两种形式的参数,一种是对象形式,对象的里键值对的键是自己要起的名字,对象里键值对的值是state里面的名字,这个值需要用引号包住,否则会读成变量
mapState({he:'sum'})
mapGetters({dahe:'bigSum'})
但如果不需要自己起名,想在组件里使用直接与state里面同名,则可以写成数组形式。
mapState(['sum'])
这两个方法的返回值都是一个对象,里面是 {VALUE:function mappedState(){}}
形式,为的是方便放入computed属性,放入时因为是一个对象,所以需要使用拓展运算符...mapState()
...
computed(){
...mapState(['sum'])
}
mapMutations和mapAction
这两个方法用于帮助生成与mutations和action对话的方法。即包含$store.commit(xxx)
和 $store.dispatch(xxx)
的函数。在使用的时候,也要先从vuex模块中引入这两个方法。
import {mapMutations,mapAction} from vuex
因为与mutations和action对话一般都是在method里,我们在组件的method里用拓展运算符展开,传递的参数也是对象和数组两种写法。
method:{
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//或数组形式 ...mapActions(['jiaOdd','jiaWait'])
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//或数组形式 ...mapMutations(['JIA','JIAN']),
}
那么问题来了,使用mapMutations和mapAction怎么传递参数呢?我们需要在模板绑定事件时传递好参数,否则参数是事件对象
<button @click="jiaOdd(参数)"></button>
vuex模块化 + namespace
store模块化。使用模块化必须要在每个模块建立时加上 namespaced:true
配置项
const countAbout = {
namespaced:true,
state:{...},
mutations:{...},
actions:{...},
getters:{....}
}
const personAbout = {
namespaced:true,
state:{...},
mutations:{...},
actions:{...},
}
//或者以上的模块使用es6模块化引入
const store = new Vuex.Store({
modules:{
countAbout, //相当于 countAbout:countAbout
personAbout
}
})
mapXXX新的传值方式:'...mapXXX(NAMESPACED',[])
或 ...mapXXX('NAMESPACED',{})
,即第一个参数是哪个模块里的数据
...mapState('countAbout',['sum','school','subject'])
...mapGetters('countAbout',['bigsum'])
...mapActions('personAbout',{addPersonServ:'addfromServer'})
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'})
如果不使用mapXXX读取store里面的东西,除了state可以直接用点xxx.xxx
读到以外,其他都要使用一些特殊手法
//读取state里的数据
this.$store.state.personAbout.list
//读取getters里的数据
this.$store.getters['personAbout/firstPersonName']
//调用dispatch
this.$store.dispatch('personAbout/addfromServer',value)
//调用commit
this.$store.commit('countAbout/JIA',value)
VueRouter 路由
- vue路由用于构建单页面应用。要使用先安装vue-router。并且注意,vue2只能用router3版所以在安装的时候要声明版本
npm i vue-router@3
,否则默认安装的是用于vue3的vuerouter4。 - 配置好router文件后然后在main.js里use这个插件,创建路径文件
src/router/index.js
//npm i vue-router@3
import VueRouter from 'vue-router'
//引入需要在路由中配置的组件
import About from '../components/About'
import Home from '../components/Home'
//在新建VueRouter实例时传入基本配置项 routes,形式是数组,数组项是由path和component组成的对象。
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露以上配置对象
export default router
去到main.js中引入并使用
//先引入这个模块并使用。
import VueRouter from './vue-router`
Vue.use(VueRouter)
//然后再引入上面的配置文件并在new Vue的时候传入router配置项
import router from './router'
new Vue({
...
router
...
})
或者在 router/index.js
里面先引入vue,并在里面use VueRouter之后导出,在main.js里面直接引入router就可以了
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import routes from './routes'
const router = new VueRouter({
routes,
scrollBehavior(to, from, savedPosition) {
return { y: 0 }
}
})
export default router
记得在mainjs里new vue实例的时候配置router属性
new Vue({
...
router
})
router-link 和 router-view 标签
- router-link标签本质是一个经过vue包装后的a标签,可以通过tag属性改为其他标签,可以书写一些正常标签属性以及自身拥有的特别属性例如 active-class 和 to
- to是去router文件里寻找匹配规则,
- active-class为活跃的路由添加class样式,不活跃的自动移去(或者自己写.router-kubj-exact-active的样式)或者去router配置项加上linkActiveClass属性,值为要加上的class名。
- 还有一个replace属性,会把本次路由跳转的地址放在浏览器历史记录上的push方式改为replace方式。
<router-link to="/about" active-class="active">点击渲染About组件</router-link>
- router-view标签为把router文件匹配到的组件渲染出来的组件,在哪展现就在哪使用
<router-view></router-view>
路由的注意点
- 普通组件我们都放在 component 组件文件夹中,但路由使用的组件,我们一般新建一个 views 文件夹放在里面,称为路由组件。
- 通过切换隐藏的路由组件,实际上是被销毁掉的,会触发生命周期的 beforeDestroy 和 destroyed ,切换的则是挂载mounted
- 使用vue router后,我们输出这个组件vc,会发现他们身上有两个属性 router。每个组件都有自己的 router 。
- 路由懒加载:component的属性值使用回调函数形式。
const router = new VueRouter({
routes: [
{
path: '/home',
component: function foo(){return import("@/views/home")}
//或者简写:component: ()=>import("@/views/home")
}
]
})
路由嵌套/多级路由
routes数组项里的新配置项children用于写一个路由下的子级路由,path的方法有所不同,不用带斜杠/
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
children:[
{
path:'news',
component:News
},
{
path:'message',
component:Message
}
]
}
]
})
然后在跳转到子路由的router-link标签里的to属性值要写完整路径
<router-link to="/home/news">News</router-link>
路由传递query参数
类似于请求url携带query参数一样,www.xxx.com?a=1&b=2
,路由的to属性也可以携带query参数。可以使用字符串形式,也可以使用配置对象形式。
//字符串形式,因为通常要携带变量,所以要使用v-bind把传的值变成js表达式,并且通常使用拼串或es6模板字符串
<router-link :to="'/home/message/detail?title=' + value.title"></router-link>
//to属性传入配置对象
<router-link :to="{
path:'/home/message/detail',
query:{
title:value.title
}
}">
如何读取query参数?在被传递的路由组件上使用 $route.query.xxx
读取
$route.query.title
路由命名
- 路由命名的目的是为了简化to属性跳转路径的书写长度,只能用对象方式写。
- 在使用之前先要给路由路径配置好name属性。通常与组件和路径名对应,当然也可以乱取。
//像下面detail那种三级children路由,起一个name属性,会让router-link的to属性方便很多
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News
},
{
path: 'message',
component: Message,
children: [
{
name:'detail'
path: 'detail',
component: Detail
}
]
}
]
}
在router-link里的to属性,使用对象方式传入name属性,就可以不用path属性了
<router-link :to="{
name:'detail'
}"></router-link>
路由传递 params 参数 (动态路由、带有动态参数的路径)
- router-link的to属性也可以传递params参数,但是想要发送params参数,先要在router配置文件的数组项的配置对象里的path属性使用占位符。
- 书写方式是
path:xxx/:VALUE/:VALUE
,如果要同时传递params和query,防止出现空串还会使用问号占位。path:xxx/:VALUE?/:VALUE?
,问号的原理是正则表达式出现0次或任意次数的意思
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News
},
{
path: 'message',
component: Message,
children: [
{
name:'detail'
path: 'detail/:id/:title',
component: Detail
}
]
}
]
}
router-link的to属性传递params参数,可以使用字符串形式和对象形式,特别注意使用对象写法不能使用path配置项,只能用name。
//字符串写法
<router-link :to="`/home/message/detail/${value.id}/${value.title}`"></router-link>
//to属性传入配置对象
<router-link :to="{
name:'detail'
params:{
id:value.id,
title:value.title
}
}">
旧版本中防止传空串在传的时候用或运算符借个undefined params:{id:value.id||undefined}
路由的props配置
- 如果不使用props,在使用
$route
上的数据的时候都要写上前面一大串的$route.query.xxx
和$route.params.xxx
- 普通组件可以使用props传递参数,路由组件也可以使用props收到参数。路由组件想要收到props参数,就需要在路由配置文件里的数组项的配置对象里写上props配置参数。
- 而且props配置参数有三种写法。为对象式,布尔值式和函数式,通常用函数式,因为没有局限性
{
path: 'message',
component: Message,
children: [
{
name: 'detail',
path: 'detail',
component: Detail
//props:{a:100}
//props:true
props($route){
return{
id:$route.query.id,
title:$route.params.value
}
}
}
]
}
- 第一种为对象,该对象中所有的key-value的组合最终都会通过props传递给传过去的那个路由组件。所以数据是静态的。
- 第二种是布尔值,布尔值为true,则把路由收到的所有params参数通过props传给那个路由组件。只能传递params参数。
- 第三种是函数式,这个回调函数会接收到一个参数,这个参数就是props所在组件的 $route 对象
编程式路由导航router.replace 和其他
- 使用编程时路由导航,目的是为了不借助
<router-link>
实现路由跳转,让路由跳转更加灵活。 - 主要使用到的是use vueRouter后挂载到vue实例上的那个
$router
,存在于vueRouter构造函数上。 - 接收的参数是类似to属性里面接受的那个配置对象,有path、name、query、params等属性
//利用 $router.push 和 $router.replace 实现路由跳转。
this.$router.push({
path:'/home',
query:{
id:xxx
}
})
this.$router.replace({
name:'detail'
params:{
id:xxx,
title:xxx
}
})
//利用 $router.back 和 $router.forward 和 $router.go 实现类似 window.history.xxx 的功能
编程式路由导航的小bug
在vue-router@3的高版本中引入了promise,会导致多次点击跳转报warning。
解决方法1是在写完第一个配置对象后再接两个回调函数。
this.$router.push({
path:'/home',
},()=>{},()=>{})
解决方法2是在router文件夹的index.js重写push和replace这个方法
const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace
VueRouter.prototype.push = function (location) { return originPush.call(this, location).catch(err => err) }
VueRouter.prototype.replace = function (location) { return originReplace.call(this, location).catch(err => err) }
路由组件缓存 keep-alive 标签
- 由“路由的注意点”可知路由切换会销毁组件。路由组件缓存的目的是为了切换路由时候,被切换的那个页面缓存不被销毁。
- 使用
<keep-alive></keep-alive>
标签包裹住<router-view></router-view>
标签即可
<keep-alive include="biaodan">
<router-view to="xxx"></router-view>
</keep-alive>
- 注意在哪个路由组件需要keep-alive的就在使用它的router-view外面包,注意不是router-link
- 注意如果不写include属性,则 keep-alive 包裹所有展示过的router-view 都会缓存。include接收的值是那个路由组件起的 name 属性,是组件里的name属性,是组件里的name属性。如果不单止一个,就传入数组参数
:inclue="['xxx','yyy']"
activated和deactivated 配合 keep-alive 独有的两个生命周期
activated() {
console.log("activated被调用了");
},
deactivated() {
console.log("deactivated被调用了");
},
注意这两个生命周期只能用作在被 keep-alive 缓存的组件中使用,组件一旦被 router-view 展示,就会触发activated。
组件激活时触发activated,组件失活时触发deactivated
全局路由守卫 router.beforeEach() 和 router.afterEach()
- 我们在暴露路由对象之前,先往router身上的beforeEach里面扔进一个函数,这个函数包含三个参数。
- 这个 beforeEach() 什么时候被调用?初始化路由和每次启动路由规则时,例如点击跳转
- afterEach() 初始化路由 和 每次路由跳转执行后会执行
const router = new VueRouter({
routes:[
{ path:'/about',
component:About },
{ path:'/home',
component:Home }
]
})
//暴露之前使用beforeEach api并传入函数
router.beforeEach((to,from,next)=>{
console.log(to,from,next)
})
export default router
首先 to 和 from 都是类似的是一个对象,里面包含了本次路由操作的一些信息,例如
path 所在或要跳转的路径,query和params 参数
路由的 meta 配置项
路由配置文件的数组项里的配置项可以写 meta属性,该属性一般用于携带一些自己写的数据给 beforeEach 和 afterEach 用作判断。
里面的值是一个对象,读取时从meta对象里读取数据 meta.xxx
const router = new VueRouter({
routes:[
{
path:'/about',
component:About,
meta:{
Auth:true,
call:666
}
},
]
})
router.beforeEach((to,from,next)=>{
console.log(to.meta.call)
})
独享路由守卫,路由的beforeEnter配置项
- 全局路由守卫挂在router上,单独某个路由需要路由守卫的话,可以单独写入配置项 beforeEnter。
- 值是一个函数。写法跟全局路由守卫一样,是一个有 to,from,next三个参数的函数。
- 独享路由守卫只有前置没有后置,可以和全局搭配使用,先使用全局后使用局部
const router = new VueRouter({
routes:[
{
path:'/about',
component:About,
beforeEnter:function(to,from,next){ //也可以es6简写模式
xxx
}
},
]
})
router.beforeEach((to,from,next)=>{
console.log(to.meta.call)
})
组件内路由
这个路由是写在vue组件里的一个配置项,通常写在路由组件里面。
beforeRouteEnter是通过路由规则进入守卫时被调用,beforeRouteLeave时通过路由规则离开守卫时被调用,注意都是通过路由规则,直接使用组件时这两个配置项是不生效的。还有一个beforeRouteUpdate
export default {
...
beforeRouteEnter(to,from,next){
//通过路由规则进入守卫时被调用
}
beforeRouteLeave:function(to,from,next){
//通过路由规则离开守卫时被调用
}
}
路由 hash模式与 history模式
- 我们前面的路由知识有两个点,一是路径都带有
/#/
,而是router配置对象只有一个routes。 - 带有 /#/ 是我们使用的是默认的hash模式, /#/ 后面的东西是不包含在http请求中带给服务器的。
- 想要改变就需要往router配置对象传入mode属性,值为history。
- hash模式:
- 地址中永远带着 # 号,不美观。
- 若以后降低至通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好
- history模式:
- 地址干净,美观。
- 但页面部署上线时需要后端人员支持,解决刷新页面服务端404的问题。例如nodejs的connect-history-api-fallback
重定向redirect 掩盖路由/别名alias
重定向通过routes配置来完成,例如从 /a 重定向到 /b
const router = new VueRouter({
routes:[
{
path:'/a',
redirect:'/b'
//重定向也可以是一个命名的路由
//redirect:{name:'foo'}
//甚至可以是一个方法
//redirect:function(to){
//方法接收 目标路由 作为参数,return 重定向的字符串路径/路径对象
}
}
]
})
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]})
Vue UI组件库(以ElementUI为例)
想要使用UI组件库,先需要安装
npm i element-ui
然后参照官方文档引入及使用
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)
按需引入
上面代码 Vue.use(ElementUI)
一旦编译,就会把整个组件库引入,造成js文件过大的问题,可以参考官方文档使用按需引入