Vue笔记
视频: https://www.bilibili.com/video/BV1Zy4y1K7SH?p=1
vue是渐进式JavaScript框架 用到什么功能,只需要引入什么功能模块 ;
vue的特点:易用,灵活,高效;
- 组件化 , 一个vue文件包括了(html +css + js)
- 声明式编程(不直接操作DOM) ;
- 虚拟DOM + diff算法(虚拟dom的对比)
Vue2
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:"世界"}
})
vue模板语法
{{ }} 插值语法/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">
双向数据绑定的原理: v-bind 绑定value属性, v-on监听input事件 ;
el与data的两种写法:
el: '#app',// 通过实例vm挂载vm.$mount("#app")
data:
对象形式
data: {msg:"你好 vue"}
函数形式, 返回一个对象
data() {return {msg:"你好 VUE"}
}
vue实例中使用数据, 记得加this;
模板中使用数据,不用加this;
模板中调用方法,加()/也可以不加,但是不传参,默认是event事件;
模板中调用计算属性,不用加(),计算属性是一个属性,get方法简写了;
MVVM框架:
Vue中的数据代理:
Object.defineProperty的getter()与setter()方法
事件处理: v-on
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);},
},
事件修饰符 :
- .stop 阻止事件冒泡 ==> event.stopPropagation( )
- prevent 阻止默认行为 ==> event.preventDefault();
- .once 事件只触发一次
- .native 触发组件或原生监听事件;
- .self 只有在 event.target是当前操作的元素时才触发这个事件
- .capture 事件的捕获阶段
- .passive
- 修饰符可以连续使用
键盘修饰符:
键盘事件:
-
keydown 键盘按下触发事件
-
keyup 按下去的键盘松开触发事件
-
键盘keyCode码
键盘修饰符: <input type="text" @keydown.enter=key>
计算属性 computed
配置项: 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>
监视属性(侦听属性) watch :
-
监视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样式
- 绑定style样式
绑定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样式
行内样式 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-show指令后面要跟表达式, 只有当表达式结果为真才显示;
v-if 是真正的节点的创建与销毁;
v-if 与 v-else-if , v-else配合使用, v-else后面不用在跟表达式,中间不能断开; v-if可以配合 template标签使用,
不会渲染template标签; tempalte标签无法添加样式;
v-show只是行内样式: display属性的显示与隐藏;
列表渲染 v-for
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 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
以下方法会改变原数组;
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- Vue.set()
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 双向数据绑定
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:输入首尾空格过滤;
v-model原理:
v-model拆解:
v-model简化:
.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>
自定义指令 direactive
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-directive3.指令的回调函数名是短横线命名的, 可以用引号引起来,解决报错;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 或 组件实例对象。
Vue的生命周期
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实例对象;
组件 component
组件 : 为了复用;
非单文件组件 : 包含多个组件。
单文件组件: 只包含一个组件(.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).组件配置中:vcdata函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。(2).new Vue(options)配置中:vmdata函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
6.Vue的实例对象,以后简称vm。
组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods以及生命周期钩子等。
el除外,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝; 防止数据的污染。
Vue与Vuecomponent的关系
- 一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。 (组件可以访问vue上的数据,方法。。。)
- vm对象和组件对象的关系: vm的原型对象 === 组件对象的原型对象的原型对象。
**单文件组件: 以.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 文件名后缀名可以省略;
组件分类: 页面组件和复用组件。
- 页面组件存放在 src/views文件夹=>配合路由, 展示页面;
- 复用组件存放在src/components文件夹=>封装复用, 展示数据;
省略
.vue文件/.js文件 引入 可以省略文件名的后缀 ;
引入index.js文件可以省略, 直接引用到文件夹路径 ;
脚手架 Vue-cli
文档: 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属性
作用: 打标记 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 自定义属性:
功能:让组件接收外部传过来的数据 ;
组件标签传值, 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' }// orreturn ["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.
mixin 混入
功能:可以把多个组件共用的配置项提取成一个混入对象(单独的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. 功能:用于增强Vue2. 本质:包含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不会报错,但不推荐这样做。
webStorage 浏览器端的本地存储
存的都是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()方法转成字符串的方法
$emit 自定义事件
在子组件标签上绑定自定义事件;
给子组件的实例对象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来触发自定义事件;
全局事件总线 (GlobalEventBus)
一种组件间通信的方式,适用于任意组件间通信。
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("自定义事件名")
使用的时候引入Bus; import Bus from ‘@/utils/EventBus.js’
消息订阅与发布 (pubsub)
一种组件间通信的方式,适用于任意组件间通信。 依靠 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&inject 依赖注入
provide/inject 提供/注入
适用于多层 祖先/父组件 向子/孙组件通信;
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' },
}
Vue.nextTick
- 在下一次 DOM 更新循环结束之后执行延迟回调。
- 在修改数据之后立即使用这个方法,获取更新后的DOM。
- 想要立即拿到更新后的DOM,要在nextTick的回调函数中去获取更新后的Dom的值。
- 语法: this.$nextTick(箭头函数)
- 作用:在DOM更新完成后,在这个方法中做某件事;
- 原因:data改变是响应式的,但是更新dom是异步的;
- 是一个微任务;
vue 异步更新队列: vue是依靠数据驱动视图更新的,该更新的过程是异步的。即:当侦听到你的数据发生变化时,Vue将开启一个异步更新队列,等这个队列中所有数据变化完成之后,再统一进行更新视图(DOM)。
vue在修改数据后,视图(DOM)不会立刻更新,要等到所有数据变化完成之后,再统一进行视图更新。
所以,在修改数据更新立马读取DOM是获取不到新数据的,
可以在修改数据之后, 使用这个nextTick方法, 在指定的函数里获取更新后的DOM。
- 等待当前 DOM 更新队列中的所有同步和异步更新完成后执行回调函数。
- 用于确保在 DOM 更新之后执行操作,例如获取更新后的 DOM 元素。
nextTick 是 Vue 提供的一个方法,用于在 DOM 更新之后执行回调函数。
它的作用是等待当前 DOM更新队列中的所有同步和异步更新完成后,再执行传入的回调函数。
在 Vue 中,当我们修改了组件的数据或者调用了一些可能会触发 DOM更新的方法时,这些更新不是立即生效的,而是会被加入到一个队列中,在下一次事件循环中进行处理。
也就是说,如果我们需要在某个 DOM 更新完成后执行一些操作(例如获取更新后的 DOM 元素),直接在更新代码之后立即执行是不准确的。
这时候就可以使用 nextTick方法来确保在 DOM 更新完成后再执行相应的操作。
需要注意的是,nextTick() 方法是异步执行的,也就是说回调函数会在下一个事件循环才被调用,因此应该将相应的操作放在回调函数中以确保在正确的时机执行。
插槽 slot
封装一个组件会多次复用,但是不同场景下又有少部分结构数据会发生变化,(当然可以用不同的子组件)
那么就得通过父组件告诉子组件结构的变化的内容是什么,此时就要用到这个插槽slot;
子组件当中<slot></sloat> 其实就是占位用的,让父组件标签内给它填充内容,可以带标签;
父组件标签内写内容, 子组件对应的位置显示内容;
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式;
插槽通常与template标签配合使用, emplate不会被解析;
插槽就是预留的一个位置, 把组件标签内的内容解析完之后放到指定位置;
- 匿名(默认)插槽
- 具名插槽
- 作用域插槽
匿名插槽
v-slot:default <==> #default
没有名字的插槽
- 父组件标签内有内容, 子组件对应的内没有默认值, 就会显示该内容;
- 父组件标签内没有内容, 子组件默认值 会显示默认值;
- 父组件标签内有内容, 子组件默认值, 插槽的默认值会被覆盖;
父组件中:
<Category><div>html结构</div>
</Category>
子组件中:
<template><div> <slot>插槽默认内容...</slot> // 会显示 <div>html结构</div>, (后备内容)</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> // <div>header位置</div><slot name="center">默认</slot> // <div>center位置</div><slot name="footer">默认</slot> // <div>footer位置</div></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上使用
Proxy 代理服务器
同源策略: 协议,主机名(域名,IP), 端口号;三者相同; 跨域: 违反同源策略, 三者有一个不相同就会跨域,
不是同源的脚本不能操作其他源下面的对象;
解决跨域的方法:
- cros+后端配置;
- JSONP, 利用了script标签的src属性不受同源策略影响的特性, 但是只能get请求;
- proxy, 配置代理服务器, (ngix服务器配置, vue-cli配置) ;
对脚手架进行定制, 在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:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080changeOrigin默认值为true;说明:1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。2.缺点:配置略微繁琐,请求资源时必须加前缀。
Vuex
集中式状态(数据)管理工具,管理共享的数据,这个数据是响应式的,多组件共享;
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对象创建的storeactions,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);
SPA单页面应用
在 Web 页面初始化时加载相应的 html结构、JavaScript交互 和CSS样式。一旦页面加载完成,根据url地址栏的不同,来实现对应的路由组件的切换,整个页⾯是没有进⾏刷新的,只是组件与组件之间的卸载与装载的过程。
- 原理:根据url地址栏的不同,来实现对应的路由组件的切换,整个页⾯是没有进⾏刷新的,只是组件与组件之间的卸载与挂载的过程。
- 整个应用只有一个完整的页面, 所用的功能在一个html上实现。
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
- 数据需要通过 ajax 请求获取。
VueRouter 路由
-
路径和组件的映射关系;
-
hash地址与组件之间的对应关系;
-
hash地址是#后面的内容(包括#);
一个路由(route)就是一组映射关系(key - value),路径和组件的展示关系;多个路由需要路由器(router)进行管理。
只有一个路由器router,每个组件都有的路由route; 全局有$router对象;且只有一个。
vue-router是基于路由和组件的;
路由用于设定访问路径,将路径和组件映射起来;
在vue-router的单页面应用中,页面的路径的改变就是组件的切换;
- 后端路由:key是路径, value是函数, 用于处理客户端提交的请求。服务器接收到一个请求时, 根据请求路径找到匹配的函数,来处理请求, 返回响应数据。
- 前端路由:key是路径,value是组件。当浏览器的路径改变时, 对应的组件就会显示。
路由组件通常存放在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' //使用VueRouterVue.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的自带两个高亮类名:
路由的自定义匹配类名:
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: { //是componentsdefault: 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/helloparams读取参数, 借助$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,}})}},
keep-alive 内置组件
缓存组件,不被销毁;
但被缓存的组件, 再次进入或离开不再执行组件的生命周期;
路由的跳转就是组件的销毁与加载;
作用:让不展示的组件保持挂载,不被销毁。
组件名 => 组件的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;
- to 将要访问的路由对象;
- from 离开的路由对象;
- next 调用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值