setup()
新的配置项,值为一个函数,用于配置组件中所用到的所有数据、方法。若返回一个对象,则对象中的属性、方法在模板中均可以直接使用;若返回一个渲染函数,则可以自定义渲染内容(了解)。
注意:不建议和vue2的配置项混用;setup不能是一个async函数。
setup执行的时机:在beforeCreate之前执行一次,this是undefined。
setup的参数:props
值为对象,包含组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象;attr
值为对象,包含租金按外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
。slots
收到的插槽内容,相当于this.$slots
。emit
分发自定义事件的函数,相当于this.$emit
。
ref
函数
作用:定义一个响应式数据的引用对象(reference对象,简称ref对象)
语法:
定义数据:const xxx = ref(initValue)
js中操作数据 :xxx.value
模板中读取数据:
注意:接收的数据可以是基本类型也可以是对象类型;基本类型数据的响应式靠Object.defineProperty()
的get
与set
实现;对象类型数据的响应式使用到了vue3中的reactive
函数。
reactive
函数
作用:定义一个对象类型的响应式数据(基本类型应该使用ref
函数)
语法: const 代理对象 = reactive(源对象)
接受一个对象(或数组),返回一个代理对象(proxy对象)
特点:reactive定义的响应式数据是深层次的,内部基于ES6的Proxy实现,通过代理对象操作源对象数据。
计算属性与监视
computed
函数
与vue2中computed配置功能一致
写法
import {computed} from 'vue'
setup() {
...
// 1)计算属性——简写
let fullName = computed(() = {
return person.firstName + '-' + person.lastName
})
// 2)计算属性——完整
let fullName = computed(() => {
get() {
return person.firstName + '-' + person.lastName
},
set(value) {
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
watch
函数
与vue2中watch配置功能一致
注意 1)监视reactive
定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
2)监视reactive
定义的响应式数据中某个属性时:deep配置有效
// 情况一:监视ref定义的响应式数据
watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue,oldValue)
}, {immediate:true})
// 情况二:监视多个ref定义的响应式数据
watch([sum, msg], (newValue, oldValue) => {
console.log('sum或msg变化了', newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视(deep属性无效)
*/
watch(person,(newValue,olaValue) => {
console.log('person变化了',newValue, oldValue)
},{immediate:true, deep:false}) // 此处的deep配置不再奏效
// 情况四:监视reactive定义的响应式数据中的某个属性
watch(() =>person.job,(newValue,oldValue) => {
console.log('person的job变化了',newValue,oldValue)
}, {immediate:true,deep:true})
// 情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([()=> person.name, ()=>person.value],(newValue,oldValue)=> {
console.log('person的name或age变化了',newValue,oldValue)
})
// 特殊情况
watch(()=>person.job, (newValue, oldValue) =>{
console.log('person的job变化了',newValue,oldValue)
}, {deep:true}) // 此处由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效
watchEffect
函数
watch
函数的特点:既要指明监视的属性,也要指明监视的回调。
watchEffect
函数的特点:不用指明监视哪个属性,监视的回调中用到哪个属性就会监视哪个属性。
watchEffect
类似于computed
,但computed
注重计算出来的值(回调函数的返回值),所以必须要写返回值。而watchEffect
更注重的是过程(回调函数的函数体),所以不用写返回值。
// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
自定义hook
函数
hook
本质上是一个函数,把setup函数中使用的Composition API进行了封装。类似于vue2中的minxin,自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂。
toRef
作用:创建一个ref对象,其value值指向另一个对象中的某个属性。语法:const name = toRef(person, 'name')
应用:要将响应式对象中的某个属性单独提供给外部使用时。toRefs
与toRef
功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
shallowReactive
与shallowRef
shallowReactive
只处理对象最外层属性的响应式(浅响应式)。shallowRef
只处理基本数据类型的响应式,不进行对象的响应式处理。如果有一个对象数据,结构比较深,但变化时只是外层属性变化,则考虑使用shallowReactive
;如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,考虑使用shallowRef
。
readonly
与shallowReadonly
readonly
:让一个响应式数据变为只读的(深只读)。shallowReadonly
:让一个响应式数据变为只读的(浅只读)。应用场景:不希望数据被修改时。
toRaw
与markRaw
toRaw
作用:将一个由reactive
生成的响应式对象转为普通对象。使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面更新。
markRaw
作用:标记一个对象,使其永远不会再成为响应式对象。应用场景:有些值不应被设置为响应式的,例如复杂的第三方类库等;当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
customRef
:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。
provide
与inject
作用:实现祖孙组件间通信,父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据。
isRef
:检查一个值是否为一个ref对象
isReactive
:检查一个对象是否是由reactive
创建的响应式代理
isReadonly
:检查一个对象是否是由readonly
创建的只读代理
isProxy
:检查一个对象是否是由reactive
或者readonly
方法创建的代理
Vue:一套用于构建用户界面的渐进式JavaScript框架
Vue的特点:
1.采用组件化模式,提高代码复用率,且让代码更好维护
2.声明式编码,让编程人员无需直接操作DOM,提高开发效率
3.使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
Vue会管理el选项命中的元素及其内部的后代元素
el:用来设置Vue实例挂载的元素
el的两种写法:
(1)new Vue 时配置el属性
(2)先创建Vue实例,随后再通过vm.$mount("#root")
指定el的值
el的写法规范:
(1)可以使用其他选择器,建议使用ID选择器
(2)可以使用其他双标签,不能使用HTML和BODY
data:数据对象,Vue中用到的数据定义在data中
data中可以写复杂类型的数据,渲染复杂类型的数据时,遵守js的语法即可。
data的两种写法:
(1)对象式:
data:{
name:""
}
(2)函数式:
data:function(){ //此处的this是Vue实例对象
return{
name:""}
}
注:组件运用时必须使用函数式。
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不是Vue实例了
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象{el,data...}
2.容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
3.容器里的代码被称为Vue模板
4.Vue实例与挂载容器一一对应
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用
6.一旦数据发生改变,那么页面中用到该数据的地方也会自动更新
Vue模板语法有2大类:
1.插值语法:
功能:用于解析标签体内容
写法:{{xxx}}
xxx是s表达式,xxx可以直接读取到data中的所有属性
2.指令语法:
功能:用于解析标签(标签属性,标签体内容,绑定事件…)
Vue中有两种绑定的方式:
1.单向绑定(v-bind):数据只能从data流向页面
2.双向绑定(v-model): 数据不仅能从data流向页面,还可以从页面流向data
备注:v-model:value
简写为v-model
,因为默认收集value值
1.M: 模型(Model):data中的数据
2.V:视图(View):模板代码
3.VM:视图模型(ViewModel):Vue实例
data中所有的属性最后都出现在了VM身上
VM身上所有的属性及Vue原型上所有的属性,在Vue模板中都可以直接使用
(1)数据代理
运用Object.defineProperty(操作对象,“属性名”,{配置项})
方法
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
(2)Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
好处:更加方便操作data中的数据
基本原理:
通过Object.defineProperty()
把data对象中所有属性添加到vm上,为每一个添加到vm上的属性都指定一个getter/setter
,
在getter/setter
内部去操作data中对应的属性
1.使用v-on绑定事件
2.事件的回调需要配置在methods对象中,最终会在vm上
3.methods中配置的函数都是Vue所管理的函数,this的指向是vm或组件实例对象
4.@click="demo($event)"
可以实现传参
prevent:阻止默认事件
stop:阻止事件冒泡
once:事件只触发一次
Vue常用的按键别名:
回车 =>enter
删除 =>delete(捕获“删除”和“退格”键)
退出 =>esc
空格 =>space
换行 =>tab(配合keydown使用)
上 =>up (下左右以此类推)
1.定义:要用的属性不存在,要通过已有属性计算得来
2.原理:底层借助了Object.defineproperty
方法提供的getter
和setter
3.getter函数什么时候执行?
(1)初次读取时会执行一次
(2)当依赖的数据发生变化时会被再次调用
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
5.备注:
计算属性最终会出现在vm上,直接读取使用即可
如果计算属性要被修改,那么必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化。
只有考虑读取不考虑修改的情况下才可以使用计算属性的简写方式
computed:{
// 计算属性靠返回值决定,所以一定要加上return!!!
fullName(){
return this.fullName = this.firstName + "-" +this.lastName
}
},
1.当被监视的属性变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视
3.监视的两种写法:
(1)new Vue时传入watch配置
(2)通过Vm.$watch
监视
4.深度监视:
(1)Vue中的watch默认不监测对象内部值的改变(一层)
(2)配置deep:true
可以监测对象内部值的改变(多层)
5.备注:
Vue本身可以监测对象内部值的改变,但Vue提供的watch默认不可以,使用watch时根据数据的具体结构,决定是否采用深度检测。
watch里面的handler()什么时候调用?当监测对象发生改变时
immediate:true
初始化时让handler调用一下
deep:true
深度监测
没有下面两个属性时可以简写
computed和watch之间的区别
1.computed能完成的功能,watch都可以完成
2.watch能完成的功能,computed不一定能完成。例如:watch可以进行异步操作。
注意:
所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、promise的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象。
1.class样式:
//(1)字符串写法:适用于样式的类名不确定,需要动态指定。
<class="basic" :class="mood">
data数值 mood= "normal" 调用changeMood()
//(2)数组写法:适用于要绑定的样式个数不确定、名字也不确定。
<:class="classArr">
data数值 classArr["样式1","样式2", "样式3"]
//(3)对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用
<:class="classObj">
data数值 classObj:{样式1:false, 样式2: false}
//注:样式键名一定要是css文件里定义过的
2.style样式(了解):
// (1)对象写法:
<:style="styleObj">
data数值 styleObj:{样式1:"", 样式2: ""}
//(2)数组写法:
<:style="styleArr">
data数值 styleArr:[{样式1:"",样式2:""},{styleObj2}],
//注:样式键名一定是符合js语法的,数组里面为样式对象
1.v-if/v-else-if/v-else
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被“打断”
2.v-show
适用于:切换频率较高的场景
特点:不展示的DOM元素的样式被隐藏
注:使用template时只能与v-if连用
v-for指令
1.用于展示列表数据
2.语法:v-for="(item, index) in xxx" :key="yyy"
3.可遍历:数组(常用)、对象、字符串、指定次数
key的作用和原理
1.虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下:
2.对比规则:
(1)旧虚拟DOM中找到了与新虚拟DOM相同的key:
a.若虚拟DOM中内容没变,直接使用之前的真实DOM
b.若虚拟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的根数据对象添加属性
若:<input type="text"/>,则v=model收集的是value值,即用户输入的值
若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值
若:<input type="checkbox"/>,
(1)没有配置input的value属性,那么收集的就是checked(布尔值)
(2)配置input的value属性:
a.v-model的初始值是非数组,那么收集的就是checked
b.v-model的初始值是数组,那么手机的就是value组成的数组
***************************************************************
//备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:1.注册过滤器:Vue.filter(name,callback)
或
new Vue(filter:{})
2.使用过滤器:{{xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
******************************************
// 备注:
(1)过滤器也可以接收额外参数,多个过滤器也可以串联
(2)并没有改变原本的数据,是产生新的对应的数据。
通过Vue实现常见的网页效果
Vue指令:以v-开头的一组特殊语法
1 内容绑定,事件绑定:v-text, v-html, v-on基础 >>计时器
2 显示切换,属性绑定:v-show,v-if,v-bind >>图片切换
3 列表循环,表单元素绑定:v-for, v-on补充, v-model >>记事本
v-text指令的作用是:设置标签的文本值
默认写法会替换全部的内容,使用差值表达式 {{}}
可以替换指定内容
内部支持写表达式
v-html:设置标签的innerHTML
内容中有html结构会被解析为标签
v-text指令无论内容是什么只会解析为文本
与插值语法的区别:
(1)v-html会替换掉节点中所有的内容,{{xxx}}
则不会
(2)v-html可以识别html结构
注意:v-html有安全性问题
(1)在网站上动态渲染任意html是非常危险的,容易导致xss攻击
(2)一定要在可信度内容上使用v-html,永远不要用在用户提交的内容上
v-cloak指令:(没有值)
1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}
的问题。
v-once指令:(没有值)
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
v-on:为元素绑定事件
指令可简写为@
绑定的方法定义在methods属性中
方法内部通过this关键字可以访问定义在data中的数据
补充:
事件绑定的方法写成函数调用的形式,可以传入自定义参数
定义方法时需要定义形参来接受传入的实参
事件的后面跟上 .修饰符 可以对事件进行限制
.enter 可以限制触发的按键为回车
事件修饰符有多种
v-show:根据真假切换元素的显示状态
原理是修改元素的display 实现显示隐藏
指令后面的内容,最终都会解析为布尔值
值为true元素显示,值为false元素隐藏
数据改变之后,对应元素的显示状态会同步更新
v-if:根据表达值的真假,切换元素的显示和隐藏(不是样式,而是操纵dom元素)
本质上是通过操纵dom元素来切换显示状态
表达式的值为true,元素存在于dom树中,为false,从dom树中移除
频繁的切换 v-show,反之使用v-if,前者的切换消耗小
v-bind:为元素绑定属性(如src title class)
v-bind:属性名= 表达式
简写为 :
需要动态的增删class建议使用对象的方式
v-for:根据数据生成列表结构
数组经常和v-for结合使用
语法是(item, index) in 数据
item代表每一项,index索引
数据:data中对应的数据
item和index可以结合其他指令一起使用
数组长度的更新会同步到页面上,是响应式的。
v-model:获取和设置表单元素的值(双向数据绑定)
绑定的数据会和表单元素值相关联
绑定的数据 <==>表单元素的值
只能应用在表单类元素(输入类元素)上
1.定义语法
(1)局部指令:
new Vue({
directives:{指令名:配置对象}
})
或
new Vue({
directives{指令名:回调函数}
})
(2)全局指令:
Vue.directive(指令名,配置对象)
或
Vue.directive(指令名,回调函数)
***************************************
<!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
-->
directives:{
big(element,binding) {
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;
// element.focus();
}
}
}
2.配置对象中常用的3个回调:
(1)bind:指令与元素成功绑定时调用
(2)inserted:指令所在元素被插入页面时调用
(3)update:指令所在模板结构被重新解析时调用
3.备注:
(1)指令定义时不加v-,但使用时要加v-;
(2)指令名如果是多个单词,要使用kebab-case
命名方式,不要使用cameCase
命名。
又名:生命周期回调函数、生命周期函数、生命周期钩子
是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
生命周期函数中的this指向是 vm 或 组件实例对象
常用的生命周期钩子:
(1)mounted
:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
(2)beforeDestroy
:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例:
(1)销毁后借助Vue开发者工具看不到任何信息
(2)销毁后自定义事件会失效,但原生DOM事件依然有效
(3)一般不会在beforeDestroy
操作数据,因为即使操作数据,也不会再触发更新流程了。
1.如何定义一个组件?
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的那个options
几乎一样,但也有点区别:
(1)el不要写,为什么?
最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
(2)data必须写成函数,为什么?
避免组件被复用时,数据存在引用关系
备注:使用template
可以配置组件结构
2.如何注册组件?
(1)局部注册:靠new Vue
的时候传入components
选项
(2)全局注册:靠Vue.component("组件名",组件)
3.编写组件标签
// 第一步:创建school组件
const school = Vue.extend({
// 组件一定不要写el配置项,因为最终所有的组件都要被vm所管理,由vm决定服务于哪个容器
template:`
...
`,
data() {
return {
...
}
},
methods: {
...
}
},
})
// 第二步:全局注册组件
Vue.component("hello",hello)
new Vue({
el:"#root",
...
// 第二步:注册组件(局部注册)
components:{
school,
student
},
})
<!-- 第三步:编写组件标签 -->
<school></school>
*****************************************************
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
备注:
(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.我们只需要写
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
。
3.特别注意:每次调用Vue.extend
,返回的都是一个全新的 VueComponent!!
4.关于this指向:
(1)组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是 VueComponent 的实例对象。
(2)new Vue(options)
配置中:
data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是Vue实例对象。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
axios:网络请求库
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios特性:
安装axios:npm install axios -save
// main.js:
// 1.axios的基本使用
import axios from "axios"
// 请求方式一: axios(config)
// 传入config对象
axios({
// 默认发送get请求
url:"...",
params: {
type:"pop",
page:1
},
// method:"get"
}).then(res => {
console.log(res);
})
********************************
// 请求方式二:axios.get(url[,config])
axios.get(地址?查询字符串).then(funtion(response){}, function(err){})
查询字符串拼接:key=value&key2=value2
axios.post(地址,参数对象).then
(function(response){},function(err){})
参数对象:{key:value,key2:value2}
**********************************************
key:由接口文档提供
value:具体传输的数据
axios必须先导入才可以使用
使用get或post方法即可以发送对应的请求
then方法中的回调函数会在请求成功或失败时触发
通过回调函数的形参可以获取响应内容,或错误信息
注意事项:
有时候,我们可能需要同时发送两个请求:使用axios.all,可以放入多个请求的数组;axios.all([ ])返回的结果是一个数组,使用 axios.spread可将数组[res1,res2]展开为res1,res2
// 2.axios发送并发请求:多个请求到达后代码才可继续向下执行
axios.all([axios(), axios()])
.then(axios.spread((res1,res2) => {
...
}))
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
如果你想在稍后移除拦截器,可以这样:
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
在开发中很多参数都是固定的,这个时候我们可以利用axios的全局配置抽取公共的部分
axios.defaults.baseURL = "..."
axios.defaults.timeout = 5000
在实际开发中,为了应对后续请求配置的变化,应当避免使用全局的axios和对应的配置进行网络请求,对axios进行封装,创建对应的axios实例,便于后期维护。
// 对axios的封装:src/network/request.js:
import axios from "axios"
export function request(config) {
// 1.创建axios的实例
const instance = axios.create({
baseURL:"...",
timeout:5000
})
// 发送真正的网络请求 原理:instance本身就是promise
return instance(config)
}
***************************************************
// 实例的使用: main.js:
//封装request模块
import {request} from "./network/request";
request({
url:"..."
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
官方文档
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1)vue.js是完整版的Vue,包含:核心功能+模板解析器
(2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模板解析器
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement
函数去指定具体内容
1.被用来给元素或子组件注册引用信息(id的替代者)
2.应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3.使用方式:
打标识:
或 ......
获取:this.$refs.xxx
功能:让组件接收外部传过来的数据(父子关系)
(1)传递数据:
<Demo name="xxx"/>
(2)接收数据:
第一种方式(只接收):
props:["name"]
第二种方式(限制类型):
propos: {
name:String
}
第三种方式(限制类型、限制必要性、指定默认值):
props :{
name: {
type:String,
required:true,
default:"yyy"}
}
备注:props是只读的,Vue底层会检测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
//minxin.js:
export const mixin = {
data(){...},
methods: {
showName() {
...
}
},
}
***********************************
第二步使用混合,例如:
import {mixin} from "../mixin"
(1)全局混入:Vue.mixin(mixin)
(2)局部混入:mixins:[mixin]
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:
对象.install = function(Vue,options) {配置项...}
使用插件:Vue.use()
作用:让样式在局部生效,防止冲突。
写法:
总结TodoList案例
1.组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1)一个组件在用:放在组件自身即可。
2)一些组件在用:放在它们共同的父组件上(状态提升)。
(3)实现交互:从绑定事件开始
2.props适用于:
(1)父组件 =>子组件 通信
(2)子组件 =>父组件 通信(要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会不报错,但不推荐这样做。
1.存储内容一般支持5MB左右
2.浏览器通过Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制。
备注:
1.sessionStorage存储的内容会随着浏览器窗口关闭而消失
2.localStorage存储的内容需要手动清除才会消失。
3.如果key对应的value获取不到,返回值为null
4.JSON.parse(null)
的结果依然是null
1.一种组件间通信的方式,适用于:子组件 =>父组件
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
3.绑定自定义事件:
(1)第一种方式,在父组件中:<Demo @atguigu="test"/>
或
<Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:
<Demo ref="demo"/>
...
mounted(){
this.$refs.demo.$on("atguigu",this.test)
}
(3)若想让自定义事件只能触发一次,可以使用once修饰符
,或$once
方法
4.触发自定义事件:this.$emit("atguigu",数据)
5.解绑自定义事件:this.$off("atguigu")
6.组件上也可以绑定原生DOM事件,需要使用native修饰符
7.注意:通过this.$refs.xxx.$on("atguigu",回调)
绑定自定义事件时,回调要么配置在methos中,要么用箭头函数,否则this指向会出问题!
1.一种组件间通信的方式,适用于任意组件间通信
2.安装全局事件总线
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
3.使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods() {
demo(data){....}
}
....
mounted() {
this.$bus.$on("xxxx",this.demo)
}
(2)提供数据:this.$bus.$emit("xxxx",数据)
4.最好在beforeDestroy钩子
中,用$off
去解绑当前组件所用到的事件。
1.一种组件间通信方式,适用于任意组件间通信。
2.使用步骤:
(1)安装 pubsub:npm i pubsub-js
(2)引入:import pubsub from "pubsub-js"
(3)接收数据:
// A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods() {
demo(data) {...}
}
....
mounted() {
this.pid = pubsub.subscribe("xxx",this.demo) //订阅消息
}
(4)提供数据:pubsub.publish("xxx",数据)
(5)最好在beforeDestroy钩子
中,用Pubsub.unsubscribe(pid)
去取消订阅。
1.语法:this.$nextTick(回调函数)
2.作用:在下一次DOM更新结束后执行其指定的回调
3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
写法:
// 1.准备好样式:
(1)元素进入的样式:
v-enter:进入的起点
v-enter-active:进入的过程中
v-enter-to:进入的终点
(2)元素离开的样式:
v-leave:离开的起点
v-leave-active:离开的过程中
v-leave-to:离开的终点
// 2.使用包裹要过渡的元素,并配置name属性:
<transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>
// 3.备注:若有多个元素需要过渡,则需要使用:,
且每个元素都要指定key值。
【方法一】
// 在vue.config.js中添加如下配置:
devServer: {
proxy: 'http://localhost:5000'
}
说明:
1.优点:配置简单,请求资源时直接发给前端(8080)即可。
2.缺点:不能配置多个代理,不能灵活地控制请求是否走代理。
3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
【方法二】
// 编写vue.config.js配置具体代理规则:
devServer: {
proxy: {
'/api1': { // 匹配所有以"/api1"开头的请求路径
target: 'http://localhost:5000', //代理目标的基础路径
pathRewrite:{"^/api1":""},
ws: true, //默认为true
changeOrigin: true // 默认为true
},
'/api2': { //匹配所有以“/api2”开头的请求路径
target: 'http://localhost:5001',
pathRewrite:{"^/api2":""},
},
/*
changeOrigin设置为 true 时,服务器收到的请求中的host为:localhost:5000
changeOrigin设置为 false 时,服务器收到的请求中的host为:localhost:8080
*/
说明:
1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理
2.缺点:配置略微繁琐,请求资源时必须加前缀
1.xhr (最原始)
2.jQuery的封装
3.axios(常用)
4.fetch(与xhr平级)
5.vue-resourse(vue的一个库,对xhr的封装,不再维护)
安装:npm i vue-resourse (安装失败了)
在main.js中导入:import vueResourse form "vue-resourse"
使用:Vue.use(vueResourse)
安装成功后,vm及所有vc身上都会带有$http属性
发送请求方法:同axios,将所有axios
替换成this.$http
即可
1.作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件=>子组件。
2.分类:默认插槽、具名插槽、作用域插槽
3.使用方式:
(1)默认插槽:
// 父组件中:
<Category>
<div>html结构1</div>
</Category>
// 子组件中:
<template>
<div>
<slot>插槽默认内容</slot>
</div>
</template>
(2)具名插槽:
// 父组件中:
<Category>
<!-- 使用具名插槽必须使用template结构 -->
<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>
(3)作用域插槽:
// 1.理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
// (games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
2.具体编码:
// 父组件中:
<Category title="游戏">
<!-- 使用作用域插槽必须加template结构 -->
<template scope="message">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="(g,index) in message.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<!-- 解构赋值写法 -->
<template scope="{games}">
<!-- 生成的是h4标题 -->
<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
</template>
</Category>
// 子组件中:
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽 -->
<slot :games="games">如果没有设置结构则显示这句话</slot>
</div>
</template>
<script>
export default {
name:"Category",
props:["title"],
//数据在子组件自身
data() {
return {
games:["红色警戒","穿越火线","劲舞团","超级玛丽"],
}
},
}
</script>
(1)理解:一个路由(route
)就是一组映射关系(key-value
),多个路由需要路由器(router
)进行管理
(2)前端路由:key是路径,value是组件。
(1)安装 vue-router
,命令:npm i vue-router
(2)应用插件:Vue.use(VueRouter)
(3)编写路由器配置项:src/router/index.js
// 该文件专门用于创建整个应用的路由器
// 引入插件
import vueRouter from 'vue-router'
// 引入组件
import About from '../pages/About'
import Home from '../pages/Home' // 出现了 /# 表示路由已就位
// 创建并暴露一个路由器
export default new vueRouter({ //是一个构造函数
// 配置路由规则
routes:[
{ path:"/about", component:About},
{ path:"/home",component:Home}
]
})
(4)实现切换 :App.vue
<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>
<!-- 注:1.to表示想把路径改成什么,不是相对路径
2. router-link 最终也会变成a标签渲染到页面
3.使用 active-class 指定动态高亮
-->
(5)展示指定位置
<router-view></router-view>
(6)注意事项
(1 路由组件通常存放在pages/views文件夹
,一般组件通常存放在components文件夹
。
(2 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
(3 每个组件都有自己的$route属性
,里面存储着自己的路由信息。
(4 整个应用只有一个router
,可以通过组件的$router属性
获取到;
一般组件:引入、注册,亲自写的组件标签;
路由组件:靠路由规则匹配出来的,由路由器给渲染的组件。
工作过程描述:用户点击了路由链接修改了路径,前端路由器检测到了,匹配规则时发现该路径对应哪个组件,于是将该组件渲染出来放在指定的位置。
(1)配置路由规则,使用children配置项:
routes:[
{ path:"/about", component:About},
{
path:"/home",
component:Home,
// 配置子路由
children:[
{path:"news", component:News}, //此处一定不要写/news
{path:"message", component:Message}
]
}
]
(2)跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
1)传递参数
<!-- 写法一:跳转路由器并携带query参数to的字符串写法
在to前面加: 里面的内容会被当作js解析,加上模板字符串,
里面混入js变量的代码写进${}里面
-->
<router-link
:to="`/home/message/detail?id=${m.id}&title=${m.title}`"
>
{{m.title}}
</router-link>
<!-- 写法二(推荐):跳转路由器并携带query参数to的对象写法-->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
(2)接收参数
$route.query.id
$route.query.title
(1)作用:可以简化路由的跳转
(2)如何使用
给路由命名:
routes:[
{ path:"/about", component:About},
{
path:"/home",
component:Home,
// 配置子路由
children:[
{path:"news", component:News},
{
path:"message",
component:Message,
children:[
// 给路径过长的路由取名 可以简化跳转编码
{name:"detail", path:"detail", component:Detail}
]
}
]
}
]
********************************************************************
简化跳转:
<!--简化前需要写完整的路径-->
<router-link to="/home/message/detail">跳转</router-link>
<!--简化后,直接通过名字跳转-->
<router-link :to="{name:'detail'}">跳转</router-link>
<!--简化写法配合传递参数-->
<router-link
:to="{
name:'detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
(1)配置路由,声明接收params参数
children:[
// 给路径过长的路由取名 可以简化跳转编码
// 给path添加占位符,配合params传参
{name:"detail", path:"detail/:id/:title", component:Detail}
]
(2)传递参数
<!-- 写法一:跳转路由并携带params参数to的字符串写法:直接拼接 -->
<router-link
:to="`/home/message/detail/${m.id}/${m.title}`"
>
{{m.title}}
</router-link>
****************************************************************
<!-- 写法二:跳转路由并携带params参数to的对象写法-->
<router-link
:to="{
name:'detail',
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
【特别注意】路由携带params参数时,若使用to的对象写法,则不能使用path配置
项,必须使用name配置
!
(3)接收参数
$route.params.id
$route.params.title
作用:让路由组件更方便的接收参数
{
name:"detail",
path:"detail/:id/:title",
component:Detail,
*********************************************************
//props的第一种写法,值为对象,
//该对象中所有的key-value都会以props的形式传给Detail组件
//--数据是写死的,用得非常少
props:{a:1,b:2}
*********************************************************
//props的第二种写法,值为布尔值,
//若为真就会把该路由组件收到的所有params参数,
//以props的形式传给Detail组件
props:true
*********************************************************
props($route) {
return {id:$route.query.id,title:$route.query.title}
}
// props的第三种写法,值为函数,
//该对象中所有的 key-value 都会以props的形式传给Detail组件
props({query:{id,title}}) {
return {id,title}
} //解构赋值的连续写法 -- 可读性差
}
的replace属性
作用:控制路由跳转时操作浏览器历史记录的模式
浏览器的历史记录有两种写入方式,分别为push和replace,push是追加历史记录,replace是替换当前栈顶的记录。路由跳转时默认为push
如何开启replace模式:
<router-link replace ...>News</router-link>
作用:不借助实现路由跳转,让路由跳转更加灵活
具体编码:
//$router的两个API
this.$router.push({
name:'detail',
query:{
id:m.id,
title:m.title
}
})
this.$router.replace({
name:'detail',
query:{
id:m.id,
title:m.title
}
})
this.$router.back()
this.$router.forward()
// 向后退两部
this.$router.go(-2)
作用:让不展示的路由组件保持挂载,不被销毁
具体编码
// 找到其父组件
<keep-alive include="News"> // 这里写的是组件名
<router-view></router-view>
</keep-alive>
// 缓存多个组件写法
:include="['News','Message']"
Vue在特殊时候帮我们调用的特殊的函数
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体名字:
(1)activated
路由组件被激活时触发
(2)deactivated
路由组件失活时触发
作用:对路由进行权限控制
分类:全局守卫(前置、后置)、独享守卫、组件内守卫
// ***全局前置守卫,初始化的时候被调用,每一次路由切换之前被调用
// 接收三个参数
router.beforeEach((to,from,next)=>{
console.log(to,from);
************************************************************
// 一、name写法
// 事先给所有路由组件配置名字
if(to.name === "News" || to.name === "Message");
************************************************************
// 二、path写法
if(to.path === "/home/news" || to.path === "/home/message")
****************************************************************
// 三、当组件过多怎么判断?
// 给路由组件配置meta属性 meta:{isAuth:true}
if(to.meta.isAuth===true) //判断当前路由是否需要权限控制
{ // 事先在浏览器应用loaclStorage配置好school:atguigu
if(localStorage.getItem("school") === "atguigu") {
//权限控制的具体规则
// school的值对上了才允许访问这两个界面
next()
} else {alert("学校名错误,无权限访问!")}
} else {
next() //放行
}
})
// ***全局后置守卫,初始化的时候被调用,每一次路由切换之后被调用
// 适用场景:当组件被过滤之后需要进行的操作
// 提出需求:页签名称改变
router.afterEach((to,from)=> { //接收两个参数
console.log(to,from);
//将title配置在meta里
//设置默认 并修改index.html的title配置
document.title = to.meta.title || "硅谷系统"
})
// 在暴露之前使用路由守卫过滤
export default router
某一个路由所独享的
beforeEnter(to,from,next){
...
}
// 通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next){
}
// 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next){
}
对于一个url
来说,什么是hash值
?——#及其后面的内容
就是hash值
hash值
不会包含在HTTP请求中
,即:hash值
不会带给服务器
hash模式
:
(1)地址中永远带着#号,不美观
(2)若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
(3)兼容性较好
history模式
:
(1)地址干净,美观
(2)兼容性和hash模式相比略差
(3)应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
配置路由器工作模式
router{
mode:"hidtory"
routes: ...
}
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
(1)多个组件依赖于同一状态
(2)来自不同组件的行为需要变更同一状态
(1)npm i vuex 安装Vuex
(2)Vue use(vueX)
(3)创建store管理actions
、mutations
、state
(4)vc =>store
(1)创建文件:src/store/index.js
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用vuex插件
// 为什么不在main.js中引用?因为import语句先执行,要保证import时已引入Vuex
Vue.use(Vuex)
// 准备actions对象--响应组件中对象的动作
const actions = {}
// 准备mutations对象--修改state中的数据
const mutations = {}
// 准备state对象--保存具体的数据
const state = {}
// 创建并暴露store
export default new Vuex.Store({
// 传入配置项
actions,
mutations,
state
})
***********************************************
(2)在main.js中创建vm时传入store配置项
...
import store from './store/index'
....
new Vue({
el:"#app",
render:h => h(App),
store
})
(1)初始化数据、配置actions
、配置mutations
、操作文件index.js
(2)组件中读取vuex中的数据:$store.state.数据
(3)组件中修改vuex中的数据:$store.dispatch("actions中的方法名",数据)
或$store.commit("mutations中的方法名",数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions
,即不写dispatch
,直接编写commit
。
(1)概念:当state中的数据需要经过加工后再使用时,如果逻辑复杂并且还想复用,可以使用getters加工。getters与state的关系相当于computed与data的关系。
(2)
...
// 在index.js中追加getters配置
const getters = {
// getters能收到一个参数state
bigSum(state) {
// 类似于计算属性,都是靠返回值来决定自己的值
return state.sum * 10
}
}
...
(3)组件中读取数据:$store.getters.bigSum
mapState
方法:用于帮助我们映射state中的数据为计算属性computed: {
....
// 1.借助mapState生成计算属性,从state中读取数据(对象写法)
...mapState({sum:"sum",school:"school",subject:"subject"}), //展开运算符:在对象里面拼接对象
// 此处不适用对象的简写,会读取sum变量,而此处是字符串
// 2.借助mapState生成计算属性,从state中读取数据(数组写法)
...mapState(["sum","school","subject"]), //要保证生成的计算属性名和读取出来的名是一致的
...
},
mapGetters
方法:用于帮助我们映射getters中的数据为计算属性computed: {
....
// 1.借助mapGetters生成计算属性,从getters中读取数据(对象写法)
...mapGetters({bigSum:"bigSum"}),
// 2.借助mapGetters生成计算属性,从getters中读取数据(数组写法)
...mapGetters(["bigSum"])
},
mapMutations
方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)
的函数methods: {
...
// 1.借助mapMutations用commit去联系mutations生成对应的方法(对象写法)
...mapMutations({increment:"ADD",decrement:"SUB"}),
// 2.借助mapMutations用commit去联系mutations生成对应的方法(数组写法)
// 方法名要和mutations中对应
// ...mapMutations(["ADD","SUB"]),
mapActions
方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)
的函数 methods: {
...
// 1.借助mapActions用dispatch去联系actions生成对应的方法(对象写法)
...mapActions({incrementOdd:"addOdd",incrementWait:"addWait"}),
// 2.借助mapActions用dispatch去联系actions生成对应的方法(数组写法)
// ...mapActions(["addOdd","addWait"])
},
备注
mapActions
与mapMutations
在使用时,若有传递参数需要,须在模板绑定事件时传递好参数,否则参数为默认事件对象。
(1)目的:让代码更好维护,让多种数据分类更加明确(业务逻辑)。
(2)修改index.js
:
// 模块一
const countAbout = {
namespaced:true, //开启命名空间
state:{x:1},
actions:{ ... },
mutationos:{ ... },
getters:{ ... }
}
// 模块二
const personAbout = {
namespaced:true,
...
}
...
// 暴露并创建store
export default new Vuex.Store({
// 传入配置项
modules: {
countAbout:countOptions,
personAbout:personOptions
}
})
(3)开启命名空间后,组件中读取state数据:
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助 mapState读取
...mapState("countAbout",["sum","school","subject"]),
(4)开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取
this.$store.getters[personAbout/firstPersonName]
//方式二:借助 mapGetters读取
...mapGetters("countAbout",["bigSum"]),
(5)开启命名空间后,组件中调用dispatch
//方式一:自己直接 dispatch
this.$store.dispatch("personAbout/addPersonWang",personObj)
//方式二:借助 mapActions
...mapActions("countAbout",{incrementOdd:"addOdd",incrementWait:"addWait"}),
(6)开启命名空间后,组件中调用commit
//方式一:自己直接 commit
this.$store.commit("personAbout/ADD_PERSON",personObj)
//方式二:借助 mapMutations
...mapMutations("countAbout",{increment:"ADD",decrement:"SUB"}),