Vue入门到精通
带你谈谈Vue的细节
文章目录
- 1. Vue模板语法2大类:
- 2. Vue中有2种数据绑定的方式:
- 3.理解MvvM模型
- 4. Object.defineProperty数据代理
- 5.Vue中的数据代理
- 6. 事件的基本使用:
- 7.Vue中事件修饰符:
- 8.计算属性
- 9. 监视属性watch
- 10.watch和computed区别
- 11.绑定样式
- 12.条件渲染
- 13.react、vue中的key有什么作用?(key的内部原理)
- 14. Vue监视数据的原理
- 15.收集表单数据:
- 16.过滤器
- 17.指令
- 18.自定义指令
- 19.生命周期:
- 20.Vue中使用组件的三大步骤:
- 21.关于VueComponent:
- 22.Vue和VueComponent关系
- 23.脚手架文件结构:
- 24.关于不同版本的Vue:
- 25.vue.config.js配置文件
- 26.ref属性
- 27.配置项props
- 28.mixin(混入)
- 29.插件
- 30.scoped样式
- 31.总结TOdoList案例
- 32.组件的自定义事件
- 33全局事件总线(GlobalEvnetBus)
- 34消息订阅与发布(pubsub)
- 35nextTick(钩子函数)
- 36动画
- 37Vue封装的过渡与动画
- 38vue脚手架配置代理
- 39插槽
- 40Vuex
- 41路由
- 42.编程式路由导航
- 43.缓存路由组件
- 44.路由里面的两个钩子函数
- 45.路由守卫
- 46.路由器的两种工作模式
- 总结
1. Vue模板语法2大类:
1.插值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js的表达式,且可以直接读取到data中所有属性
2.指令语法:
功能:用于解析标签(包括:标签属性,标签内容,绑定事件....)
举例:v-bind:href='xxx'或 简写:href='xxx',xxx同样要写js表达式。
且可以直接读取到data中所有属性
备注:Vue中有很多的指令,且形式都是:v-????,此处我们只是那v-bind举例
2. Vue中有2种数据绑定的方式:
1.单向绑定(v-bind):数据只能从data流向页面
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
备注:
1.双向绑定一般都应用在表单元素上(如:input,select等)
2.v-model:value可以简写成v-model,因为v-model默认收集的就是value值
3.理解MvvM模型
1.mvvm模型
1.M:模型(model):data中数据
2.v:视图(view):模块代码
3.vm:视图模型(viewModel):vue实例
2.观察发型:
1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性 及 vue原型上所有属性在Vue模板中都可以直接使用。
4. Object.defineProperty数据代理
let number = 20;
let person = {
name: '张三',
sex: '男',
}
Object.defineProperty(person, 'age', {
//value:19,
//enumerable:true,//控制属性是否可以枚举默认值是false
//writable:true,//控制属性是否可以被修改,默认值是false
//configurable:true//控制属性是否可以被删除,默认值是false
//当有人读取Person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
return number;
},
//当有人修改person.age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
number = value
}
})
5.Vue中的数据代理
数据处理
1.vue中数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.vue中数据代理的好处:
更加方便的操作data中数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性
6. 事件的基本使用:
1.使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不需要箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象;
5.@click="demo"和@click="demo($event)"效果一直,但后者可以传递参数
6.@click="yyyy",绑定事件的时候可以写一些简单的语句
7.Vue中事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式
5.self:只有event.target是当前操作的元素时才触发事件
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
可以连着写,例如@click.stop.prvent
8.计算属性
1.定义:要用的属性不存在,要通过已有属性计算得来
2.原理:底层借助了Objcet.defineperoperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
methods设计为计算属性,当双向绑定数据发生改变,使用methods设计的计算属性也会被触发(效率很低)
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
6.计算属性只有读取不修改可以简写
//完整写法
computed: {
fullName: {
get() {
return this.firstName.substring(0, 3) + "-" + this.lastName
},
set(value) {
let arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
//简写
computed: {
fullName(){
return this.firstName.substring(0, 3) + "-" + this.lastName
}
}
9. 监视属性watch
1.当被监视的属性发生变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视
3.监视的两种写法
immediate:true,//初始化时让handler调用一下
(1).new Vue时传入watch配置
(2).通过vm.$watch监视
4.深度监视:
(1).VUe中的watch默认不监视对象内部值的改变(一层)。
(2).配置deep:true可以监视对象内部值改变(多层)
备注:
(1).Vue自身可以监视对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时候根据数据的具体结构,决定是否采用深度监视。
watch: {
'number.a': {
immediate: true,//初始
handler(newValue, oldValue) {
console.log("多层监视a", newValue, oldValue);
}
},
number: {
immediate: true,
deep: true,//深度监视
handler(newValue, oldValue) {
console.log("值修改", newValue, oldValue);
}
}
}
5.简写,在不需要使用deep和immediate可以简写
watch:{
number(newValue, oldValue) {
console.log("监听number", newValue, oldValue);
}
}
vm.$watch('number2', function(newValue, oldValue) {
console.log("监听number2", newValue, oldValue);
})
10.watch和computed区别
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,列如:watch可以进行异步操作
两个重要的小原则:
1.所被Vue给管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
2.所有不被Vue所管理的函数(定时器的函数调用,ajax的回调函数等,Promise的回调函数)最好写成箭头函数.这样this指向的才是vm或实例对象
11.绑定样式
1.class样式
写法:class样式="xxx"xxx可以是字符串,对象,数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适合用于:要绑定多个样式,个数确定,名字也确定
数组写法使用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
2.style样式
:style="{fontSize:xxx}"其中xxx是动态值。
:style="[a,b]"其中a,b是样式 对象
12.条件渲染
1.v-if
写法:
(1).v-if="表达式"
(2).v-else-if="表达式"
(3).v-else="表达式"
适用于:切换频率比较低的场景。
特点:不展示的DOM元素直接被移除
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if元素的时,元素可能获取不到,而使用v-show元素一定能获取到(因为v-if为false时直接移除DOM元素,这个时候获取不到元素而v-show为false还是可以获取到元素)
4.如果使用v-if、if-show破坏了元素结构可以使用template避免
13.react、vue中的key有什么作用?(key的内部原理)
1.虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟dom】与【旧虚拟dom】的差异 比较规则如下:
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟dom相同的key:
1.虚拟dom中内容没有变化,直接使用之前的真实dom!
2.若虚拟dom发生了变化,则生成新的真实dom随后替换页面中之前的真实dom
(2). 旧虚拟dom中未找到与新虚拟dom相同的key
创建新的真实dom,随后渲染到页面
3.用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实dom更新==》界面效果没问题,但效率底。
2.如果结构中还包含输入类的dom:
会产生错误DOM更新==》界面有问题
4.开发中如果选择key?
1.最好使用每条数据的唯一标识key,比如id,手机号,身份证号,学号 唯一值。
2.如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示。使用index作为key是没有问题的
14. 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()
unshift()添加头部元素
push()添加尾部元素
pop()删除尾部元素
shift()删除头部元素
splice(索引下标,删除个数,插入元素)修改指定位置元素
splice(索引下标,1)删除指定位置元素
sort(a,b)排序 列如:arr.sort((a, b) => this.type === 1 ? a.age - b.age : b.age - a.age)
reverse()方法用于颠倒排列数组元素
2.Vue.set()或vm.$set()
特别注意:Vue.set()和vm.$set()不能给vm或vm根数据对象 添加属性!!!
(5).filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的
15.收集表单数据:
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
若:<input type="checkbox"/>
1.没有配置input的value属性,那个收集的就是checked(勾选or未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那个收集的就是checked(勾选or未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的就是value组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
16.过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
1.注册过滤器:Vue.filter(name,callback) 或new Vue{filters:{}}
2.使用过滤器:{{xxx|过滤器名}}或v-bind:属性="xxx|过滤器名"
备注:
1.过滤器也可以按照额外参数,多尔衮过滤器也可以串取
2.并没有改变原本的数据,是产品新的对应的数据
17.指令
v-text指令
1.作用:向其所在的节点中渲染文本内容
2.与插值语法的区别:v-text会替换掉节点的内容,{{xxx}}则不会
v-html指令
1.作用:向指定节点中渲染包含html结构的内容
2.与插值语法的区别:
(1).v-hmtl会替换掉节点中所有的内容,{{xx}}则不会
(2).v-html可以识别html结构
3.严重注意:v-html有安全问题!!
(1).在网站上动态渲染任意html是非常危险的,容易导致xss攻击
(2).一定要在可信的内容上使用v-html,永不要在用户提交的内容上
v-cloak指令(没有值)
1.本质是一个特殊属性,Vue实例创建完毕,并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
[v-cloak] {
display: none;
}
v-once指令:
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
1.跳过其所在节点的编译过程
2.可以利用它跳过:没有使用指令语法、没有使用差值语法的节点,会加快编译
18.自定义指令
1.指令与元素成功绑定时(一上来)。2.指令所在的模板会重新解析时
一、定义语法:
(1).局部指令:
new Vue({directives:{指令名:配置对象}})或 new Vue({directives:{指令名:回调函数}})
(2).全局指令:
Vue.directive(指令名,配置对象)或 Vue.directive(指令名,回调函数)
(3).directives对象中的this指向是window不是vue对象
二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用
(3).update:指令所在模板结构被重新解析时调用。
三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指定名如果是多个单词,要使用kebab-case命名方式,不要使用camelCase命令
19.生命周期:
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可以更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this指向是vm或组件实例对象。
vm的一生(vm的生命周期):
将要创建===》调用beforeCreate函数
创建完毕===》调用created函数
将要挂载===》调用beforeMount函数
(重要)挂载完毕===》调用mounted函数=======》【重要的钩子】
将要更新===》调用beforeUpdate函数
更新完毕===》调用update函数
(重要)将要销毁===》调用beforeDestroy函数=======》【重要的钩子】
销毁完毕===》调用destoryed函数
常用的生命周期钩子:
1.mounted:发送ajax请求、启动定时器、绑定自定义事件,订阅消息等【初始化操作】
2.beforeDestory:清楚定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息
2.销毁后自定义事件会失效、但原生DOM事件依然有效
3.一般不会在beforeDestory操作数据,因为即便操作数据,也不会再触发更新流程了
20.Vue中使用组件的三大步骤:
1、定义组件(创建组件)
2、注册组件
3、使用组件(写组件标签)
一、如何定义一个组件?
使用Vue.extend(options)创建,启用options和new Vue(options)时传入的那个options几乎一样,但也有点区别:
区别如下:
1.el不要写,为什么?—————最终所有的组件都要经过vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么?————避免组件被复用时,数据存在引用关系。
注意:使用template可以配置组件结构。
二、如何注册组件?
1.局部注册:靠new Vue的时候传入components选择
2.全局注册:靠Vue.component('组件名',组件)
三、几个注意点
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
21.关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义,是Vue.extend生成的。
2.我们只需写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
既Vue帮我们执行:new VueComponent(options)
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!
4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、computed中的函数、watch中的函数 它们的this均是【VueComponent实例对象】
(2).new Vue(options)配置中:
data、methods、computed、watch中的函数它们的this均指向【Vue实例】
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
Vue的实例对象,以后简称vm
22.Vue和VueComponent关系
1.一个重要的内置关系:VueComponent.prototype.__proto__==Vue.prototype
2.为什么要有这个关系:让组件实例对象VueComponent可以访问到Vue原型上的属性、方法
23.脚手架文件结构:
node_moudels
public
favicon.ico:页签图标
index.html:主页面
src
assets:存放静态资源
logo.png
component:存放组件
HelloWord.vue
App.vue:汇总组件
main.js:入口文件
.gitignore:git版本管理忽略的配置
babel.config.js:babel的配置文件
package.json:应用包配置文件
README.md:应用描述文件
package-lock.json:包版本控制文件
24.关于不同版本的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函数去指定具体内容
25.vue.config.js配置文件
使用vue inspect>output.js可以查看都Vue脚手架的默认配置。
使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh/config
26.ref属性
1.被用来给元素或子元素注册引用信息(id的替代者)
2.应用在html标签上获取真实的dom,应用在组件标签上是组件实例对象(vc)
3.使用方式:
打标识:<h1 ref="xxx">......</h1>或<School ref="xxx"></School>
获取:this.refs.xxx
27.配置项props
功能:让组件接受外部传过来的数据
(1).传递数据:
<Demo name="xxx"/>
(2).接收数据:
第一种方式(只接受)
props:['name']
第二种方式(限制类型):
props:{
name:Number
}
第三种方式(限制类型、限制必要性、指定默认值);
props:{
name:{
type:String,//类型
required:true,//必要性
default:'老王'//默认值
}
}
(3).备注
props是只读的,Vue底层会检测你对props的修改,如果进行了修改,就会发出警告,若业务需求确定需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
28.mixin(混入)
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合,列如:
{
data(){
....
},
methods:{...}
.....
}
第二步使用混入,列如:
(1).全局混入:Vue.mixin(xxx)
(2).局部混入:mixins:['xxx']
29.插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
对象.install=function(Vue,options){
//1.添加的全局过滤器
Vue.filter(...)
//2.添加全局指令
Vue.directive(...)
//3.配置全局混入(合)
Vue.mixin(...)
//4.添加实例方法
Vue.prototype.$myMethod=function(){...}
Vue.prototype.$myProperty=xxx
}
使用插件:Vue.use()
30.scoped样式
作用:让样式在局部生效,防止冲突
写法:<style scoped>
31.总结TOdoList案例
1.组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件再用:
1).一个组件在用:放在组件自身即可。
2).一些组件在用:放在他们共同的父组件上(``状态提升``)
(3).实现交互:从绑定事件开始。
2.props适用于:
(1).父组件===>子组件 通信
(2)。子组件==>父组件 通信(要求父组件先给子组件一个函数)
3.使用v-model时要记住:v-mdeol绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做
32.组件的自定义事件
1.一种组件间通信的方式,适用于:**子组件===》父组件**
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么久要在A中给B绑定自定义事件(事件的回调在A中
)
3.绑定自定义事件:
1.第一种方式,在父组件中:<Demo @hahah="test"/>
或<Demo v-on:hahah="test"/>
2.第二种方式,在父组件中
<Demo ref="demo"/>
.....
mounted(){
this.#refs.xxx.$on('haha',this.test)
}
3.若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法。
4.触发自定义事件:this.$emit('hahha',数据)
5.解绑自定义事件:this.$off('haha')
,解绑多个:this.$off(['haha'])
,全部解绑:this.$off()
6.组件上可以绑定原生DOM事件,需要使用native
修饰符。
7.注意:通过this.$refs.xxx.$on('haha',回调)绑定自定义事件,回调
要么配置在methods中,要么用箭头函数``,否则this指向会出问题
33全局事件总线(GlobalEvnetBus)
1.一种组件通信的方式,适用于任何组件间通信。
2.安装全局事件总线:
new Vue({
......
beforeCreate(){
Vue.prototype.$bus=this//安装全局事件总线,$bus就是当前应用的vm
}
})
3.使用事件总线:
1.接受数据:A组件想接受数据,则在A组件中给$bus
绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){
.....
}
}
....
mounted(){
this.$bus.$on('xxx',this.demo)
}
....
beofreDestory(){
this.$bus.off('xxx')
}
2.提供数据:this.$bus.$emit(‘xxxx’,‘数据’)
4.最好在beforeDestory钩子中,用$off去解绑`当前组件所用到的事件
34消息订阅与发布(pubsub)
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('show', (subName, name) => {
this.name = name;
console.log(name + ",替换成功," + subName);
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pid)//取消订阅
}
4.提供数据:pubsub.publish(‘xxx’,数据)
5.最好在beforDestroy钩子中,用 pubsub.unsubscribe(this.pid)
去取消订阅
35nextTick(钩子函数)
1.语法:this.$nextTick(回调函数)
2.作用:在下一次DOM更新结束后执行其指定代码的回调
3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
36动画
属性 | 描述 |
---|---|
transition | 简写属性,用于将四个过渡属性设置为单一属性。 |
transition-delay | 规定过渡效果的延迟(以秒计)。 |
transition-duration | 规定过渡效果要持续多少秒或毫秒。 |
transition-property | 规定过渡效果所针对的 CSS 属性的名称。 |
transition-timing-function | 规定过渡效果的速度曲线。**** |
37Vue封装的过渡与动画
1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
2.图片
3.写法
1.准备好样式:
元素进入的样式:
1.v-enter:进入的起点
2.v-enter-active:进入过程
3.v-enter-to:进入终点
离开样式:
1.v-leave:离开起点
2.v-leave-active:离开过程
3.v-leave-to:离开终点
2.使用<transition>
包裹要过渡的元素,并配置name属性:
<transition name="hello" appear>
<h1 v-show="isShow">你好啊</h1>
</transition>
3.备注:若多个元素需要过渡,则使用:<transition-group>
,且每一个元素都要指定key
值。appear初次加载触发
4.进入离开触发条件
<Transition>
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
<component>
切换的动态组件 - 改变特殊的
key
属性
38vue脚手架配置代理
方法一
在vue.config.js中添加如下配置:
devServer:{
proxy:'http://localhsot:5000'
}
说明:
1.优点:配置简单,请求资源时直接发给前端 (8080)即可。
2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理
3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
proxy: {
'/api': {//匹配所有以,'/api'开头的请求路径
target: 'http://localhost:9999',//代理目标的基础路径
changeOrigin: true,
pathRewrite:{'^/api':''} //把请求的路径中/api替换成''
}
}
}
})
/**
changeOrigin设置为true时候,服务器收到的请求头中的host为:localhsot:9999
changeOrigin设置为false时候,服务器收到的请求头中的host为:前端运行项目的端口ip(8080),
默认changeOrigin为true
说明:
1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理
2.缺点:配置略微繁琐,请求资源时必须加前缀
39插槽
1.作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通讯的方式,适用于父组件—》子组件。
2.分类:默认插槽,具名插槽,作用域插槽
3.使用方式:
1.默认插槽:
<!-- 子插槽 -->
<template>
<div class="container">
<h4>{{ titel }}</h4>
<!-- 定义插槽 -->
<slot>这是一个默认插槽</slot>
</div>
</template>
<!-- 父插槽 -->
<categrop-vue titel="美食分类">
<img src="favicon.ico" alt="">
</categrop-vue>
2.具名插槽
<!-- 子插槽 -->
<div class="container">
<h4>{{ titel }}</h4>
<slot>这是一个默认插槽</slot>
<!-- 具名插槽-->
<slot name="footer">这是一个name插槽</slot>
</div>
<!-- 父插槽 -->
<categrop-vue titel="游戏分类">
<ul style="list-style: none;">
<li v-for="item in game">{{ item }}</li>
</ul>
<div class="conter" slot="footer">
<a href="">单机游戏</a>
<a href="">网络游戏</a>
</div>
</categrop-vue>
3.作用域插槽
1.l理解:数据在组件自身,但根据数据生成的结构需要用组件的使用者来决定。(games数据在Categrop组件中,但使用数据所遍历出来的结构由App自身决定;。
<!-- 子插槽 -->
<div class="container">
<h4>{{ titel }}</h4>
<!-- 要将指定数据传递给父组件 -->
<slot :game="game">这是一个默认插槽</slot>
<slot name="footer">这是一个name插槽</slot>
</div>
<!-- 父插槽 -->
<categrop-vue titel="美食分类">
<div class="conter" slot="footer">
<a href="">更多美食</a>
</div>
<!-- 获取子组件数据方式1 -->
<template scope="{game}">
<img src="favicon.ico" alt="">
<ul style="list-style: none;">
<li v-for="item in game">{{ item }}</li>
</ul>
<!-- 父元素数据传入子插槽 多个子组件共用同一个父元素数据-->
<ul style="list-style: none;">
<li v-for="item in movie">{{ item }}</li>
</ul>
<button @click="movie.shift()">删除父元素</button>
</template>
</categrop-vue>
<categrop-vue titel="游戏分类">
<!-- 获取子组件数据方式2 -->
<template slot-scope="{game}">
<!-- 子元素数据在子插槽 作用域为当前-->
<ul style="list-style: none;">
<li v-for="item in game">{{ item }}</li>
</ul>
<button @click="game.shift()">删除子元素</button>
<!-- 父元素数据传入子插槽 多个子组件共用同一个父元素数据-->
<ul style="list-style: none;">
<li v-for="item in movie">{{ item }}</li>
</ul>
<button @click="movie.shift()">删除父元素</button>
</template>
<div class="conter" slot="footer">
<a href="">单机游戏</a>
<a href="">网络游戏</a>
</div>
</categrop-vue>
40Vuex
1.概念
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
2.安装搭建
npm install vuex --save
import Vue from "vue"
import vuex from "vuex"
//为啥vuex要注册到vue才可以使用vuex因为vuex的底层用到了vue
Vue.use(vuex)
//保存具体数据
const state = {
}
//支持异步发送 响应组件中用户的动作
const actions = {
}
//修改state中的数据
const mutations = {
}
//创建并暴露Store
export default new vuex.Store({
state,
actions,
mutations
})
main文件中:
// 该文件是整个项目的入口文件
//引入vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
import store from './store'
//关闭vue的产生提示
Vue.config.productionTip = false
//注册插件
new Vue({
store,
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this;
}
}).$mount('#app')
3.基本使用
1.初次化数据、配置actions
、配置mutations,操作文件store.js
import Vue from "vue"
import vuex from "vuex"
//为啥vuex要注册到vue才可以使用vuex因为vuex的底层用到了vue
Vue.use(vuex)
const state = {
number: 100
}
//支持异步发送
const actions = {
incrementOdd(context, value) {
if (value % 2 === 0) {
context.commit('increment', value)
}
},
incrementWait(context, value) {
setTimeout(() => {
context.commit('incrementWait', value)
}, 2000);
}
}
const mutations = {
increment(state, value) {
state.number += value;
},
decrement(state, value) {
state.number -= value;
},
incrementOdd(state, value) {
state.number += value;
},
incrementWait(state, value) {
state.number += value;
}
}
export default new vuex.Store({
state,
actions,
mutations
})
2.组件中读取vuex中的数据:$store.state.sum
3.组件中修改vuex的数据:$store.dispatch(‘action中的方法名’数据)
或$store.comit(‘mutations中的方法名’,数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,既不写dispatch
,直接编译commit
4.getters的使用
1.概念:当store中的数据需要经过加工后在使用时,可以使用getters加工。
2.在store.js
中追加getters
配置
//对state中数据进行加工,像极了computed监听data
const getters = {
number(state) {
return state.number * 10;
}
}
export default new vuex.Store({
state,
actions,
mutations,
getters
})
3.组件中读取数据:$store.getters.number
5.四个map方法使用
1.mapState方法:用于帮助我们映射State中的数据为计算属性,既简化了:$store.state
computed: {
//借助mapState生成计算属性:number(对象写法)
// ...mapState({ number: 'number' }),
//借助mapState生成计算属性:number(数组写法)
...mapState(['number'])
},
2.mapGetters方法:用于帮助我们映射getters
中的计算属性,帮我们省略了:$store.getters
computed: {
//借助mapGetters生成计算属性:getNumber(对象写法)
// ...mapGetters({ getNumber: 'getNumber' })
//借助mapGetters生成计算属性:getNumber(数组写法)
...mapGetters(['getNumber'])
}
3.mapActions方法:用于帮助我们生成与actions对话的方法:既:包含$store.dispatch(xxx)
的函数
methods: {
//靠mapActions生成:incrementWait,incrementOdd(对象形式)
// ...mapActions({ incrementWait: 'incrementWait', incrementOdd: 'incrementOdd' }),
//靠mapActions生成:incrementWait,incrementOdd(数组形式)
...mapActions(['incrementWait', 'incrementOdd']),
}
4.mapMutations方法:用于帮我们生成mutations
对话的方式,即:包含$store.commit(xxx)
函数
methods:{
//靠mapMutations生成:increment,decrement(对象形式)
// ...mapMutations({ increment: 'increment', decrement: 'decrement' }),
//靠mapMutations生成:increment,increment(数组形式)
...mapMutations(['increment', 'decrement'])
}
备注:mapMutations、mapActions使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象``
6.模块化+命名空间
1.目的:让代码更好维护,让多种数据分享更加明确
2.修改store.js
import Vue from "vue"
import vuex from "vuex"
import axios from 'axios'
//为啥vuex要注册到vue才可以使用vuex因为vuex的底层用到了vue
Vue.use(vuex)
const ShopList = {
//开启命名空间
namespaced: true,
actions: {
items(context, value) {
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
rep => {
value.name = rep.data;
context.commit('items', value)
},
err => {
console.log(err)
}
)
}
},
mutations: {
items(state, value) {
state.items.push(value)
}
},
state: {
items: [
{ id: '001', name: '张三1' },
{ id: '002', name: '张三2' },
{ id: '003', name: '张三3' },
{ id: '004', name: '张三4' }
]
},
getters: {
}
}
const Count = {
//开启命名空间
namespaced: true,
actions: {
incrementOdd(context, value) {
if (context.state.number % 2 === 0) {
context.commit('increment', value)
}
},
incrementWait(context, value) {
setTimeout(() => {
context.commit('incrementWait', value)
}, 2000);
}
},
mutations: {
increment(state, value) {
state.number += value;
},
decrement(state, value) {
state.number -= value;
},
incrementOdd(state, value) {
state.number += value;
},
incrementWait(state, value) {
state.number += value;
}
},
state: {
number: 100
},
getters: {
getNumber(state) {
return state.number * 10;
}
}
}
export default new vuex.Store({
modules: {
ShopList,
Count
}
})
3.开启命名空间后,组件中读取state数据:
//方式一:直接读取
this.$store.state.ShopList.items
//方式二:借助mapState读取:
...mapState('ShopList',['items'])
4.开启命名空间后,组件中读取getters数据:
//方式一:直接读取
this.$store.getters.Count.getNumber
//方式二:借助mapGetters读取:
...mapGetters('Count',['getNumber'])
5.开启命名空间后,组件中调用dispatch
//方式一:直接读取
this.$store.dispatch('Count/incrementOdd',value)
//方式二:借助mapGetters读取:
...mapActions('Count',['incrementOdd','incrementWait'])
6.开启命名空间后,组件中调用commit
//方式一:直接读取
this.$store.commit('Count/increment',value)
//方式二:借助mapGetters读取:
...mapMutations('Count',['increment','decrement'])
41路由
1.基本使用
1.安装vue-router,命令npm i vue-router
2.应用插件Vue.use(VueRouter)
3.编写router配置项
//改文件专门用于创建整个程序的路由器
import VueRouter from "vue-router";
//引入组件
import About from '../components/About.vue'
import Home from '../components/Home.vue'
//创建router实例对象,去管理一组一组的路由规则
export default new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
}
]
})
//暴露router
4.实现切换(active-class可配置高亮切换样式)
<router-link to="/about" active-class="active">关于</router-link>
5.指定展示位置
<RouterView></RouterView>
2.几个注意点
1.路由组件通常存放在pages
文件夹,一般组件通常存放在components
文件夹
2.通过切换,”隐藏“了的路由组件,默认是被销毁的,需要的时候在去挂载。
3.每个组件都有自己的route
属性,里面存储着自己的路由信息
4.整个应用只有一个roter,可以通过组件的$router
属性获取。
3.多级路由
1.配置路由规则,使用children配置项:
//创建一个路由器
export default new VueRouter({
routes: [
{
path: '/about',
component: About,
children: [//通过children配置子级路由
{
path: 'demo1',//此处一定不要写:/demo1
component: Demo1
},
{
path: 'demo2',//此处一定不要写:/demo2
component: Demo2
}
]
},
{
path: '/home',
component: Home
}
]
})
2.跳转(要写完整路径):
<router-link to="/about/demo1">demo1</router-link>
4.路由的query参数
1.传递参数
<o1>
<!-- 跳转并携带参数query参数,to的字符串写法 -->
<li v-for="item in list" :key="item.id">
<router-link :to="`/home/Detail?id=${item.id}&name=${item.name}`">{{ item.name }}</router-link>
</li>
<!-- 传递并携带参数对象写法 -->
<li v-for="item in list" :key="item.id">
<router-link :to="{
path: '/home/Detail',
query: {
id: item.id,
name: item.name
}
}">{{ item.name }}</router-link>
</li>
</o1>
<router-view></router-view>
2.接受参数:
$route.query.id
$route.query.title
5.命名路由
1.作用:可以简化路由的跳转(name)
//创建一个路由器
export default new VueRouter({
routes: [
{
//起一个路由名字
name: 'about',
path: '/about',
component: About,
children: [//通过children配置子级路由
{
name: 'demo1',
path: 'demo1',//此处一定不要写:/demo1
component: Demo1
},
{
//起一个路由名字
name: 'demo2',
path: 'demo2',//此处一定不要写:/demo2
component: Demo2
}
]
},
2.简化跳转:
<!-- 简化前 -->
<router-link to="/about/demo1">demo1</router-link>
<!-- 简化后,直接通过name跳转 -->
<router-link :to="{ name: 'demo1' }">demo1</router-link>
<!-- 简化版,配合传参 -->
<router-link :to="{
name: 'hello', query: {
id: '666',
titel: '张三'
}
}">demo1</router-link>
6.路由的params参数
1.配置路由,声明接收params参数
export default new VueRouter({
routes: [
{
name: 'about',
path: '/about',
component: About,
children: [//通过children配置子级路由
{
name: 'demo1',
path: 'demo1',//此处一定不要写:/demo1
component: Demo1
},
{
name: 'demo2',
path: 'demo2',//此处一定不要写:/demo2
component: Demo2
}
]
},
{
name: 'home',
path: '/home',
component: Home,
children: [//通过children配置子级路由
{
name: 'Detail',
path: 'Detail/:id/:titel',//使用占位符声明接受params参数
component: Detail
}
]
}
]
})
2.传递参数
<o1>
<!-- 跳转并携带参数query参数,to的字符串写法 -->
<li v-for="item in list" :key="item.id">
<router-link :to="`/home/Detail/${item.id}/${item.name}`">{{ item.name }}</router-link>
</li>
<!-- 传递并携带参数对象写法
路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!-->
<li v-for="item in list" :key="item.id">
<router-link :to="{
name: 'Detail',
params: {
id: item.id,
name: item.name
}
}">{{ item.name }}</router-link>
</li>
</o1>
<router-view></router-view>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
3.接收参数
$route.params.id
$route.params.name
7.路由的props配置
作用:让路由组件更方便收到参数
{
name: 'home',
path: '/home',
component: Home,
children: [
//通过children配置子级路由
{
name: 'detail',
path: 'detail',//使用占位符声明接受params参数
component: Detail,
//第一种写法,props值为对象,该对象中所有的key-value的组合最终都会通过props传递给Detail组件
// props: { a: '张三', b: '李四' }
// 第二种写法,值为布尔,若为真,就会把该路由组件收到的所有params参数,以props的形式给Detail组件
// props: true
// 第三种写法,值为函数
props($route) {
return { id: $route.query.id, titel: $route.query.titel }
}
//es6
// props({ query: { id, titel } }) {
// return { id, titel }
// }
}
]
}
8.的replace属性
1.作用:控制路由跳转时操作浏览器历史记录的模式,底层是通过队列实现
2.浏览器的历史记录有两种写入方式:分别为push
和replace
,push是追加历史记录,replace是替换当前记录,路由跳转时候默认为push
3.如何开启replace
模式:<router-link replace……>News</router-link>
42.编程式路由导航
1.作用:不借助<router-link>
实现路由跳转,让路由跳转更加灵活
2.具体编码:
//跳转路由两个api 底层是promis实现的
toRouter(m) {
this.$router.push({
name: 'detail',
params: {
id: m.id,
name: m.name
}
}).catch(err => { err })
},
toReplace(m) {
this.$router.replace({
name: 'detail',
params: {
id: m.id,
name: m.name
}
}).catch(err => { err })
}
this.$router.forward();//前进
this.$router.back();//后退
this.$router.go(1);//可前进也可后退
43.缓存路由组件
1.作用:让不展示的路由组件保持挂载,不被销毁。include=“组件名”
2.具体编码:
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
//缓存多个
<keep-alive :include="['News','New2">
<router-view></router-view>
</keep-alive>
44.路由里面的两个钩子函数
1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2.具体名字:
1.activated
路由组件被激活时触发
2.deactivated
路由组件失活时触发
45.路由守卫
1.作用:对路由进行权限控制
2.分类:全局守卫、独享守卫、组件守卫
3.全局守卫
//全局前置路由;初始化执行、每次路由切换前执行
router.beforeEach((to, form, next) => {
console.log(to, from,"@before");
if (to.meta.isAuth === true) {
if (localStorage.getItem('user') === 'admin') {
next()
} else {
alert("权限不够")
}
} else {
next()
}
})
//全局后置守卫,初始化时执行,每次路由切换后执行
router.afterEach((to, from) => {
console.log(to, from,"@after");
document.title = to.meta.titel || '优品汇'//修改网页titel
})
4.独享路由守卫(可以配合全局后置路由守卫用)
children: [
//通过children配置子级路由
{
name: 'detail',
path: 'detail',//使用占位符声明接受params参数
component: Detail,
meta: {
isAuth: true,
titel: '契合'
},
//独享路由守卫
beforeEnter(to, form, next) {
console.log(to)
console.log(form);
if (to.meta.isAuth === true) {//判断当前路由是否需要权限控制
if (localStorage.getItem('user') === 'admin') {
next()
} else {
alert("权限不够")
}
} else {
next()
}
console.log("===============================")
}
}
]
5.组件内守卫
//进入守卫,通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next) {
console.log("进入守卫路由beforeRouteEnter");
next();
},
//离开守卫,通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {
console.log("进入守卫路由beforeRouteEnter");
next();
}
46.路由器的两种工作模式
1.对一个url来说,什么是hash值?——#及其后面的内容就是hash值
2.hash值不会包含在http请求中,既:hash值不会带给服务器
3.hash模式
1.地址中永远带着#号,不美观
2.若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合格。
3.兼容性较好
4.history模式:
1.地址干净,美观
2.兼容性和hash模式的相比略差
3.应用部署上线时需要后端人员支持,解决刷新页面服务器404的问题
5.abstract模式:
隐藏路径
总结
学习从来没捷径,循序渐进登高峰