Vue笔记
视频: https://www.bilibili.com/video/BV1Zy4y1K7SH?p=1
vue是渐进式JavaScript框架 用到什么功能,只需要引入什么功能模块 ;
vue的特点:易用,灵活,高效;
vue引入:
引入开发版本的vue, 下载vue Devtools (vue开发者工具) ,
// 阻止 vue 在启动时生成生产提示。
Vue.config.productionTip = false
创建vue实例:
创建vue实例, 创建一个配置对象; 通过new 关键字调用;
配置项
el => element : 即 vue实例管理的元素, 值通常为 id 选择器 的字符串;
data 对象或函数且返回一个对象, 用于存储数据, 供el管理的元素使用;
vue实例和被管理的容器是一一对应的;且真实开发中只有一个vue实例;
data中的数据改变,页面中用到该数据的地方自动更新; (响应式)
const vm = new Vue({
el:"#app",
data:{
msg:"世界"
}
})
{{ }} 插值语法/mustache语法; {{ }}插值语法的内容可以自动读取data中的数据,只能放js表达式;
指令语法,v-开头, 也可以直接使用data中的数据;
单向数据绑定(属性绑定) v-bind: 简写为 :
单向数据绑定: <input type="text" :value="msg">
双向数据绑定 v-model , 通常绑定表单元素, v-model: value, 直接简写为 v-model
双向数据绑定:<input type="text" v-model="msg">
el与data的两种写法:
el: '#app',
// 通过实例vm挂载
vm.$mount("#app")
data:
对象形式
data: {
msg:"你好 vue"
}
函数形式, 返回一个对象
data() {
return {
msg:"你好 VUE"
}
}
vue实例中使用数据, 记得加this;
模板中使用数据,不用加this;
模板中调用方法,加()/也可以不加,但是不传参,默认是event事件;
模板中调用计算属性,不用加(),计算属性是一个属性,get方法简写了;
MVVM框架:
Object.defineProperty的getter()与setter()方法
v-on: 事件名 = “事件处理函数”
事件包括 ( 点击事件, 键盘事件, 滚动事件等等)
v-on:事件名 ==> 简写 @事件名
插值语法中调用事件处理函数加 ( ); 也可以不加()
<h4>姓名:{{fullName()}}</h4>
事件处理函数要写法到 methods (方法) 配置项中去;
事件不传参可以省略();
==不传参,事件处理函数的形参第一个默认是event事件; 传参可以传vm上的数据;
事件传参, 想要保留event 事件,用$event占位;
<button v-on:click="showInfo1">按钮1</button> //不传参
<button @click="showInfo2(100,$event)">按钮2</button> //传参
methods: {
showInfo1(e) { // 调用不加(), 回调函数的形参默认event事件;
console.log(e.target); // e.target 拿到触发的对象(元素)
console.log(e.target.innerText); // e.target.innerText 拿到触发对象的内容
console.log(this); // this是Vue实例对象
},
showInfo2(num, e) {
console.log(num, e);
},
},
事件修饰符 :
键盘修饰符:
键盘事件:
键盘修饰符: <input type="text" @keydown.enter=key>
配置项: computed: { }
计算属性本质是一个函数,可以实时监听data中数据的变化,并return一个计算后的值,供组件渲染dom时使用;
调用的时候不需要加()。
计算属性依靠的是他的返回值 return, 必需要有;
计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算,所以计算属性性能更好。
计算属性: <span>{{fullName}}</span> -->
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
计算属性的简写: 当只读取(get),不设置(set) 的时候,可以简写;
不简写:
computed: {
fullName: {
get() {
return this.xing + "-" + this.ming;
},
},
}
调用的时候: <h4>姓名:{{fullName}}</h4>
简写:
computed: {
fullName() { //对象中函数的简写
return this.xing + "-" + this.ming;
},
},
调用的时候: <h4>姓名:{{fullName}}</h4>
监视data中数据的变化;
组件在初次加载完毕后不会调用 watch 侦听器,因为immediate默认false。
watch监视的是个整个对象的变化,只想监听对象中某个属性的变化, 加引号; " xx.xxx " : { } ;
配置项: watch: { 监听对象: { } }
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法:
(1) new Vue时传入watch配置
(2) 通过vm.$watch("监视对象",{配置项} ) 监视 ; ( 监视的属性,记得加引号)
watch监视的属性的配置项:
/ immediate: false(默认),为true时,实例初始化时会调用一下handler函数; /
handler(newValue,oldValue){} 函数; 监视的属性发生修改时,触发handler()函数;
deep:true; 默认不开启(效率问题), 开启深度监视: 监视属性的多层级的数据的改变, 监听对象内的数据改变;a.b.c
deep深度监视:
(1) Vue中的watch默认不监测对象内部值的改变(一层)。
(2) 配置deep:true可以监测对象内部值改变(多层)。
(3) 监视多级结构中属性所有数据的变化
监视属性的简写:
// 不简写
watch: {
hot:{
handler(newValue,oldValue){
console.log('hot改变了', newValue,oldValue);
},
immediate:true,
deep:true,
}
},
当监视属性只有handler()函数时,可以简写
简写, / 当监视属性只存在handler()函数时/,可以简写
watch: {
hot(newValue,oldValue){
console.log('hot改变了',newValue,oldValue);
}
},
// 简写 第二种方法, 在vue实例上监视
vm.$watch("hot", function (newValue, oldValue) {
console.log("hot改变了", newValue, oldValue);
});
computed、watch、方法的区别:
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象vc。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
这样this的指向才是vm 或 组件实例对象vc。
样式绑定:
固定的样式写死, 不固定的写成动态的;
绑定class样式 :
通过v-bind: 属性绑定, v-bind: 指令可以使用data中的数据 ;
<div class="box1" :class="color"></div>
<div class="box1" :class="colorArr"></div>
绑定对象 , 当对象的 value值为真时, 才绑定上
<div class="box1" :class="colorObj"></div>
行内样式 style绑定; 注意: 短横线命名法与驼峰命名法的切换;
v-bin: style 通常写成对象的形式 ;
对象写法
<div class="box1" :style="{fontSize: font_size + 'px'}">字体大小</div>
<div class="box1" :style="styleObj">字体大小</div>
数组写法
<div class="box1" :style="styleArr">字体大小</div>
data: {
font_size: 40,
styleObj:{
fontSize:40 + "px"
},
// 数组里面写样式对象
styleArr:[
{fontSize:40 + "px"}
]
},
v-if与v-show指令后面要跟表达式, 只有当表达式结果为真才显示;
v-if 是真正的节点的创建与销毁;
v-if 与 v-else-if , v-else配合使用, v-else后面不用在跟表达式,中间不能断开; v-if可以配合 template标签使用,
不会渲染template标签; tempalte标签无法添加样式;
v-show只是行内样式: display属性的显示与隐藏;
v-for 遍历, 要加 :key, key要属性绑定, 且要有唯一性;
v-for/ v-of v-for 遍历数组, (item,index) 每一项, 下标 ;
v-for 遍历对象 (value , key, index) 属性值, 属性名 , 下标;
key的作用:
虚拟dom对比, diff算法;
文本节点对比, 标签节点对比, 具有差异的节点,新的节点更新旧的节点;
:key 要有唯一性 ;
用index作为key, 打乱顺序容易错乱
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟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.set()方法
Vue.set(目标, key , val)
;V要大写
Vue.set(目标, "key'", "val")
等价与vm.$set(目标,"key","val")
添加具有响应式的数据到vm实例上, 但不能直接给vm实例 或 根数据对象(data) 添加 ;
响应式的数据, 具有 set()和get()方法;
数据代理
把vm._data.person 代理成 vm.person ;
Vue.set()方法
vm配置对象中, this 就是 vm
Vue.set(this.person,"address","曹县")
等价与
Vue.set(this._data.person,"address","曹县")
vm.$set()方法
this.$set(this.person,"school","港湾学院")
等价与
this.$set(this._data.person,"school","港湾学院")
在vue实例的代码中, vm实例的配置对象中, this 指向vm的,使用data中的数据,或者methods中的方法加this ;
在插值语法中, 可以直接使用 vm实例中的data中的数据,不用加this;
数组更新检测的方法
变更方法:
在vue中不能直接通过数组的下标直接赋值的方法修改数组(arr[0]=123), 因为视图不会更新;
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
以下方法会改变原数组;
vue监测数据
Vue监视数据的原理:
- vue会监视data中所有层次的数据。
- 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
this.$set(target,propertyName/index,value)- 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或vm.$set()
特别注意:Vue.set() 和vm.$set()
不能给vm 或 vm的根数据对象 添加属性!!!
v-model绑定的就是value值; v-model是v-bind和v-on的语法糖;
v-bind绑定value属性,v-on监听input事件;
v-model:value 简写==> v-model
<input type="text" :value="msg" @input="msg = $event.target.value" />
收集表单数据:
若:,则v-model收集的是value值,用户输入的就是value值。
若:,则v-model收集的是value值,且要给标签配置value值。使用v-model后,可以不用再写name属性;
若:
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值true/false)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值true/false)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
若: input的类型是 select 或 textarea , v-model 收集的也是value值;
v-model的三个修饰符:
.lazy:当前表单元素失去焦点时再收集数据;
.number:输入字符串转为有效的数字, 通常配合 number 类型的input框使用 ; (输入框通常输入的是字符串类型);
.trim:输入首尾空格过滤;
.sync修饰符:
实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值。
本质: 函数 , 依靠返回值 , 对 | 前的数据进行处理;
过滤器本质是一个函数, 可以加() 或者 不加 ; 加()后, ()内的传的实参;
过滤器的处理函数的默认实参默认就是要处理的数据, 即 | 前的数据;
过滤器可以传参, 传的参数就是额外参数, 形参默认值(rest参数);
过滤器可以串联使用, 上一个过滤器的返回值作为下一个过滤器的参数;
过滤器依靠返回值对数据进行处理,通过管道符 | 对数据进行处理 ;
要处理的数据会作为一个实参,过滤器函数处理完, 把返回值 替换掉整个 {{ }} 中的数据;
{{ 数据名 | 过滤器名 }}
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{ filters:{} }
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”, 插值语法中使用,或者v-bind属性绑定定时用;备注: 1.过滤器也可以接收额外参数、多个过滤器也可以串联 2.并没有改变原本的数据, 是产生新的对应的数据;
全局的过滤器 Vue.filter("过滤器名", 处理函数) , 全局过滤器要写在new Vue() 之前;
<h4>全局过滤器: {{msg | quanju_Filter}}</h4>
Vue.filter("quanju_Filter", function(val){
return val.slice(0,7)
})
局部过滤器: new Vue配置项中的 filters:{ }
<h4>过滤器的时间是: {{time | filterTime}}</h4>
<h4>过滤器传参: {{time | filterTime("YYYY&&MM&&DD")}}</h4>
<h4>过滤器串联: {{time | filterTime("YYYY&&MM&&DD") | mySlice()}}</h4>
filters:{
// 过滤器传参, 形参默认值: 如果有传了参数就用&&, 如果没有参数就用##
filterTime(val,str="YYYY##MM##DD"){
return dayjs(val). format(str)
},
// 过滤器串联使用
mySlice(val){
return val.slice(0,4)
}
}
v-指令名, 可以直接使用data中的数据;
v-on: 简写 @ 事件绑定
v-bind: 简写 : 属性绑定, 可以使用vue实例data中的数据, 单向的数据绑定;
v-model:value 简写 v-model input元素双向数据绑定
v-if, v-else-if , v-else (这个不用跟数据) ; 条件渲染 条件为真才会创建这个节点;
v-show, 条件渲染 条件为真,会把这个节点的css属性的dispaly:none和block切换;
v-for, 列表渲染; :key = "something" ; 当v-for和 v-if 一起使用时,v-for 的优先级比 v-if 更高
v-text指令; 不解析标签, 会替代节点的原有的内容;
<h2 v-text="msg"></h2>
{{ }} 是v-text的另一种写法
v-html指令; 解析标签, 会替代节点原有的内容;
<h2 v-html="msg"></h2>
v-cloak指令,不用跟值; 用于解决网速慢时页面展示出{{xxx}}的问题,先隐藏起来,
通常配合属性选择器使用, [v-cloak]{display:none;}; 当vue解析到v-cloak指令时,移除这个指令;
<h4 v-cloak>{{msg}}</h4>
v-once指令,不用跟值;只渲染一次,
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
<h4 v-once>计数器:{{n}}</h4>
v-pre指令, 不用跟值; 不解析节点的{{}}内容;
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<h4 v-pre>{{msg}}</h4>
v-自定义指令名 = "数据"
对底层DOM元素的操作; 自定义指令的this指向window, 方便操作 DOM 元素 (不是自定义指令中的回调函数的this指向)。
自定义指令中的函数何时调用: 1. 指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
局部自定义指令
directives:{指令名 : 配置对象 }
directives:{指令名 : 回调函数 }
对象的key可以加引号也可以不加,
对象中函数的增强写法, 省略 : function
全局自定义指令 Vue.directive(“指令名”,配置对象)
Vue.directive(“指令名”,回调函数)
配置对象中的钩子函数;
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置;
inserted:被绑定元素插入父节点时调用 ;
update:指令所在模板结构被重新解析时;
unbind:只调用一次,指令与元素解绑时调用;
*/
bind:绑定时调用。
inserted:插入到父节点时调用。
update:组件更新时调用。
componentUpdated:组件更新完成时调用。
unbind:解绑时调用。
钩子函数的参数;
el:指令所绑定的元素,可以用来直接操作 DOM;
binding:一个对象; 主要使用binding.value:自定义指令所依赖的值; 例如: v-big='num'
binding 对象具有以下常见属性:
value: 指令绑定的值。
oldValue: 指令前一个绑定的值,只在update钩子函数中可用。
expression: 绑定值的原始表达式字符串。
arg: 指令的参数。
modifiers: 一个包含修饰符键值对的对象。
name: 指令的名称。
vnode: 虚拟节点对应的 VNode 实例。
instance: 当前组件实例。
el: 指令所绑定的元素,可以通过binding.el访问。
/通过访问 binding.value 属性,可以获取指令的绑定值,例如: v-big='num'/
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('big', function (el, binding) {
el.innerText = binding.value*10
})
自定义指令总结:
一、定义语法:
(1).局部指令:
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
(1) bind:指令与元素成功绑定时调用。
(2) inserted:指令所在元素被插入页面时调用。
(3) update:指令所在模板结构被重新解析时
三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case(短横线)命名方式,不要用camelCase(驼峰)命名。 例如: v-custom-directive
3.指令的回调函数名是短横线命名的, 可以用引号引起来,解决报错;
directives:{
'big-number'(element,binding){ element.innerText = binding.value * 10; }
}
<input type="text" v-fbig="num">
Vue.directive("fbig",{
bind(el,binding){
el.value = binding.value*10
},
inserted(el,binding){
el.focus()
},
update(el,binding){
el.focus()
el.value = binding.value*10
}
})
生命周期:一些特殊的回调函数;
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改。
4.生命周期函数中的this指向是vm 或 组件实例对象。
vm.$destroy() 方法 ;完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及自定义事件监听器; 触发beforeDestroy 和 destroyed 的钩子。
初始化页面(第一次进入页面)会触发前四个钩子;
常用的生命周期钩子:
1.created : 请求数据; (调用methdos中的函数, methods中定义函数,请求数据);
2.mounted: 操作DOM;发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
3.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
(解绑的是自定义事件, 原生DOM事件不影响, 该钩子函数可以改变数据,但不会在视图更新了)
函数作用域之间有作用域, 无法访问到其他函数内的变量;
解决方法: 追加vm的属性; this.XXX = “变量” ; 这里的this是vm实例对象;
组件 : 为了复用;
非单文件组件 : 包含多个组件。
单文件组件: 只包含一个组件(.vue文件的组件)。
全局组件 Vue.component(“标签名”, 组件构造器)
局部组件 components: { } 配置项 Vue.extend()
组件构造器,配置项内容和vm一样, 但是不要写el配置项,data要写成一个函数,返回一个对象;
template模板内要有一个根元素包裹, 换行可以使用模板字符串 ;
Vue中使用组件的三大步骤:
一、创建组件(定义组件)
二、注册组件 (components配置项中注册)
三、使用组件(写组件标签)
一、如何创建一个组件? 使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别; 区别如下:
1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,返回一个对象; 为什么? ———— 避免组件被复用时,数据存在引用关系, 组件各自维护独立的数据拷贝。 备注:使用template可以配置组件结构, 要有根元素包裹, 换行可以使用模板字符串;
二、如何注册组件?
1.局部注册:靠new Vue的时候传入components配置项, components:{"组件标签名": 组件构造器名}; //对象的key与value ;
2.全局注册:靠Vue.component('组件标签名',组件构造器名)
三、编写组件标签: <组件标签名></组件标签名> //单词首字母大写
组件名注意点:
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):全小写; my-school , 对象的key值加引号;
第二种写法(CamelCase命名):单词首字母大写; MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2都不行。
(2).组件构造器配置项中, 可以使用name配置项,指定组件在开发者工具中呈现的名字。
2.关于组件标签使用:
第一种写法:<school></school>
第二种写法:闭合标签 <school/>
(需要Vue脚手架支持, 不用使用脚手架时,<school/>会导致后续组件不能渲染。)
3.组件构造器的简写方式:
const school = Vue.extend(options) 可简写为:const school = options ; 省略了 Vue.extend() ,直接{ }
配置项中, name 属性 , 可以指定组件在开发者工具中呈现的名字;
vm是根组件; vm管理app组件, app组件管理所有的组件;
main.js文件是入口文件,挂载App组件;
创建App标签,render函数的 createElement(“标签名”,“内容”)函数
组件的嵌套: 父子组件关系;
子组件定义之后, 子组件在父组件components:{ }配置项中注册;
在父组件的template模板中使用子组件标签;
子组件的定义要在父组件之前; 否则父子间读取不到;
定义 => 引入 => 注册 => 使用
VueComponent 组件实例对象
组件的本质: 一个构造函数 ==》VueComponent的构造函数,调用Vue.extend()方法生成的。
每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
(1).组件配置中:vc
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:vm
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
6.Vue的实例对象,以后简称vm。
组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods以及生命周期钩子等。
el除外,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝; 防止数据的污染。
**单文件组件: 以.vue结尾的文件; **
包含html,js,css;一个文件就是一个组件;
export default {组件的定义} 暴露出去;Vue.extend()省略; 但仍调用Vue.extend()方法,生成一个全新的组件实例对象;
组件的name属性, vue开发者工具查看到的组件名,没有配置name,则是组件发文件名;
单文件组件文件的命名, 推荐大驼峰命名;同组件标签命名一致;
template标签内要有一个根组件div包含;
mian.js 入口文件,import App from ‘./App.vue’,创建vue实例;
index.html 首页;
App.vue文件, 汇总组件, 管理组件,引入汇总组件,在配置项componets中注册,并在模板中使用, 由vm管理 App.vue文件;一人之上,万人之下;
impiort 组件名 from “XXX路径” ;
.vue .js 文件名后缀名可以省略;
组件分类: 页面组件和复用组件。
省略
.vue文件/.js文件 引入 可以省略文件名的后缀 ;
引入index.js文件可以省略, 直接引用到文件夹路径 ;
文档: https://cli.vuejs.org/zh/guide/
第一次全局安装@vue/cli。 npm install -g @vue/cli
切换到你要创建项目的目录,然后使用命令创建项目 。 vue create 项目文件名
进入所在的项目文件夹, 然后启动项目 npm run serve/dev
切换淘宝镜像
npm config set registry https://registry.npm.taobao.org
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,
vue inspect > output.js
脚手架文件结构
├── node_modules : 所依赖的包
├── public :打包后发布的
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src : 写代码的地方
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
vue.config.js配置文件 (项目的根目录下)
使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh/config
配置项 : lintOnSave: false; // 关闭语法检查
render函数 渲染函数
关于不同版本的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函数去指定具体内容。
render: h => h(App) 函数 ; render 函数跟template配置项 一样都是创建 html 模板的;
必须要有返回值, vue自动调用; 参数是createElement函数, 这个函数第一个参数是创建的标签名, 第二个参数是标签的内容;
render:function(createElement){return createElement("标签名","内容")} ;
h代替了createElement函数;
render:function(h) {
return h(App) //App组件, App是一个变量,已将引入了, App组件内有标签内容;
},
箭头函数的简写 :
render: h => h(App)
小计简写:
箭头函数; ()=>{}
对象的简写; 属性的简写,方法的简写;对象中方法通常是匿名函数;
作用: 打标记 ref = "xxx"
this.$refs.xxx 应用在html标签上获取的是真实DOM元素; 应用在组件标签上是该组件实例对象(vc)
1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
1. 打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
2. 获取:this.$refs.xxx
功能:让组件接收外部传过来的数据 ;
组件标签传值, props配置项接收, 然后使用传过来的值;
同过v-bind 属性绑定,可以使用组件内的数据; 可以传数据,也可以传递函数;
<Student :id="name"></Student>
一个等号, 赋值操作, 等号右边赋值给左边;
把右边name的值赋值给左边的id, id属性绑定可以使用组件data中的数据了;
在子组件中props配置项中接收id
通常左右两边都是一样的; <Student :name="name" />
props ==>property 属性 ;
v-bind 属性绑定; v-bind:xxx = “表达式” ;
props: [ ] 或 { } ;
props 只可读,不能修改,数据单向流;
type 限制传入的数据的类型;
default 不传值, 会显示默认值 ;
required 和 default 冲突 ;
如果传入的数据是对象或数组类型;
default必须是一个函数; 返回 默认对象或数组 return { } 或 [ ] ;
default() {
return { message: 'hello' }
// or
return ["hello"]
}
1. 功能:让组件接收外部传过来的数据
2. 传递数据: 在组件标签内写属性 <Demo name="xxx"/>
3. 接收数据: props 配置项接受数据
1. 第一种方式:数组形式(只接收):props:['name'] // 加引号接受, 接收到是传过来的数据
2. 第二种方式:对象形式(限制类型):props:{name:String}
3. 第三种方式:对象形式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必传项
default:'张三' //默认值
}
}
> 备注:props是只读的,Vue底层会监测你对props的修改,
如果进行了修改,就会发出警告,
若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
data() {
return {
msg: this.message, // props中的message;
myAge:this.age // props中的age ;
};
},
组件中的this是组件实例对象; vc
通过props实现子父通信 :
通过自定义属性传递方法
通信: 数据/函数等在各个组件实例对象(vc)上的传递;
父组件先在methods中定义一个函数 ;
把这个函数通过v-bind 在 子组件标签上传递给子组件;
子组件在props配置项中接收;
然后子组件调用父组件传递过来的函数,把要传递的数据作为该函数的实参传递过去(也可以不传数据,仅调用父组件传递过来的函数);
父组件接收传递过来的参数,然后使用;
父组件 //App组件
methods: {
// 父组件先在methods中定义一个函数 ;
addTodo(xxx){ //形参
// 父组件接受子组件传递过来的数据作为该函数的参数, 然后使用
this.todos.unshift(xxx)
}
},
<MyHearder :addTodo="addTodo"/> // 在子组件标签上通过v-bind把这个函数传给子组件; //不加()
子组件 // Myhearder组件
props: ["addTodo"], //接受传递过来的函数 // [ ] 中加引号;
this.addTodo(xxx); // 子组件调用这个函数, 把数据作为参数传递给父组件
v-bind: ==> : 属性绑定, 绑定之后可以使用组件/vm上的数据,方法…
v-bind 可以传数据, 也可以传函数, 等等;
函数的形参和实参是一一对应的,函数的调用是实参,函数定义时的参数是形参,函数内部使用的是形参,实参是要确定传过去的值;
实参和形参可以名称不一致, 但一定要一一对应;
模板中,可以使用方法(), 显示方法的最终处理结果return.
功能:可以把多个组件共用的配置项提取成一个混入对象(单独的js文件,暴露出去), 然后引入使用; (复用相同的配置项)
局部混入 引入混入文件, 组件中 mixins:[ ];
全局混入 main.js中 引入混入文件, Vue.mixin( ),vm和所有的vc都可以使用;
组件和混入的配置一样时, 以组件的为主;
mixin(混入)
1. 功能:可以把多个组件共用的配置提取成一个混入对象, 然后复用
2. 使用方式:
第一步: 定义混合, 抽离成单独的js文件,暴露出去:
{
data(){....},
methods:{....}
....
}
第二步: 引入混入文件, 使用混入:
全局混入:Vue.mixin(xxx)
局部混入:mixins:['xxx']
插件(plugins) 与 Vue.use() 方法
Vue.use()的原理: 调⽤插件的 install()⽅法 ;
Vue 插件是一个包含 install 方法的对象
通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等
引入插件, 使用插件
1. 功能:用于增强Vue
2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
3. 定义插件:抽离成一个单独的js文件, plugins.js 暴露出去 ;
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
4. main.js文件 引入插件 import {xxx} from "路径"
5. main.js文件 使用插件:Vue.use( )
scope
App.vue文件不加scoped, 可以给组件添加统一的样式;
scoped作用
lang
/deep/
添加 scoped
<style scoped>
作用:让样式只在当前组件生效,使组件的样式不相互污染。
原理: 自动给标签添加一个唯一标识(hash值), data-v-xxx的属性,配合属性选择器给元素添加样式;
<div data-v-3375b0b8 data-v-7ba5bd90 class="school">
.school[data-v-3375b0b8] {XXX:XXX;}
<style lang="">
编译语言,less/scss/css; 不加lang=""默认css ;
深度穿透;深度作用选择器;
>>> /deep/ ::v-deep :deep()
封装组件:
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数,通过调用函数传参的方法,进行通信)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
存的都是JSON格式的字符串对象;
JSON.stringify()
JSON.parse()
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
相关API:
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
xxxxxStorage.getItem('key');
该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear();
该方法会清空存储中的所有数据。
备注:
SessionStorage存储的内容会随着浏览器窗口关闭而消失。
LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
JSON.parse(null)的结果依然是null。
/ 深拷贝 /
JSON.stringify()方法 // stringify 字符串化;
将对象转成JSON对象,JSON对象的key和value都是字符串;
{"a":"hello","b":"你好"};
对象的key加不加引号都可以的;
JSON.parse()方法
把JSON字符串解析成对象;
toString()方法
转成字符串的方法
在子组件标签上绑定自定义事件;
给子组件的实例对象vc绑定一个自定义的事件, 通过触发这个自定义事件, 传数据来进行通信;
事件处理函数(是一个回调函数) ,写在父组件的methods中;
@自定义事件 = “事件处理函数”
事件处理函数写在父组件中;
自定义事件写在父组件中的子组件标签上;
自定义事件在子组件中触发this.$emit
;
自定义事件被触发, 对应的事件处理函数就会执行;
自定义事件可以和事件处理函数名成可以一致;
事件处理函数在父组件中定义, 来处理子组件传递过来的值 ;
在子组件中通过this.$emit()
来触发自定义事件, 并传递值过去;
this.$emit("自定义事件", 数据)
; // 自定义事件名要加引号;
自定义事件也可以使用事件修饰符;
解绑自定义事件, 解绑指定的this.$off("自定义函数名")
;
解绑多个放一个数组中this.$off(["自定义1","自定义2",.....])
; 解绑所有this.$off()
;
组件也可以绑定原生事件, 通过native修饰符 ; < Demo @click.native = “事件处理函数” / >
1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件, 事件的回调函数(事件处理函数)在A的methods中。
3. 绑定自定义事件:
(1)第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:通过ref绑定自定义事件;
this.$refs.xxx 获得的是该组件的实例对象vc, 然后给他绑定自定义事件,通过$on("自定义事件",回调函数)
// 这个回调函数要么写成箭头函数, 要不提前在methods中写好,然后调用这个函数 this.函数名;
因为写成普通函数, this会指向子组件的实例对象上;
<Demo ref="demo"/>
......
mounted(){
this.$refs.demo.$on('atguigu',this.test)
}
(3)父组件绑定: 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法绑定事件。
<Demo @atguigu.once="test"/>或this.$refs.demo.$once('atguigu',this.test)
(4)子组件触发: 触发自定义事件:this.$emit('atguigu',数据)
(5)父组件: 解绑自定义事件this.$off('atguigu') ;
(6)要在beforeDestroy函数中解绑;
(7)组件上也可以绑定原生DOM事件(click,等等),需要使用native修饰符。
(8)注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
绑定自定义事件
$on("事件名",事件处理函数)
;
解绑自定义事件$off()
;
绑定一次性的自定义事件$once("事件名",处理函数)
;
子组件触发父组件绑定的自定义事件,$emit("自定义事件名",参数)
emits节点:
用于声明由组件触发的自定义事件。
vue3中配置象, emits: [ ‘xxx’ ], 字符串数组;
可以理解为父组件把自定义事件传递给子组件, 子组件通过emits来接收;
vue2中emits节点可以省略, 直接通过$emit来触发自定义事件;
一种组件间通信的方式,适用于任意组件间通信。
this.$bus.$on()
this.$bus.$emit()
this.$bus.$off()
全局事件总线说到底就是个对象,我们通常就是用vm对象作为全局事件总线使用,把vm对象添加到Vue原型对象, 就形成全局事件总线(vm);
Vue.prototype.$bus = this
想要成为事件总线的条件:
1、所有的组件对象必须都能看得到这个总线对象,因此我们把这个对象放在了Vue原型;
2、这个事件总线对象必须能调用$on,$emit,$off方法;
3、总线对象必须是Vue的实例化对象或者是组件对象;
安装全局事件总线:
位置: 在入口文件mian.js中的创建vm实例的beforCreated钩子中 ;
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this, / this就是vm实例,挂载到vue原型上, 每个组件通过this.$bus都也以使用 /
//安装全局事件总线,$bus就是当前应用的vm ; 给Vue原型上添加了一个$bus公共项, 都去通过这个公共项进行通信;
},
......
})
使用事件总线:
1.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(){......} // 事件的处理函数
}
......
mounted() {
this.$bus.$on('xxxx',this.demo) // this.$bus.$on('xxx',()=>{}) 处理函数为箭头函数
}
2.传递数据的组件 :
通过触发 this.$bus.$emit('xxxx',数据)
3.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
beforeDestroy() {
this.$bus.$off('xxxx')
}
要数据的组件 ==> 绑定事件 this.$bus.$on("自定义事件名",处理函数); 箭头函数或者提前定义好 this调用;
传数据的组件 ==> 触发事件 this.$bus.$emit("自定义事件名",参数) ;
要数据的组件 ==> 解绑事件 this.$bus.$off("自定义事件名")
一种组件间通信的方式,适用于任意组件间通信。 依靠 pubsub-js包,pubsub对象;
subscribe订阅消息; publish 发布消息; unsubscribe 取消订阅;
使用步骤:
安装pubsub:npm i pubsub-js -S
引入: import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
订阅消息的组件定义事件,来接收数据
methods(){
// 这个处理函数,两个参数,第一个是自定义事件的名称, 第二个才是传递过来的数据;
demo(name,data){......}
}
......
mounted() {
//订阅消息
this.pid = pubsub.subscribe('事件名',this.demo) //或箭头函数
// this.pid 给vc上追加属性, 要取消订阅this.pid; 类似于定时器的取消;
}
提供数据的组件发布数据;
提供数据:pubsub.publish('事件名',数据) // 发布消息
在订阅消息的组件取消订阅;
最好在beforeDestroy钩子中,用PubSub.unsubscribe(this.pid) 去取消订阅。
provide写法(提供):
1. 使用函数的形式,返回一个对象,可以访问到 this,可以使用data中的数据;
provide(){
return{
xxx:'xxx', // 基本数据类型=>非响应式的;
xxx:{xxx..} // 引用数据类型=>响应式的;
}
}
2. 对象形式,不能使用this,无法传递data中的数据;
provide:{
xxx:'xxx",
}
基本数据类型响应式方法:
import {computed} from 'vue'
provide() {
return {
// c:this.count , count是基本数据类型,不是响应式的;
c: computed(()=>this.count)
/ vue3中 通过computed()方法, 通过匿名函数,return出去 /
},
* / 后代组件使用该值时, .value 使用, 因为该数据变成响应式的了; /
{{c.value}}
inject(注入)写法:
1. 数组写法
inject:['xx','xxx']
2. 对象写法
inject:{
//注入别名, localName是在本组件的属性名; provideName是父组件提供的属性;
localName:{from: 'provideName'} 简写: localName: 'provideName',
//默认值,防止父组件没有提供值过来;
meg:{ default:'默认值xxx' },
}
- 语法: this.$nextTick(箭头函数)
- 作用:在DOM更新完成后,在这个方法中做某件事;
- 原因:data改变是响应式的,但是更新dom是异步的;
- 是一个微任务;
vue 异步更新队列: vue是依靠数据驱动视图更新的,该更新的过程是异步的。即:当侦听到你的数据发生变化时,Vue将开启一个异步更新队列,等这个队列中所有数据变化完成之后,再统一进行更新视图(DOM)。
vue在修改数据后,视图(DOM)不会立刻更新,要等到所有数据变化完成之后,再统一进行视图更新。
所以,在修改数据更新立马读取DOM是获取不到新数据的,
可以在修改数据之后, 使用这个nextTick方法, 在指定的函数里获取更新后的DOM。
nextTick 是 Vue 提供的一个方法,用于在 DOM 更新之后执行回调函数。
它的作用是等待当前 DOM更新队列中的所有同步和异步更新完成后,再执行传入的回调函数。
在 Vue 中,当我们修改了组件的数据或者调用了一些可能会触发 DOM更新的方法时,这些更新不是立即生效的,而是会被加入到一个队列中,在下一次事件循环中进行处理。
也就是说,如果我们需要在某个 DOM 更新完成后执行一些操作(例如获取更新后的 DOM 元素),直接在更新代码之后立即执行是不准确的。
这时候就可以使用 nextTick方法来确保在 DOM 更新完成后再执行相应的操作。
需要注意的是,nextTick() 方法是异步执行的,也就是说回调函数会在下一个事件循环才被调用,因此应该将相应的操作放在回调函数中以确保在正确的时机执行。
封装一个组件会多次复用,但是不同场景下又有少部分结构数据会发生变化,(当然可以用不同的子组件)
那么就得通过父组件告诉子组件结构的变化的内容是什么,此时就要用到这个插槽slot;
子组件当中<slot></sloat> 其实就是占位用的,让父组件标签内给它填充内容,可以带标签;
父组件标签内写内容, 子组件对应的位置显示内容;
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式;
插槽通常与template标签配合使用, emplate不会被解析;
插槽就是预留的一个位置, 把组件标签内的内容解析完之后放到指定位置;
匿名插槽
v-slot:default <==> #default
没有名字的插槽
父组件中:
<Category>
<div>html结构</div>
</Category>
子组件中:
<template>
<div>
<slot>插槽默认内容...</slot> // 会显示 html结构, (后备内容)
</div>
</template>
具名插槽
有名字的插槽, 父组件标签内容放到子组件<slot name="xxx">的name相对应的位置;
语法: <p v-slot:xxx> hello vue </p>
<slot name="xxx"> </slot>
插槽的简写: v-slot:xxx <==> #xxx <==> slot="xxx" 只能添加在 <template>标签上;
插槽的默认值: v-slot:default <==> #default ;
<slot></slot> <==> <slot name="default"></slot>
template标签的v-slot:xxx == #xxx ;
v-slot:xxx 只能配合template标签使用;
父组件中:
<Category>
<template v-slot:header>
<div>header位置</div>
</template>
<template #center>
<div>center位置</div>
</template>
<template slot="footer">
<div>footer位置</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="header"></slot> // header位置
<slot name="center">默认</slot> // center位置
<slot name="footer">默认</slot> // footer位置
</div>
</template>
作用域插槽:类似于子传父通信, 把子组件的数据通过属性,包装成一个对象(通常命名scope),传递给父组件使用;
默认不能有其它插槽,如果要有其它插槽,必须设置为具名插槽;
理解:数据在子组件,但根据数据生成的结构需要父组件来决定。
子组件slot标签, 通过添加属性的方式传值;
所有添加的属性,会收集到一个对象中(通常命名scope), 传递给父组件;
父组件标签中的tempalate通过 #插槽名="scope"接收, 匿名插槽名为default;
子组件:
<template>
<div>
<slot msg='四大名著' :shuName='books'></slot> // 默认插槽
<slot name="game" id='手游' :games='games'></slot> // 具名插槽
</div>
</template>
<script>
export default {
name: 'slotComponent' ,
props: {
},
data () {
return {
books:['西游记','水浒传','三国演义','红楼梦'],
games:['原神','王者荣耀','穿越火线']
}
}
}
</script>
父组件:
<MySlot>
<template v-slot:default="scope"> // 匿名插槽 #default, 传递过来的对象通常命名为scope;
<div>{{scope}}</div> // { "msg": "四大名著", "shuName": [ "西游记", "水浒传", "三国演义", "红楼梦" ] }
<div>{{scope.msg}}</div> // 四大名著
</template>
<template #game="{games:youxi}"> // v-slot简写, scope对象的解构并重命名
<div>{{youxi}}</div> // [ "原神", "王者荣耀", "穿越火线" ]
</template>
</MySlot>
slot属性弃用,具名插槽通过指令参数v-slot:插槽名的形式传入,可以简化为#插槽名;
slot-scope属性弃用,作用域插槽通过v-slot:xxx="slotProps"的slotProps来获取子组件传出的属性对象;
v-slot属性只能在template上使用
同源策略: 协议,主机名(域名,IP), 端口号;三者相同; 跨域: 违反同源策略, 三者有一个不相同就会跨域,
不是同源的脚本不能操作其他源下面的对象;
解决跨域的方法:
对脚手架进行定制, 在vue.config.js中配置;
方法一: 在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
1.优点:配置简单,请求资源时直接发给前端(8080)即可。
2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端本地的资源)
方法二: 编写vue.config.js配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径, 请求前缀,在端口号后面;
target: 'http://localhost:5000',// 代理服务器的请求地址
changeOrigin: true,
pathRewrite: {'^/api1': ''} // 键值对形式,正则表达式; 代理服务器对请求路径进行重定向以匹配到正确的请求地址
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径;
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true, // 伪装
pathRewrite: {'^/api2': ''} // 重新匹配请求路径
}
}
}
}
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true;
说明:
1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2.缺点:配置略微繁琐,请求资源时必须加前缀。
集中式状态(数据)管理工具,管理共享的数据,这个数据是响应式的,多组件共享;
vuex中的数据储存在内存中,刷新消失,单向数据流;
在Vue中实现集中式状态(数据)管理的一个Vue插件;
对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
核心在于创建的store对象, 全局都能访问到store对象;$store
对象管理数据, 不能直接修改$store
中的state数据,vue开发者工具检测不到;
state 存储数据 ==> data ;
actions 处理异步任务: 调用commit方法 ; 可以获取context上下文对象,mini版的$store;
mutations 处理state中的数据, 处理同步任务:修改state中的数据必须通过mutations, 可以获取state对象;
getters ==>计算属性,依靠返回值:对state中的数据进行处理,然后使用,不会修改state中的数据, $store.getters.计算属性名; 可以获取state对象;
moduls 模块化的vuex:可以让每一个模块拥有自己的state、mutations、actions、getters,使得结构非常清晰,方便管理。
搭建vuex环境
npm i Vuex -S ;
创建文件:src/store/index.js 导出store对象; // 引入文件时, 引入index.js文件, 可以省略/index.js // import store from "./store"
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex) // vue-cli 解析时,会先解析import 引入的东西;
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//准备getters对象, 列斯与计算属性, 对state中的数据进行加工然后使用;
const getters = {}
//创建并暴露store
export default new Vuex.Store({ // 通过new Vux.Store对象创建的store
actions,
mutations,
state,
getters
})
在main.js入口文件中创建vm时传入store配置项 // 全局都可以访问store ,有了$store对象;
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
基本使用
组件读取vux中的数据 : $store.state.数据名;
组件修改vuex中的数据: $store.dispatch(‘actions中的方法名’,数据) 或 $store.commit(‘mutations中的方法名’,数据);
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit;
mutation中方法(函数)和commit的方法, 方法名通常大写;
context上下文对象, 迷你版的$store
,actions中用到 ;
actions 中的方法,接收到两个参数:
第一个是context对象,这是一个min版的$store
对象, 第二个参数是,传递过来的数据;
mutations 中的的方法,修改state中的数据, 接收到两个参数:
第一个是 state 对象,可以拿到state中的数据 ;第二个参数是 传递过来的数据;
getters 中的方法中的参数可以接收到state对象:对state中的数据经过加工后再使用,this.$store.getters.getter中的数据
;
state, 保存的数据; 读取this.$store.state.数据名
// 处理异步任务,业务逻辑,按顺序处理
const actions = {
jia(context,value){
context.commit('JIA',value) // commit的方法名,通常大写
},
}
// 处理同步任务,可以直接修改state中的数据 , mutations中的方法名,通常大写
const mutations = {
JIA(state,value){
state.sum += value
}
}
//vux中的数据
const state = {
sum:0
}
// 对state中的数据进一步加工
const getters = {
bigSum(state){
return state.sum * 10
}
}
vuex的四个辅助函数函数;
// 引入vuex的辅助函数
import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
...mapState和...mapGetters在computed中映射并解构出来值,然后使用;
...mapMutations和...mapActions在methods中映射并解构出来方法,然后调用方法;
mapState方法:用于帮助我们映射state中的数据为计算属性; 在computed配置项中;
<p>学校名:{{ xuexiaoName }}</p>
computed: { // 依靠return返回值;
//借助mapState生成计算属性:schoolName、subject(对象写法)
...mapState({xuexiaoName:'schoolName',subject:'subject'}), // 对象解构的重命名, 把schoolName重命名为xuexiaoName.
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','schoolName','subject']),
//等价与
xuexiaoName(){
return this.$store.state.schoolName
},
....
},
mapGetters方法:用于帮助我们映射getters中的数据为计算属性; 在computed配置项中;
<h4>计算属性:{{ bigSum }}</h4>
computed: { // 计算属性依靠返回值;
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
//等价与
bigSum() {
return this.$store.getters.bigSum
},
...
},
mapActions方法:用于映射生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数 ; 在methods配置项中;
1. 在methods中解构出来action中方法,在模板中直接调用这个方法并传参;
<button @click="incrementOdd(n)">++</button>
<button @click="jiaOdd(n)">++</button>
methods:{ //methods配置项;
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//对象的解构并命名; methods配置项中的方法名=>incrementOdd; action中的方法名=>jiaOdd
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
2. 绑定事件时不传参, 在事件的处理函数中,调用映射过来的函数时传参;
<button @click="evenJia">++</button>
methods:{
// 解构出来action中的方法, 调用传参
...mapActions(['jiaOdd'])
evenJia(){
this.jiaOdd(this.n)
},
}
3.//等价与
methods:{
incrementOdd() {
// 通过dispatch调用action中的方法
this.$store.dispatch("jiaOdd", this.n);
},
}
mapMutations方法:通过导入的mapMutations函数,将需要的mutations方法,映射为当前组件的methods的方法,然后调用这个方法,即:包含$store.commit(xxx)的函数, 在methods配置项中;
1. <button @click="increment(n)">++</button>
<button @click="increment2">++</button>
methods:{ // methods配置项中
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}), //increment: method配置项中的方法名 ; JIA: mutation中的方法名
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
// 调用JIA方法并传参
increment2(){
this.JIA(n)
}
}
- <button @click="evenJia">++</button>
methods:{
// 解构出来, 调用传参
...mapActions(['increase'])
evenJia(){
this.increase(this.n)
}
}
- methods:{
//等价与
increment() {
this.$store.commit("JIA", this.n);
},
}
获取state中的数据: 1.
this.$store.state.数据名
2. …mapState
获取getters中的数据: 1.this.$store.getters.计算属性名
2. …mapGetters
触发actions中的方法: 1.this.$store.dispatch("方法名", 参数)
; 2. …mapActions
触发mutations中的方法: 1.this.$store.commit("方法名",参数)
2. …mapMutations
mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象evnet。
模块化 + 命名空间
模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
作用:让代码更好维护,让多种数据分类更加明确,每个组件各维护对应的数据;
namespaced:true; 开启命名空间
…mapXXX(“模块化名” , [ “xxx”] )
…mapXXX( “模块化名”, { “xxx”: “xxx” } )
创建关于count组件的模块
// counAbout
export default{
namespaced:true,//开启命名空间
state:{...},
mutations: { ... },
actions: { ... },
getters: {
...}
}
创建关于person组件的模块
// personAbout
export default{
namespaced:true,//开启命名空间
state:{...},
mutations: { ... },
actions: { ... },
getters: {
...}
}
src/store/index.js文件
// 创建store对象
import Vue from "vue";
import Vuex from "vuex"
// 使用vuex
Vue.use(Vuex)
// 引入模块化的vuex
import countAbout from "./module/count.js";
import personAbout from "./module/person";
const store = new Vuex.Store({
// 模块化
modules: {
countAbout,
personAbout
}
})
开启命名空间后,组件中读取state数据:
this.$store.state.moduleName.value
//方式一:自己直接读取; 从对应的模块中读取state中的数据
this.$store.state.personAbout.xxx;
//方式二:借助mapState读取:
computed: {
...mapState('countAbout',['sum','school','subject']), // 在computed中从对应的模块中读取state中的数据
}
开启命名空间后,组件中读取getters数据:
this.$store.getters['moduleName/getterName']
//方式一:自己直接读取 ; 从对应的模块读取getters中的数据
this.$store.getters['personAbout/xxx']
//方式二:借助mapGetters读取:
computed: {
...mapGetters('countAbout',['bigSum']) // 在computed中从对应的模块中读取getters中的数据
}
开启命名空间后,组件中调用dispatch触发actions中的方法:
this.$store.dispatch('moduleName/actionName',payload)
//方式一:自己直接dispatch; 明确从对应的模块中读取actions中方法;
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
methods: {
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) // 在methods中从对应的模块中读取actions中方法;
...mapActions('moduleName', ['actionName'])
}
开启命名空间后,组件中调用commit中的方法 :
this.$store.commit('moduleName/mutationName')
//方式一:自己直接commit; 明确从对应的模块中读取mutations中方法;
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
methods: {
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //在methods中从对应的模块中读取mutations中方法;
...mapMutations('moduleName', ['mutationName'])
}
vuex的缺点: 刷新浏览器,vuex中的state会重新变为初始状态; 因为vuex存储的数据在内存中,刷新消失;
解决方案:
1.使用插件vuex-persistedstate 让vuex的数据持久化;
2. 将vuex的数据进行本地存储,将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie);
在 Web 页面初始化时加载相应的 html结构、JavaScript交互 和CSS样式。一旦页面加载完成,根据url地址栏的不同,来实现对应的路由组件的切换,整个页⾯是没有进⾏刷新的,只是组件与组件之间的卸载与装载的过程。
VueRouter 路由
路径和组件的映射关系;
hash地址与组件之间的对应关系;
hash地址是#后面的内容(包括#);
一个路由(route)就是一组映射关系(key - value),路径和组件的展示关系;多个路由需要路由器(router)进行管理。
只有一个路由器router,每个组件都有的路由route; 全局有$router对象;且只有一个。
vue-router是基于路由和组件的;
路由用于设定访问路径,将路径和组件映射起来;
在vue-router的单页面应用中,页面的路径的改变就是组件的切换;
路由组件通常存放在pages或views文件夹,一般组件通常存放在components文件夹。
通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
每个组件都有自己的$route
属性,里面存储着当前组件的路由信息。
整个应用只有一个router,可以通过组件的this.$router
属性获取到。
this.$router是路由的导航对象
this.$route是路由的参数对象
vueRouter的使用:
路由模块封装的基本使用:
安装vue-router,命令:npm i vue-router
编写router/index.js配置项: 创建router对象;
// 创建并暴露router对象;
//引入VueRouter
import VueRouter from 'vue-router'
//使用VueRouter
Vue.use(VueRouter)
//引入路由的组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
mode: 'hash', // 'history' 路由的工作模式;
routes:[ //路由规则是 routes ,一个数组;
{
path:'/about', // path路径,带/
component:About //引入的组件
},
{
path:'/home',
component:Home
}
]
})
//暴露router对象
export default router
在main.js引入,并挂载到vm上; 全局就有了$router
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入创建的router对象
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router, // 挂载router
}).$mount('#app')
实现切换(active-class可配置高亮样式)
active-class属性可以控制路径切换的时候对应的router-link渲染的dom添加的类名;
router-link自带激活时的高亮类名;
<router-link active-class="active" to="/about">About</router-link>
active-class 激活时的样式active样式, 点击后高亮;
router-link:
进行跳转, 自带激活时的高亮类名;
该标签是一个vue-router中已经内置的组件,它会被渲染成一个<a>标签; tag属性渲染成其他标签;
replace属性:
作用: 1.replace属性可以控制router-link的跳转不被记录;
2.浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push;
3.开启replace模式: < router-link replace > ==> < router-link :replace="true" >
router-view
路由组件的展示位置;
<router-view></router-view> :指定组件展示的位置; 没有name, 默认default;
<router-view name="componentsName">:该标签会根据当前的命名视图name,动态渲染出不同的组件;
切换路径时,切换的是<router-view>挂载的组件;
多级路由(路由的嵌套)配置:
在上级路由配置项中添加 children:[ { } ], 在children中配置自己的路由规则;
子级路由的path不要加 / ;
子级路由也要在父级路由组件中配置路由出口,
默认子路由, 子路由的path: " " ;
routes:[
{ // 一级路由
path:'/home',
component:Home,
redirect: '/home/mews', / 重定向到News路由组件 /
children:[ //通过children配置子级路由
{ // 二级路由
path:'news', // 子级路由不要加 /, => 渲染后/home/news ;
component:News
},
/ 默认子路由 /
{
path:'', // 子路由的path为空, 默认展示该子路由; => /home时就展示Title子路由组件;
component:Title
},
}
]
跳转(要写完整路径): 从父子到子级具体的路径; 路由的路径, 带 / 开始 ;
<router-link to="/home/news">News</router-link>
router-link 解析成a标签; to == href ; tag解析成其他标签, tag="div',通过配置 tag 属性生成别的标签;
路由的路径
单独的路由规则
// 默认路由
{
path:"/", / 根路径; /
path:"*", / 任意路径, 用作404页面,前面路径都不匹配就用这个,配置在路由的最后面; /
redirect:"/home" , / 匹配到path后, 强制跳转到指定的path; /
alias: "/about", / 别名 /
},
命名路由
作用:可以简化路由的跳转。 给路由组件的配置项添加name属性 ;
routes:[
{ // 一级路由
path:'/home',
name:'shouye',
component:Home,
children:[ //通过children配置子级路由
{ // 二级路由
path:'news', //此处一定不要写/news; 子级路由不要加 /
name: "xinwen", // 命名路由
component:News
},
}
]
<!--简化前,需要写完整的路径 -->
<router-link to="/home/news">新闻</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'xinwen'}">新闻</router-link> // name命名路由相当与path的路径;
等价与
this.$router.push({name:"xinwen"})
命名视图
在components内配置;
如果 router-view 没有设置名字,那么默认为 default ;
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
// 一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置;
const router = new VueRouter({
routes: [
{
path: '/',
components: { //是components
default: Foo, // 默认
a: Bar,
b: Baz
}
}
]
})
路由传参
路由传参:嵌套路由时父路由向子路由传递参数;
路由传参:父路由把数据传递给子路由;
query传参 ? key1=value1&key2=value2
类似于get请求
http://localhost:8080/#/home/?id=1&title=消息1
params传参 /xxx/xxx/xxx
类似于post请求, 将参数放在请求体中传递;
params传参需要配置path, 在path中用 : 占位 ; (动态路由)
url地址栏不显示参数名, 只会显示参数值;
params 传参是将参数直接嵌入到路由路径中,而不是作为查询字符串追加到 URL 上。
{
path: '/home/:id/:title', // : 动态路由参数:占位;
name:'home' // 命名路由
}
<router-link :to="{ name: 'home', params: { id: 1, title:'消息1' } }">home组件</router-link>
http://localhost:8080/#/home/1/消息1
query传参
声明式
<router-link :to=`/home?id=${'hello'}&title=${'你好'}`>字符串写法</router-link> //模板字符串写法
<router-link
:to="{
path:'/home',
query:{
id:'hello',
title:'你好'
}
}"
>对象写法</router-link> // path写法
<router-link
:to="{
name:'name',
query:{
id:'hello',
title:'你好'
}
}"
>对象写法</router-link> // name命名路由写法
url地址栏解析为: /home?id=hello&title=你好
编程式
this.$router.push( `/home?id=${1}&title=${'hello'}`) // 模板字符串写法
this.$router.push({ path:"/home", query:{ id:1, title:"hello"} }) // path写法
this.$router.push({ name:"home", query:{ id:1, title:"hello"} }) // name命名路由写法
url地址栏解析为: /home?id=1&title=你好
读取参数, 借助组件的$route;
this.$route.query.xxx
params传参
通过路由属性中的name来确定匹配的路由,通过params来传递参数。
params传参, 需要占位符, 跳转路由,只能使用name命名路由, 不能使用path;
配置路由,声明接收params参数, : 占位符占位; (动态路由)
如果通过path跳转,params 会被忽略, paramas传参需要通过name命名路由来跳转;
接收参数的组件,配置路由
params传参,先要配置路由; path: " :占位符 "
const router = new VueRouter({
routes: [
{
name:'news',
path:'news/:id/:title', //使用占位符声明接收params参数
component:News
}
]
})
声明式
<router-link
:to="{
name:'news,
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link> // 通过name命名路由跳转
// 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
<router-link
:to=`/news/&{666}/${'你好'}}}`
>跳转</router-link> // 通过模板字符串跳转
url地址栏解析为: /news/666/你好
编程式
this.$router.push({name:'news',params:{id:123,title:'hello'} })
this.$router.push(`/news/${123}/${'hello'}`)
url地址栏解析为: /news/123/hello
params读取参数, 借助$route;
this.$route.params.xxx
动态路由参数可选符:
路由有时候需要参数,有时候不需要参数,也希望匹配, 使用 ?
{
path:'home/:id?', / :参数? /
component:()=>import('@/views/home'),
name:'Home'
}
路由的props配置项
作用:让路由组件更方便的收到参数;
接收动态参数, 配置 path:’ /xxx/:xxx’, 例如: path: ‘/home/:id’;
谁接收参数,谁去配置props配置项;
在路由配置中配置props; 在组件中, 配置props,接收参数; prosp:[‘xxx’,‘xxx’]
路由关系中: prosp:{ } 对象形式;
props:true, 只能接收params参数, 所以要声明接收的是params参数, path要是用:占位符;
props:function($route
){ return { } } ,$route
为参数, 依靠返回值;
接收参数的组件配置props:
props:[ "id","a"]
//接收参数的组件==>路由配置关系
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给组件
props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给组件
props:true; //只能使params传参;
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给路由组件
props($route){ // props为函数时,可以接收到$route参数, 也可以解构赋值,连续解构赋值;
return {
id:$route.query.id,
title:$route.params.id // 需要提前声明接收的是params参数;
}
}
声明式导航,编程式导航
实现路由的跳转
声明式导航
to, tag, replace
编程式导航
this.$router.push() //传参相当于router-link的to属性;
this.$router.replce() //传参相当于router-link的to属性,跳转不被记录;
this.$router.back() //后退,不用传参
this.$router.forward() //前进,不用传参
this.$router.go() //传参数,正数前进,负数后退,0刷新;
编程式导航跳转
1.this.$router.push('路径')
2.this.$router.push({
path:'路径'
})
3.this.$router.push({
name:'路由名'
})
编程式导航跳转+传参-query
this.$router.push('/路径?参数名1=参数值1&参数名2=属性值2')
this.$router.push({
path:'路径',或 name:'路由名',
query:{
参数名1:参数值1,
参数名2:参数值2,
}
})
编程式导航跳转+传参-params
首先需要配置动态路由 {path:'路径/:参数值', component:()=>import('@/文件路径'), name:'路由名'}
this.$router.push('/路径/参数值1/参数值2')
this.$router.push({
name:'路由名', / 编程式导航跳转+params传参只能通过name跳转 /
params:{
参数名1:参数值1,
参数名2:参数值2
}
})
<button @click="pushShow(n)">push</button>
methods: { //methods中
pushShow(n){ // 使用数据,调用传参的形式传递过来;
this.$router.push({ //调用push方法, 传参相当于router-link的to属性;
name:'xw',
params:{id:n.id,news:n.new}
})
}
},
<button @click="replaceShow(m)">replace</button>
methods: {
replaceShow(m){
this.$router.replace({ //调用replace方法
name: 'msg',
query: {
id: m.id,
title: m.title,
}
})
}
},
缓存组件,不被销毁;
但被缓存的组件, 再次进入或离开不再执行组件的生命周期;
路由的跳转就是组件的销毁与加载;
作用:让不展示的组件保持挂载,不被销毁。
组件名 => 组件的name配置项;
缓存一个组件 include = “组件名”;
缓存多个组件 include = “组件名1, 组件名2,…” ; //逗号隔开
缓存多个组件 :include = “[ ‘xxx’, ‘xxx’]” // 数组名数组
不缓存某个组件,其他组件都缓存; exclude= “组件名”
不加include,默认包裹的组件及其子组件都被缓存;
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
keep-alive的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活和失活状态。
原因: 被缓存的组件,再次进入或离开不再执行生命周期函数;
activated() { }, 路由组件被激活时触发。
deactivated() { }, 路由组件销毁时触发。
compnent 动态组件-内置组件
组件的占位, 渲染成指定的组件;
通过is属性指定要渲染的组件;
is属性的值是components配置项中的组件注册名;
<keep-alive>
<component v-bind:is="componentName"></component>
</keep-alive>
1.搭配<keep-alive>来实现动态组件的缓存;
2.可以通过v-bind:is动态指定显示那个组件;
3.'componentName'是components配置项中的注册组件名;
meta路由元信息
路由的配置项: 路由元信息,是一个对象;
在路由规则中配置这个路由组件独有的信息; meta:{title:“首页” } ;
是一个对象,获取:this.$route.meta.xxx
路由懒加载
当路由被访问时,才加载对应的组件,提高了首屏加载效率;
一打开就有的页面不需要路由懒加载;
在router文件的index.js文件中配置;
写成箭头函数的形式,按需加载;
import Film from './views/Film'
{
path: '/film',
component: Film,
}
/ 替换成路由懒加载 /
*1.
const Film = () => import('../views/Film.vue')
{
path: '/film',
component: Film,
}
*2.
{
path: '/film',
component: () => import('../views/Film.vue'), // 箭头函数形式,按需加载
}
控制路由的访问权限;
路由钩子函数, 进入之前调用,离开之前调用;进行拦截或者其他操作;
作用:对路由进行权限控制;
当路由跳转前或跳转后、进入、离开某一个路由组件前、后,需要做某些操作,就可以使用路由钩子来监听路由的变化;
分类:全局守卫、独享守卫、组件内守卫;
参数: 是一个箭头函数, 这个箭头函数的参数, to from next;
- next() 直接放行,正常跳转;
- next('/路径') 或 next({path: '/路径'}), 强制跳转到指定页面;
- next(false), 不跳转,强制停留在当前页;
- 不声明next形参,默认允许访问每一个路由;
- 声明了next形参,必须调用next()函数,否则不允许访问任何一个路由;
全局路由守卫 : 在路由配置文件 index.js中配置;
router.beforeEach( ) 全局前置路由守卫; 页面初始化、路由切换之前调用;
router.afterEach( ) 全局后置路由守卫( 没有next方法); 页面初始化、路由切换之后调用;
router.beforeResolve( ) 全局解析守卫;
独享路由守卫: 在路由规则中配置; to参数的对象就是要配置的路由组件;
beforeEnter( ) ; 切换对应的组件之前调用;
组件内路由守卫: 配置在路由组件文件中的钩子;
beforeRouteEnter( ) ; 通过路由规则,进入该组件时被调用;
beforeRouteLeave( ) ; 通过路由规则,离开该组件时被调用;
beforeRouteUpdate( ); 在当前路由改变,同时该组件被复用时调用;
全局路由守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行,没有next()
router.afterEach((to,from)=>{
document.title = 'to.meta.title' || "vue"
})
独享路由守卫 :
// 切换对应的组件之前调用;
routes: [
{
path: '/foo',
component: Foo,
//进入到Foo组件之前调用!
beforeEnter: (to, from, next) => {
next()//放行
}
}
]
组件内路由守卫:
路由组件文件中的函数钩子,类似于生命周期函数;
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
/不能获取组件实例 `this`, 因为当守卫执行前,组件实例还没被创建; /
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
/ 离开该组件的对应路由时调用,可以访问组件实例 `this`; /
},
beforeRouteUpdate(to, from, next) {
/ 在当前路由改变,但是该组件被复用时调用, 可以访问组件实例 `this`; /
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
}
创建router时; mode: ‘hash’ 或 ‘history’
hash模式(前端路由):默认hash模式;
hash 改变会触发 hashchange 事件;
hash模式原理: 调用了window.onhashchange方法监听 hash值的切换;
hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件,由于 hash值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段);
更关键的一点是,因为hash发生变化的url都会被浏览器记录下来;
mode: 'hash'
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式的原理: 本质使用H5的histroy.pushState方法来更改url,不会引起刷新。
mode: 'history'
地址干净,美观 ,不带#号。
兼容性和hash模式相比略差。
上线服务器后,刷新页面服务端404的问题,应用部署上线时需要后端人员配置,将所有访问都指向index.html;
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式下,仅hash符号之前的内容会被包含在请求中。
history模式下,前端的url必须和实际向后端发起请求的url 一致。
hash值