Vue基础
vue的特点
1.采用组件化模式,提高代码复用率,且让代码更好维护
2.声明式编码,让编码人员无需直接操作DOM,提高开发效率
3.使用虚拟DOM+优秀Diff算法,尽量复用DOM节点
初识vue
1.想让vue工作,就必须创建一个vue实例,且要传入一个配置对象
2.root容器里的代码依然符合html规范,只不过混入了一些特殊的vue语法
3.root容器里的代码被称为vue模板
new Vue({
el:'#root',//el用于指定当前vue实例为哪个容器服务,值通常为css选择器字符串
data:{//data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象(等讲到组件的时候会写成函数)
name:'张三'
}
})
容器与vue实例一一对应
真实开发中只有一个vue实例,并且会配合着组件一起使用
{{}}里面要写js表达式,且可以自动读取到data中的所有属性
一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
如何区分js表达式和js代码(语句)
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
如:(1).a (2).a+b (3).demo(1) (4).x==y?‘a’:‘b’
2.js代码(语句)
(1)if(){} (2)for(){}
vue模板语法
vue模板语法有2大类
1.插值语法
功能:用于解析标签体内容
写法:{{}},里面写js表达式,且可以直接读取到data中的所有属性
2.指令语法
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
例如:v-bind:src=‘xxx’ 可简写为 :src=‘xxx’ xxx同样要写js表达式,且可以直接读取到data中的所有属性
vue数据绑定
1.单向绑定(v-bind):数据只能从data流向页面
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
备注:
1.双向绑定一般都应用在表单类元素上(例如:input、select这种输入类元素,通常含有value值)
2.v-model:value可以简写为v-model,因为v-model默认收集的是value值。
el与data的2种写法
1.el
(1).new Vue时候配置el属性
(2).先创建vue实例,随后再通过vm.$mount('#root)指定el的值
2.data
(1).对象式
(2).函数式(学习到组件时,data必须使用函数式,否则会报错,函数中的this指向vue实例)
由vue管理的函数,一定不要写箭头函数,因为箭头函数中的this指向上一层
MVVM模型
1.M:模型(Model):data中的数据
2.V:视图(View):模板代码
3.VM:视图模型(ViewModel):vue实例
观察可得:
1.data中所有的属性,最后都出现在vm身上
2.vm身上所有的属性及vue原型上所有属性,在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(index){
console.log('正在设置age的值');
number=index
}
})
数据代理
1.vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
2.vue中数据代理的好处:更加方便操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上
为每一个添加到vm上的属性,都指定一个getter/setter
在getter/setter内部去操作(读/写)data中对应的属性
{{name}} === {{vm.name}}
{{name}} ----->setter----->data.name
难点:无法直接得到data.name,但是vm._dataoptions.datadata 修改vm.name–>data.name–>{{name}}
{{name}} <-----getter<-----data.name
事件的基本使用
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
2.事件的回调需要配置在methods对象中,最终会在vm上
3.methods中配置的函数,不要使用箭头函数,否则this就不是vm了
4.methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象
5.@click=‘demo’ 和 @click=‘demo($event)’ 效果一致,但后者可以传参
事件修饰符
1.prevent 阻止默认事件
2.stop 阻止事件冒泡
3.once 事件只触发一次
4.capture 使用事件的捕获模式
5.self 只有event.target是当前操作的元素时才触发事件
6.passive 事件的默认行为立即执行,无需等待事件回调执行完毕
键盘事件
1.vue中常用的按键别名:
回车–>enter
删除–>delete
退出–>esc
空格–>space
换行–>tab
上–>up
下–>down
左–>left
右–>right
2.vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名) 例如:caps-lock
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2).配合keydown使用:正常触发事件
4.也可以使用keyCode去指定具体的按键(不推荐:keyCode已经被弃用且不同品牌的键盘其keyCode会不一致)
5.Vue.config.keyCodes.自定义键名=键码,可以去定制按键别名
两个小技巧:
(1).a标签点击要阻止默认行为且阻止冒泡 @click.stop.prevent
(2).按下ctrl+y才触发事件 @keyup.ctrl.y
计算属性computed
1.定义:要用的属性不存在,要通过已有属性计算得来
2.原理:底层借助了Object.defineProperty方法提供的getter和setter
3.get函数什么时候执行
(1).初次读取时会执行一次
(2).当依赖的数据发生改变时会被再次调用
set函数什么时候执行?当计算属性被修改时
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
5.备注:
1.计算属性最终会出现在vm上,直接读取即可(计算属性内的get函数与set函数中的this指向vm实例对象)
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据要发生改变
计算属性简写:
已经明确了计算属性不需要修改,即不需要set函数,可直接写为:
computed:{
fullName(){
return this.firstName+this.secondName
}
}
监视属性
1.当被监视的属性变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视(data内的属性和计算属性都可以被监视)
3.监视的两种写法
(1). new Vue时传入watch配置
watch:{
flag:{
immediate:true,//初始化时让handler调用一下
//handler什么时候调用?当监视的属性发生改变时
handler(newVal,oldVal){
console.log(`flag被修改了,之前是${oldVal},现在是${newVal}`);
}
}
}
(2). 通过vm.$watch监视
app.$watch('weather',{
handler(newVal,oldVal){
console.log(`天气被修改了,之前是${oldVal},现在是${newVal}`);
}
})
深度监视
1.vue中的watch默认不检测对象内部值的改变
2.配置deep:true可以监测对象内部值的改变
注意:
1.vue自身可以监测对象内部值的改变,但vue提供的watch默认不可以
2.使用watch时根据数据的具体结构,决定是否采用深度监视
如果不配置deep:true,默认监视numbers的地址是否发生改变
data:{
flag:true,
numbers:{
a:1,
b:1
}
},
watch:{
numbers:{
deep:true,
handler(){
console.log('注意!numbers被改变了');
}
}
}
深度监视简写
如果不需要配置其他属性,可以将配置对象写为一个函数
watch:{
flag:function(newVal,oldVal){
console.log(`flag被修改了,之前是${oldVal},现在是${newVal}`);
}
}
app.$watch('weather',function(newVal,oldVal){
console.log(`天气被修改了,之前是${oldVal},现在是${newVal}`);
})
computed和watch之间的区别
1.computed和watch能完成的功能,watch都可以完成
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的原则:
1.所被vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
2.所有不被vue所管理的函数(定时器的回调函数,ajax的回调函数,Promise的回调函数…),最好写成箭头函数,这样this的指向才是vm或组件实例对象
绑定样式
1.class样式
写法:class=‘xxx’ xxx可以是字符串、对象、数组
字符串写法适用于:类名不确定、要动态获取
对象写法适用于:要绑定多个样式、个数不确定、名字也不确定
数组写法适用于:要绑定多个样式、个数确定、名字也确定、但不确定用不用
2.style样式
:style="{fontSize:xxx}“其中xxx是动态值
:style=”[a,b]"其中a、b是样式对象
条件渲染
1.v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
适用于:切换频繁较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和v-eles-if、v-else一起使用,但要求结构不能被打断
2.v-show
写法:v-show=“表达式”
适用于:切换频繁较高的场景
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到
当多个元素有相同的显示条件时,可将条件加在父元素身上,但此时可能会破坏元素的结构,导致css样式无法生效,可以配合template和v-if使用
template不会破坏元素结构,但无法配合v-show使用
react、Vue中key的作用(key的内部原理)
1.虚拟DOM中key的作用
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较
2.比较规则
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key
若虚拟DOM中内容没变,直接使用之前的真实DOM
若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到页面
3.用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新----->界面效果没问题,但效率低
2.如果结构中还包含输入类的DOM:
会产生错误DOM更新----->界面有问题
4.开发中如何选择key
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
Vue监视数据的原理
1.vue会监视data中所有层次的数据
2.如何监测对象中的数据?
通过setter实现监视,且要再new Vue时就传入要监测的数据
(1).对象中后追加的属性,vue默认不做响应式处理
(2).如果需要给后添加的属性做响应式,要使用以下API
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
3.如何监测数组中的数据
通过包裹数组更新元素的方法实现,本质是做了两件事
(1).调用原生对应的方法对数组进行更新
(2).重新解析模板,进行页面更新
4.在Vue修改数组中的某个元素的时候要使用以下方法:
(1).使用以下API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2).Vue.set() 或者 vm.$set()
注意:Vue.set()和vm.$set()
不能给vm 或 vm的根数据对象(vm._data)添加属性
收集表单数据
若:,则v-model收集的是value值,用户输入的就是value值
若:,则v-model收集的就是value值,且要给标签配置value值
若:
1.如果没有配置value值,收集的是checked(是否勾选),且一个checked变化,全都变化
2.配置了input的value值
(1).v-model的初始值为数组,收集的是value组成的数组
(2).v-model的初始值为字符串,收集的是checked,且一个checked变化,全都变化
v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效数字
trim:输入首尾空格过滤
过滤器
定义:对要显示的数据进行待定格式化后再显示(适用于一些简单逻辑的处理)
语法:
1.注册过滤器:Vue.filter(name,callback) —>全局,其他vue实例也可以使用 或 new Vue(filters:{}) —>局部
```
Vue.filter('mySub',function(str){
return str.substring(0,4);
})
```
2.使用过滤器:{{xxx|过滤器名}} 或 v-bind:属性=“xxx|过滤器名”
备注:
1.过滤器也可以接收额外参数(内部默认第一个参数为管道符前面的xxx)、多个过滤器也可以串联
2.并没有改变原本的数据,而是产生新的对应的数据
vue指令语法
v-html的作用
1.作用:向指定节点中渲染包含html结构的内容
2.与插值语法的区别
(1).v-html会替换节点中所有的内容,{{xxx}}则不会
(2).v-html可以识别html结构
3.严重注意:v-html有安全性问题
(1).在网站上动态渲染任意html是非常危险的,容易导致xss攻击
(2).一定要在可信的内容上使用v-html,永远不要在用户的提交的内容上
v-cloak指令(没有值)
1.本质是一个特殊属性,vue实例创建完毕并接管容器后,会删掉v-cloak属性
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
v-pre
1.跳过其所在节点上的编译过程
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
v-once
1.v-once所在节点在初次动态渲染后,就视为静态内容了
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
自定义指令
1.定义语法
(1).局部指令
//对象式
new Vue({
directives:{指令名:配置对象}
})
//函数式
new Vue({
directives:function(){
}
})
(2).全局指令
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
//全局注册自定义指令
Vue.directive('green',function(ele,obj){
ele.style.color='green';
})
2.配置对象中常用的3个回调
(1).bind:指令与元素成功绑定时调用
(2).inserted:指令所在元素被插入页面时调用
(3).update:指令所在模板结构被重新解析时调用
注意:回调函数中的this指向window
3.备注:
1.指令定义时不加v-,但使用时要加v-
2.指令名如果是多个单词,要使用kebab-case命名方式,不用camelCase(驼峰)命名
生命周期
1.又名:生命周期回调函数、生命周期函数、生命周期钩子
2.是什么:vue在关键时刻帮我们调用一些特殊名称的函数
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
4.生命周期函数中的this指向的是vm或者组件实例对象
常见的生命周期钩子:
1.mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化工作
2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁vue实例
1.销毁后借助vue开发者工具看不到任何消息
2.销毁后自定义事件会失效,但原生DOM事件依然有效
3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会触发更新流程了
vue组件
vue中使用组件的三大步骤:
1.定义组件(创建组件)
2.注册组件
3.使用组件(写组件标签)
1.如何定义一个组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有区别
区别如下:
1.el不要写—>最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
2.data必须写成函数—>避免组件被复用时,数据存在引用关系
如果不写成函数,直接写成对象,多个模块复用时,使用数据为浅拷贝,会更改原来data中的数据,导致其他模块数据变更
备注:使用template可以配置组件结构
2.如何注册组件
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component(‘组件名’,组件)
let hi=Vue.extend({
template:`
<h3>say HelloWorld</h3>
`
})
Vue.component('hi',hi)
3.编写组件标签
<school></school>
组件的几个注意点
1.关于组件名
一个单词组成:
写法一:首字母小写----->school
写法二:首字母大写----->School
多个单词组成:
写法一:kebab-case命名:my-school
写法二:CamelCase命名:MySchool(需要脚手架支持)
备注:
1.组件名尽可能回避html中已有的元素名称,例如:h2、H2都不行
2.可以使用name配置项指定组件在开发者工具中呈现的名字
2.关于组件标签
写法一:<school></school>
写法二:<school/>
写法三:不能使用脚手架时,<school/>
会导致后续组件不能渲染
3.一个简写方式
const school=Vue.extend(options)----->const school=options
VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
2.我们只需要写<school></school>
或者<school/>
,Vue解析时会帮我们创建school组件实例对象,
即Vue帮我们执行:new VueComponents(options)
3.需要注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!
4.关于this的指向
(1).组件配置中:
data函数、methods函数、watch函数、computed函数、它们的this均是 VueCpmponent实例对象—>vc
(2).new Vue()配置中
data函数、methods函数、watch函数、computed函数、它们的this均是 Vue实例对象—>vm
5.VueComponent的实例对象,以后简称为vc—>组件实例对象
一个重要的内置关系
1.一个重要的内置关系:VueComponent.prototype.proto===Vue.Prototype
2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
vue-cli
ref
1.作用:用于给节点打标识
2.读取方式:this.$refs.xxxxx
<template>
<div>
<button ref="btn" @click="showInfo">click</button><br>
</div>
</template>
methods:{
showInfo(){
console.log(this.$refs);
}
},
可以通过这种方式拿到子组件的实例对象
props
问题引入:如果一个模块多次复用,可用到的具体数据不一样如何处理???
1.作用:父组件给子组件传递数据
如果只是传入普通且明确的数据,直接写name="xxx"即可,但如果要传入数组、对象或是data里的数据,要使用v-bind:name=“xxx”
<template>
<div>
<h3>APP组件</h3>
<school-vue></school-vue>
<student-vue name='张三' age='18'></student-vue>
<student-vue name='李四' age='17'></student-vue>
</div>
</template>
2.子组件如何获取数据
方式一:只指定名称
props:[‘name’,‘age’]
方式二:指定名称和类型
props:{
name:String,
age:Number
}
方式三:指定名称、类型、默认值
props:{
name:{
type:String,
required:true,
default:xxx
},
age:{
type:Number,
required:true,
default:xxx
}
}
mixin(混入)
问题引入:不同组件要使用相同的方法或数据
在src下创建一个mixin.js,在里面写一些methods和data暴露出去
export default {
methods: {
show(){
alert(this.name)
}
},
}
在组件内引入mixin----->import mixin from ‘…/mixin’
在创建组件时传入配置对象options----->mixins:[mixin]
插件(plugins)
问题引入:不同组件可以使用相同的过滤器filter和自定义指令directive
在src下创建一个plugins.js,向外暴露出去,里面配置一个install方法,该方法的参数ele为全局对象Vue
export default {
install(vue){
vue.filter('mySub',(str)=>{
return str.substring(0,4);
})
}
}
在main.js内导入plugins.js并使用
import plugins from './plugins'
Vue.use(plugins)
scoped
问题引入:不同组件可能会有相同的css类名,导致引入组件时css样式覆盖
scoped有作用域的意思,在组件的style标签上使用scoped即可
<style scoped></style>
todoList
把该案例分为4个组件,Header.vue、List.vue、Item.vue、Footer.vue
问题引入:list中要遍历数组,所以把核心数据写在list里面,却无法得到从header传入而来的数据,即:当前无法解决兄弟之间通信的问题
解决办法:把核心数据写在最大的组件app内,把兄弟之间通信的问题转化为父子之间通信的问题
方法一:利用props,父组件给子组件传入一个方法,该方法的回调写在父组件里,当相关事件被触发时,子组件主动调用该事件,父组件做出相应行为
父组件:
<template>
<div class="container">
<header-vue :addList="addList"></header-vue>
</div>
</template>
methods:{
addList(item){
this.todoList.unshift(item);
},
}
子组件:
methods: {
add(){
…
this.addList(obj);//主动调用该函数
…
}
},
props:[‘addList’]//接收该函数名称
依旧存在问题:爷爷组件要想跟孙子组件通信,需要先传给父亲组件,父亲组件再传给孙子组件,可能父亲组件压根使用不上该函数
组件自定义事件
方法二;父组件给子组件绑定一个自定义事件,其回调函数写在父组件内,当相关事件被触发时,子组件主动调用(执行)该绑定事件,父组件做出回应
父组件:
方式一:
//绑定自定义事件
<template>
<div class=".app">
<student-vue @ggBond="sendStudentName" ref='student'></student-vue>
</div>
</template>
//回调函数
methods:{
sendStudentName(name){
this.studentName=name;
}
},
方式二:
//绑定自定义事件
<template>
<div class=".app">
<school-vue ref="school"></school-vue>
</div>
</template>
mounted(){
//给子组件绑定事件,回调写在父组件里面,事情都由父组件来干,类似于点击div1,div2变化形态与颜色
this.$refs.school.$on('sendSchoolName',(name)=>{
this.schoolName=name
})
}
子组件
methods:{
showInfo(){
this.$emit('sendSchoolName',this.name);//主动执行该函数
}
}
解绑自定义事件:
this.
o
f
f
(
)
/
/
解绑组件上的所有事件
t
h
i
s
.
off()//解绑组件上的所有事件 this.
off()//解绑组件上的所有事件this.off(‘ggBond’)//解绑指定事件
this.$off([‘ggBond’,‘show’])//解绑多个事件
全局事件总线
方法三:全局事件总线
引入:受组件自定义事件启发,是否存在这么一个"东西",其有以下两个特征:
特征一:它可以被所有组件实例对象看到
特征二:它具有自定义事件相关的API,
o
n
、
on、
on、emit、KaTeX parse error: Expected group after '_' at position 37: …nent.prototype._̲_proto__===Vue.…bus
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.KaTeX parse error: Expected 'EOF', got '}' at position 12: bus=this }̲ }).mount(‘#app’)
在需要数据的组件中定义事件:
mounted(){
this.
b
u
s
.
bus.
bus.on(‘getSchoolName’,(name)=>{
console.log(name);
})
},
beforeDestroy(){
//在组件销毁时解绑事件,防止占用
this.
b
u
s
.
bus.
bus.off(‘getSchoolName’);
}
在创造数据的组件中主动执行该函数:
methods:{
sendSchoolName(){
this.
b
u
s
.
bus.
bus.emit(‘getSchoolName’,this.name);
}
}
消息订阅与发布
方法四:消息订阅与发布
安装:
npm install pubsub-js
引用:
import pubsub from ‘pubsub-js’
在需要数据的组件里订阅消息:
mounted(){
this.subId=pubsub.subscribe(‘title’,(msg,data)=>{
console.log(我收到了订阅的消息${data}
);
})
}
beforeCreate(){
pubsub.unsubscribe(this.subId);
}
在创造数据的组件里发布消息:
methods:{
showInfo(){
pubsub.publish(‘title’,this.name)
}
}
nextTick
1.语法:this.$nextTick(callback)
2.作用:在下一次DOM更新结束后执行其指定的回调
3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
vue中的ajax
代理服务器.
当我们在本机的8080端口去访问5000端口的服务器时,会因为跨域问题(协议、域名、端口号)而无法接收到浏览器返回的数据,此时,我们可以使用代理服务器来解决这个问题,代理服务器的端口号与发送请求的端口号一致,尽管代理服务器的端口号与访问的服务器端口号不一致,但同源策略限制不了服务器
方式一:
vue.config.js
module.exports = {
devServer: {
proxy: 'http://localhost:5000'//代理服务器去访问的地址
}
}
App.vue
methods: {
getStudent(){
axios.get('http://localhost:8080/students')//访问代理服务器,提出要求
.then(val=>console.log(val.data))
.catch(err=>console.log(err))
},
},
此时仍存在两个问题:
问题一:只能设置一个代理服务器访问的服务器地址
问题二:当本机服务器上存在与访问资源相同名字的文件时,代理服务器会返回本机服务器上的资源
方式二:
vue.config.js
devServer: {
proxy: {
'/ggBond': {//以ggBond开头请求的具体路径代理服务器都会转发给target
target: 'http://localhost:5000',
pathRewrite:{'^/ggBond':''},//重新修改路径,不要'/ggBond'
ws: true,//用于支持websocket
changeOrigin: true//用于控制请求头中的host值
},
'/zzBond': {
target: 'http://localhost:5001',
pathRewrite:{'^/zzBond':''}
}
}
}
App.vue
methods: {
getStudent(){
axios.get('http://localhost:8080/ggBond/students')
.then(val=>console.log(val.data))
.catch(err=>console.log(err))
},
getCar(){
axios.get('http://localhost:8080/zzBond/cars')
.then(val=>console.log(val.data))
.catch(err=>console.log(err))
}
},
方式二中我们在给代理服务器发送请求时,在端口号后面加上一个路径并在vue.config.js中进行配置,以后只要是以该路径为首的请求资源,代理服务器都会帮我们进行转发,但此时请求的路径包含了该标识路径,需要对请求路径进行重写,通过正则表达式匹配该标识路径并转化为空
两个常用的ajax库
axios
安装:npm i axios
引入:import axios from ‘axios’
使用:
methods:{
getUser(){
this.$bus.$emit('getUserList',{isFirst:false,isLoading:true,userList:[],errMsg:''})
axios.get('https://api.github.com/search/users?q='+this.query)
.then(res=>{
this.$bus.$emit('getUserList',{isLoading:false,userList:res.data.items,errMsg:''})
})
.catch(err=>{
this.$bus.$emit('getUserList',{isLoading:false,userList:[],errMsg:err.message})
})
}
}
vue-resource
官方已不再维护,推荐使用axios
安装:npm i vue-resource
安装完成之后,组件实例对象身上会多出一个对象$http
使用:
methods:{
getUser(){
this.$bus.$emit('getUserList',{isFirst:false,isLoading:true,userList:[],errMsg:''})
this.$http.get('https://api.github.com/search/users?q='+this.query)
.then(res=>{
this.$bus.$emit('getUserList',{isLoading:false,userList:res.data.items,errMsg:''})
})
.catch(err=>{
this.$bus.$emit('getUserList',{isLoading:false,userList:[],errMsg:err.message})
})
}
}
插槽
默认插槽
同一组件可能展示不同类型的内容,可以通过slot进行占位
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 通过插槽进行占位 -->
<slot></slot>
</div>
</template>
App.vue
<template>
<div class="container">
<category-vue title="美食" :list="foods">
<!-- 展示图片 -->
<img src="" alt="">
</category-vue>
<category-vue title="水果" :list="fruits">
<!-- 展示视频 -->
<video controls src=""></video>
</category-vue>
<category-vue title="游戏" :list="games">
<!-- 展示列表 -->
<ul>
<li v-for="(item,index) in games" :key="index">
{{item}}
</li>
</ul>
</category-vue>
</div>
</template>
具名插槽
通过给插槽取名字进行标识,例如:<slot name="footer"></slot>
,占位时也要对应插槽名字进行标识
<a slot="footer" href="https://www.bilibili.com/">bilibili</a>
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot name="content"></slot>
<slot name="footer"></slot>
</div>
</template>
App.vue
<category-vue title="游戏" :list="games">
<ul slot="content">
<li v-for="(item,index) in games" :key="index">
{{item}}
</li>
</ul>
<template slot="footer">
<div class="foot">
<a href="https://www.bilibili.com/">bilibili</a>
<a href="https://www.bilibili.com/">bilibili</a>
<a href="https://www.bilibili.com/">bilibili</a>
</div>
<h5>吾乃常山赵子龙</h5>
</template>
</category-vue>
作用域插槽
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽,可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes。即当核心数据在子组件里,父组件要通过数据进行内容的不同风格展示
子组件
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="游戏详细信息"></slot>
</div>
</template>
父组件
<template>
<div class="container">
<category-vue title="游戏">
<template scope="{games,msg}">
<div>
<h5>{{msg}}</h5>
<ul>
<li v-for="(item,index) in games" :key="index">
{{item}}
</li>
</ul>
</div>
</template>
</category-vue>
<category-vue title="游戏">
<template scope="data">
<div>
<h5>{{data.msg}}</h5>
<ol>
<li v-for="(item,index) in data.games" :key="index">
{{item}}
</li>
</ol>
</div>
</template>
</category-vue>
</div>
</template>
vuex
搭建Vuex环境
问题引入:用组件编写简单的求和器,如果sum在app组件里,而操作数据的按钮在Count组件里,如果使用全局事件总线要写大量的方法,
故使用Vuex,使所有的组件都可以访问到变量sum,实现全局数据共享
流程:
当用户在页面点击相加按钮时,可以在点击事件的函数里面主动调用dispatch函数,里面传入2个参数,对应事件名与相关数据,之后来到Actions这里,根据对应事件名查找相关函数,相关函数里面有2个参数,一个迷你store和相关数据,通过迷你store里面的commit(事件名,数据)函数流程来到Mutations这里,根据事件名查找相关函数,相关函数里面有2个参数,包含数据的state和传入而来的数据,通过代码完成数据操作,最后页面进行更新;
页面点击事件----->store.dispatch(事件名A,val)----->Actions中查找事件A:事件A(miniStore,val,){miniStore.commit(事件B,val)}
----->Mutations中查找事件B:事件B(state,val){操作数据代码}----->页面更新
以上流程中:页面类似于顾客,Actions类似于服务员,Mutations类似于后厨,State类似于待加工的食物
疑问:Actions中只是调用了相关函数,之后流程来到Mutations这里,Actions是否多余?
Actions中主要用于与后端交互,一些逻辑代码通常写在这里,如:连接数据库、发送ajax请求,如果代码逻辑简单,只是简单操作数据,可以省略这一步,直接快进到Mutations这里
操作步骤如下:
1.安装Vuex
npm i vuex
注意:vue2要安装vuex的3版本,vue3要安装vuex的4版本
2.引入Vuex插件并使用
import Vuex from ‘vuex’
Vue.use(Vuex)
注意:要先使用Vuex插件再配置store实例对象,若在main.js里面使用Vuex插件再引入store,因为js会把import语句提前导致报错,
所以要在index.js里面使用Vuex插件(要引入Vue全局对象)
3.在src下新建store文件夹,store文件夹里面新建index.js,在index.js里面声明store变量并暴露出去
store对象里面有三个核心的配置项:
1.actions–用于响应组件中的动作
const actions={
check(context,val){
if(context.state.sum % 2 !== 0){
context.commit('CHECK',val);
}
},
wait(context,val){
setTimeout(()=>{
context.commit('WAIT',val);
},1000)
}
}
2.mutations–用于操作数据(state)
const mutations={
ADD(store,val){
store.sum+=val;
},
SUB(store,val){
store.sum-=val;
},
CHECK(state,val){
state.sum+=val;
},
WAIT(state,val){
state.sum+=val;
}
}
3.state–用于存储数据
const state={
sum:0,
persons:[{
name:'张三',
age:18
},{
name:'李四',
age:16
},{
name:'王五',
age:21
}]
}
将核心数据配置到Store里面:
export default new Vuex.Store({
actions,
mutations,
state,
})
4.在main.js里面引入store并传入配置对象options
new Vue({
render: h => h(App),
store
}).$mount('#app')
页面使用数据:{{$store.state.sum}}
getters
类似于计算属性
const getters={
mulSum(state){
return state.sum*10;
}
}
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
使用:{{$store.getters.mulSum}}
mapState与mapGetters
问题引入:页面中使用数据要重复写: s t o r e . s t a t e . x x x 或 store.state.xxx或 store.state.xxx或store.getters.xxx,代码繁琐,可以考虑使用计算属性:
computed:{
sum(){
return this.$store.state.sum
},
mulSum(){
return this.$store.getters.mulSum
}
},
这样在页面上就可以直接使用{{sum}}和{{mulSum}},不过写计算属性也很麻烦,Vue团队也考虑到了这个问题并设计了mapState与mapGetters,它们可以帮我们生成代码:
引入:import {mapState,mapGetters} from ‘vuex’
使用:
mapState([‘sum’]),其返回值为一个对象,里面的每一项都是一个函数,等价于sum(){return this.KaTeX parse error: Expected 'EOF', got '}' at position 16: store.state.sum}̲,最后使用扩展符写入计算属性内…store.state.sum}
computed:{
...mapState({ggBond:'sum'}),
...mapGetters(['mulSum'])
},
mapActions与mapMutations
与mapState和mapGetters类似,mapActions和mapMutations可以帮我们生成关于methods的代码:
mapMutations({add:‘ADD’})等价于add(val){this.$store.commit(‘ADD’,val)},注意:此时在页面上绑定函数时要传入数据
mapActions类似,mapActions({check:‘check’})等价于check(val){this.$store.dispatch(‘check’,val)}
为了区分Actions与Mutations里的函数,通常Actions里的函数名称小写,Mutations里的函数名称大写
methods:{
...mapMutations({add:'ADD',sub:'SUB'}),
...mapActions({check:'check',wait:'wait'})
}
等价于:
methods:{
add(){
this.$store.commit('ADD',this.num);
},
sub(){
this.$store.commit('SUB',this.num);
},
check(){
this.$store.dispatch('check',this.num);
},
wait(){
this.$store.dispatch('wait',this.num);
}
}
此时页面的触发事件要传入数据
<button @click="add(num)">+</button>
<button @click="sub(num)">-</button>
<button @click="check(num)">当前和为奇数相加</button>
<button @click="wait(num)">等一等再加</button>
vue-router
路由的基本使用
1.安装vue-router
npm i vue-router
2.应用插件
在main.js中引入vue-router并使用
import VueRouter from "vue-router"
Vue.use(VueRouter)
3.在src下新建router文件夹,在文件夹里面新建index.js,在index.js里面配置router并暴露出去
import VueRouter from "vue-router";
import School from '../pages/School'
import Student from '../pages/Student'
export default new VueRouter({
routes: [
{
path: '/home',
component: School,
}, {
path: '/about',
component: Student,
}
]
})
4.在main.js里面引入router
import router from './router'
new Vue({
render: h => h(App),
router
}).$mount('#app')
5.在需要实现切换功能的地方配置router-link
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
6.在需要展示不同内容的地方配置router-view
<router-view></router-view>
几个需要注意的点
1.路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
2.通过切换,"隐藏"了的路由组件,默认是被销毁的,需要的时候再去挂载,可以通过mounted,beforeDestroy生命周期函数去验证
3.每个组件都有自己的$route属性,里面存储着自己的路由信息
4.整个应用只有一个router,可以通过组件的$router属性获取到
嵌套路由
子路由写在一级路由的children配置数组里面
index.js
export default new VueRouter({
routes: [
{
path: '/home',
component: School,
}, {
path: '/about',
component: Student,
children: [{
path: 'mike',
component: Mike,
}, {
path: 'kimi',
component: Kimi
}]
}
]
})
使用:
<router-link class="list-group-item" active-class="active" to="/about/mike">Mike</router-link>
<router-link class="list-group-item" active-class="active" to="/about/kimi">Kimi</router-link>
路由的query参数
问题引入:有10个链接,点击之后展示的内容不同但结构相同,可以考虑只写一个组件,随着点击不同的链接给组件传入不同的参数而展示内容
使用:
方式一:
<router-link :to="`/about/mike/detail?id=${item.id}&name=${item.name}&age=${item.age}`">{{item.name}}</router-link>
方式二:
<router-link :to="{
path:'/about/mike/detail',
query:{
id:item.id,
name:item.name,
age:item.age
}
}">
{{item.name}}
</router-link>
Detail.vue内展示:
<template>
<div>
<h3>{{$route.query.id}}</h3>
<h3>{{$route.query.name}}</h3>
<h3>{{$route.query.age}}</h3>
</div>
</template>
命名路由
给路由取名字,可以将三级路由简化,只适用于params写法
<router-link :to="{
name:'detail',
params:{
id:item.id,
name:item.name,
age:item.age
}
}">
{{item.name}}
</router-link>
路由的params参数
需要在index.js内配置路径: path: ‘detail/:id/:name/:age’
使用:
方式一:
<router-link :to="`/about/mike/detail/${item.id}/${item.name}/${item.age}`">{{item.name}}</router-link>
方式二:
<router-link :to="{
name:'detail',
params:{
id:item.id,
name:item.name,
age:item.age
}
}">
{{item.name}}
</router-link>
在Detail.vue内使用:
<template>
<div>
<h3>{{$route.params.id}}</h3>
<h3>{{$route.params.name}}</h3>
<h3>{{$route.params.age}}</h3>
</div>
</template>
路由的props配置
问题引入:在页面上重复写{{KaTeX parse error: Expected 'EOF', got '}' at position 15: route.query.id}̲}和{{route.params.id}}太过麻烦,希望能够简化代码
在router/index.js内,需要数据的子组件配置props参数,然后在组件页面接收即可
方式一:
props:{a:1,b:2}
方式二:
props:true
方式三:
props($router) {
// return $router.params
return {
id: $router.params.id,
name: $router.params.name,
age: $router.params.age
}
}
组件页面使用:
<template>
<div>
<h3>{{id}}</h3>
<h3>{{name}}</h3>
<h3>{{age}}</h3>
</div>
</template>
<script>
export default {
name:'DetailVue',
props:['id','name','age']
}
</script>
router-link的replace属性
1.作用:控制路由跳转时操作浏览器历史记录的模式
2.浏览器的历史纪录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
3.如何开启replace模式:
<router-link replace>News<router-link>
编程式路由导航
1.作用:不借助router-link(如借助按钮)实现路由跳转,让路由跳转更加灵活
2.具体编码:
methods:{
pushShow(item){
this.$router.push({
name:'detail',
params:{
id:item.id,
name:item.name,
age:item.age
}
})
},
replaceShow(item){
this.$router.replace({
name:'detail',
params:{
id:item.id,
name:item.name,
age:item.age
}
})
}
},
this.$router.forward()//前进
this.$router.back()//后退
this.$router.go()//可前进也可后退
缓存路由组件
1.作用:让不展示的路由组件保持挂载,不被销毁
2.具体编码
<keep-alive include="KimiVue">
<router-view></router-view>
</keep-alive>
注意:
1.keep-alive写在需要缓存组件的父组件内
2.include内写的是组件的名字name
两个新的生命周期
1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
2.具体名字:
1.activated路由组件被激活时触发
2.deactivated路由组件失活时触发
路由守卫
前置路由守卫与后置路由守卫
问题引入:只有符合条件的用户点击切换栏展示区内显示的组件才会变化
作用:对路由进行权限控制
流程:进入页面前—>全局前置守卫—>进入页面next()—>全局后置守卫
一些判断条件或标题信息配置在路由的meta:{}里面
//全局前置守卫
router.beforeEach((to,from,next)=>{
if(to.name=='mike' || to.name=='kimi'){
if(localStorage.getItem('school')=='ecjtu'){
next()
}
}else{
next()
}
})
//全局后置守卫
router.afterEach((to)=>{
document.title=to.meta.title || '硅谷系统';
})
export default router
独享路由守卫
const router = new VueRouter({
routes: [
{
name: "home",
path: '/home',
component: School,
meta: { title: '主页' },
beforeEnter(to, from, next) {
if (to.name == 'mike' || to.name == 'kimi') {
if (localStorage.getItem('school') == 'ecjtu') {
next()
}
} else {
next()
}
},
}
})
组件路由守卫
流程:
进入页面前—>beforeRouteEnter—>进入页面next()
离开页面前—>beforeRouteLeave—>离开页面next()
//通过路由规则,进入组件时被调用
beforeRouteEnter(to,from,next){
if(localStorage.getItem('school')=='ecjtu'){
next()
}
},
//通过路由规则,离开组件时调用
beforeRouteLeave(to,from,next){
}
路由器工作的两种模式
1.对于一个url来说,什么是hash值?—>#及其后面的内容就是hash值
2.hash值不会包含在HTTP请求中,即:hash值不会带给服务器
3.hash模式:
1.地址中永远带着#号,不美观
2.若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
3.兼容性较好
4.histroy模式:
1.地址干净、美观
2. 兼容性和hash模式相比略差
3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404问题
element-ui
vue3
自定义hook
问题引入:不同组件要使用相同的方法或js模块,提高代码复用率,类似于vue2中的mixin
具体实现步骤:
1.src下新建hooks文件夹,在文件夹里面新建useName.js
2.创建一个函数并暴露出去,函数要有返回值,看是需要数据还是函数
3.在需要使用的模块引入,因为暴露的是函数,可以使用let getName=useName()这种方式拿到返回值,在setup()中暴露出去
export default ()=>{
return function(){
alert(this.name);
}
}
//使用
import useName from '../hooks/useName'
setup(){
let name=ref('刘大炮');
let getName=useName()
return {
name,
getName
}
}
toRef与toRefs
问题引入:使用reactive定义响应式对象在读取值时不需要xxx.value,但是需要person.name,person.age,希望在setup()中返回数据时可以简化操作
误区一:
setup(){
let person =reactive({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
return {
name:person.name,
age:person.age,
salary:person.job.j1.salary
}
}
这种操作等同于:
return {
name:'张三',
age:25,
salary:8
}
直接丢失了响应式
误区二:
setup(){
let person =reactive({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
return {
name:ref(person.name),
age:ref(person.age),
salary:ref(person.job.j1.salary)
}
}
这种操作虽然返回了响应式数据:name,age,salary,但是它们跟person并无关系,数据分家了,name,age,salary的改变并不能影响到person
toRef可以将数据变成ref响应式实例对象,其value值指向传入对象的某个属性,利用get和set达到响应式处理
setup(){
let person =reactive({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job.j1,'salary')
}
}
toRefs可以批量处理传入对象,但是创造的数据只在最外那一场
shallowReactive与shallowRef
shallowReactive:shallow浅层次的,只给第一层数据做响应式,job.j1.salary可以变化但是没有响应式
let person =shallowReactive({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
shallowRef:处理基本数据类型与ref没有区别,但是处理复杂数据类型不会请求reactive待处理
let sum=shallowRef(0);
let person =shallowRef({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
//输出person为:{name: '张三', age: 25, job: {…}}
readonly与shallowReadonly
readonly:只允许读取,不允许修改
shallowReadonly:第一层数据只允许读取,不允许修改,深层次的数据可以修改
setup(){
let sum=ref(0);
let person =reactive({
name:'张三',
age:25,
job:{
j1:{
salary:8
}
}
})
sum=readonly(sum)
person=shallowReadonly(person)
return {
person,
sum
}
}
toRaw与markRow
toRaw:将由reactive创造的复杂数据类型的响应式数据变为普通数据
markRow:将响应式数据永远变成普通数据
customRef
问题引入:在input框内输入内容,1s之后显示在h3中
setup(){
let time;
function myRef(value){
return customRef((track,trigger)=>{
return {
get(){
console.log(`你想要myRef容器中的值吗?那就给你${value}好了`);
track();//提示Vue重新追加value值,返回最新值
return value;
},
set(newVal){
console.log(`你想要修改myRef容器中的值为${newVal}吗?让我来帮你好了`);
clearTimeout(time);
time=setTimeout(()=>{
value=newVal;
trigger();//提示Vue数据被修改了,去重新解析模板
},500)
}
}
})
}
let query=myRef('HelloPain');
return {
query
}
}
Teleport
问题引入:组件的显示与隐藏会撑开父组件,考虑把组件的一部分显示在页面正中央,类似弹出表单
<teleport to='body' >
<div v-if="isShow" class="dialog">
<form action="">
<input type="text">
<input type="password">
</form>
<button @click="isShow=false">close</button>
</div>
</teleport>
Suspense
问题引入:由于网络问题组件的加载可能会受到影响,如果是静态引入会造成阻塞,考虑异步引入组件,未加载时显示"加载中,请稍后…"
父组件
<template>
<div class="app">
<h1>这里是APP组件</h1>
<Suspense>
<template v-slot:default>
<list></list>
</template>
<template v-slot:fallback>
<h3>加载中,请稍后...</h3>
</template>
</Suspense>
</div>
</template>
<script>
// import ListVue from './components/List.vue' //静态引入
import {defineAsyncComponent} from 'vue'
const list=defineAsyncComponent(()=>import('./components/List.vue'))
export default {
name:'App',
components:{list},
}
</script>
子组件
setup(){
let sum=ref(0)
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({sum})
},3000)
})
}