目录
VUEX
什么是vuex
Vuex:是vue的状态管理工具,就是管理公共数据的,这个数据在任何组件都能使用
vuex的持久化数据
安装插件
npm i vuex-persistedstate --save
在store/index.js 中引入
import vuexPersist from "vuex-persistedstate"
配置
plugins: [ new vuexPersist({ storage: window.localStorage, }).plugin, ],
vuex的五大核心
state | 状态 存放数据,相当于data |
mutations | 修改state的地方,而且只有它有权利修改state |
getters | 相当于计算属性 |
actions | 在vuex中执行异步操作的地方 |
modules | 模块 当state比较多的时候进行分模块管理 |
state的使用 this.$store.state.xxx
建议使用计算属性,这样在数据改变时会自动更新
mutations的使用 this.$store.commit ( ‘事件名’ , 传的参数)
mutations 里的事件有两个参数,第一个必须是 state ,就是vuex的state, 第二个参数是传的值
actions的使用
里面的每个事件都有一个 context 参数,是一个关联 vuex 上下文的实例
可以在 vue 页面里通过 this.$store.dispath('事件名') 提交 actions
vuex的辅助函数
辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的
都有什么辅助函数
mapState mapActions mapMutations mapGetters
在vue页面引入辅助函数
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
注意事项:
mapState与mapGetters 推荐在计算属性里 使用, 可以直接做data数据使用
computed: {
...mapState(["shopData"]),
},
其他辅助函数在 methods 里
methods: {
...mapActions(["getData"]),
...mapMutations(["add"]),
}
vue-router
前端路由原理
路由就是用来解析URL以及调用对应的控制器,在不重新请求页面的情况下,更新页面的视图
路由模式
路由分为hash模式与history模式
hash与history的切换
在router的index.js文件中,有一个new Router(),里面的mode默认为hash模式,可以在这里更改为history模式
const router = new VueRouter({
routes,
mode:'hash' //可在这里更改为history模式
})
hash模式
hash模式:在浏览器中符号的“#”,以及#后面的字符称之为hash
http://localhost:8080/#/login
特点
1 hash不会被包含在http请求中,因此,改变hash不会重新加载页面
2 hashchange 用来监听hash改变的事件
3 每一次改变,都会在浏览器中添加一个历史记录
方法
HashHistory.push 跳转路由,添加历史记录栈
HashHistory.replace() 跳转路由,直接替换当前页面,不会添加历史
history模式
http://localhost:8080/home
特点
1 history模式的URL要与后端的URL一样,后端如果没有对应路由,则会返回404错误
2 popState(window.onpopstate)用来监听history改变的事件
方法
history.pushState() 跳转路由,添加历史记录栈
history.replaceState() 跳转路由,直接替换当前页面,不会添加历史
路由传参
query,params,动态路由传参
(1) params只能使用name query可以使用name和path
(2) 使用params传参刷新后丢失,而query传参刷新后不会丢失
(3) params在地址栏中不会显示,query会显示
(4) params可以和动态路由一起使用,query不可以
// query 传参
this.$router.push({ path:'/user', query:{ userId:5 } })
// params 传参
this.$router.push({ name:'user', params:{ userId:5 } })
// 动态路由传参
path: 'config/:lawCode/salary/:lawVersionCode',
name: 'basicLawSalary',
this.$router.push(`config/${this.lawCode}/salary/${this.lawVersionCode}`)
// 动态路由获取
this.$route.params.lawCode
路由跳转
导航式路由
<router-link to='/'>
编程式路由
router.push( ) | 将有记录存放在历史栈中,点击后退,将返回上一级的记录 router-link to 就是调用的push |
router.replace() | 不会存放历史栈,而是替换了当前的历史 可以在router-link中添加replace或router.push对象中添加replace:true 激活当前功能 |
router.go(-3) | ()里的参数为整数,指的是在历史栈中前进或后退的步数 -1为返回上一历史,0为刷新页面,1为返回下一历史 |
vue的路由守卫
路由守卫分为全局守卫,路由独享守卫,组件内守卫
每个守卫接收三个参数
to 即将要进入的目标路由对象
from 当前导航正要离开的路由
next 钩子函数,里面定义参数,确认下一步路由要做什么
全局守卫
router.beforeEach | 全局前置守卫 最合适用在判断进入的页面是否需要登录 |
router.beforeResolve | 全局解析守卫 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用 |
router.afterEach | 全局后置钩子 一般用来设置动态的请求头 再这里next()是多余的 |
路由独享守卫 beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内守卫
beforeRouteEnter | 进组组件前的守卫 注意:在这里是拿不到vue的this的,但是可以通过传一个回调给 next来访问组件实例 next ( vm=> { console.log(vm) } ) vm就是vue的组件实例 通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消 |
beforeRouteUpdate | 路由更新时的守卫 |
beforeRouteLeave | 离开组件时的守卫 |
完整的导航解析流程
1 在失活的组件里调用 beforeRouteLeave 组件离开守卫。
2 调用全局的 router.beforeEach 全局前置守卫。
3 在重用的组件里调用 beforeRouteUpdate 组件更新守卫 。
4 在路由配置里调用 beforeEnter 路由独享守卫
5 解析异步路由组件。
6 在被激活的组件里调用 beforeRouteEnter 组件进入守卫
7 调用全局的 router.beforeResolve 全局解析守卫 。
8 导航被确认。
9 调用全局的 router.afterEach 全局后置钩子。
10 触发 DOM 更新。
11 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由相关问题
脚手架rem适配
封装api请求
多环境变量
webpack配置
webpack可以进行配置请求跨域,alais别名,打包配置
打包配置包括:更改路径,去掉生产环境的sourceMap资源映射文件,去除console.log打印以及注释,使用CDN 加速优化,对资源文件进行压缩,图片压缩,公共代码抽离,打包分析
vue的双项数据绑定
vue2
结合发布者-订阅者模式,通过Object.defineProperty()来劫持各个属性的setter,getter 在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图,实现数据和视图同步。
具体步骤
第一步: 需要observer(观察者)对数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样赋值时,就会触发setter,监听到了数据变化
第二步: compile(模板解析器)解析模板指令,将模板中的变量替换成数据,初始化渲染页面视图,并将节点绑定更新函数,添加订阅者
第三步: Watcher(订阅者)是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
第四步: MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化,视图变化的同步更新
vue3
利用es6的proxy的set和get方法来实现数据的劫持,进一步结合vue的watcher的update方法来实现双项数据绑定
v3相比于v2的优点
- 直接监听数组类型的数据变化
- 监听的目标为对象本身,不需要遍历每个属性,有一定的性能提升
- 可直接实现对象属性的新增/删除
vue虚拟dom,diff算法
什么是虚拟DOM
虚拟DOM就是用普通的js对象来描述真实DOM结构,这个对象有标签名(tag),属性(attrs),子元素对象(children)三个属性,通过vue的render()函数把虚拟的DOM转换为真实的DOM,在通过appendChild()添加至页面
为什么使用虚拟DOM
创建真实DOM成本比较高,频繁操作dom是一种比较大的开销,用虚拟dom成本比较低
虚拟DOM的性能不一定高于常规DOM,虚拟DOM和Diff算法的出现是为了解决由命令式编程转变为函数式编程、数据驱动后所带来的性能问题的
使用了虚拟DOM只是减少用户操作dom,其实还是会操作真实dom的
使用虚拟DOM更有益于diff算法的对比
vue2的diff算法
- 用 js 对象结构构建一个真正的 DOM树,插到文档当中
- 当状态变更的时候,重新构造一棵新的虚拟DOM。
- 然后用新的虚拟DOM和旧的虚拟DOM进行比较 所有dom都对比,把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了
vue3的diff算法
1 也就是在生成VNode的同时打上标记,在这个基础上再进行核心的diff算法
2 对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用
vue的组件化开发
组件开发的好处
1 减少代码的体积与重复性
2 容易维护,容易修改
3 封装好的组件,容易复用,使用简单
4 一个页面,分组件共同开发,更符合团队合作
如何封装组件
1 组件分为页面级组件与子组件。页面级组件就是view文件夹中的组件
2 新建一个components文件夹,用来放置我们的子组件
3 一个子组件,也是一个vue的文件
4 想要使用这些子组件,需要根据路径引入子组件,同时在components里注册
5 将子组件放置在想要放置的页面的位置中,注意使用驼峰命名法
组件的使用场景
1 头部input的搜索框
2 页面中的相同模块,例如,头部底部的模块,导航条,面包屑,通过插槽修改数据
3 轮播图的封装,返回顶部按钮,阶梯导航等功能
组件的样式穿透
css样式穿透 >>> sass样式穿透 /deep/
组件之间的传值与组件使用插槽
详情请看下面的内容
组件通信
父传子
1 自定义属性传值
在父组件的子组件标签上绑定一个属性,挂载要传输的变量。在子组件中通过props来接受数据
prop
props可以是数组也可以是对象,接受的数据可以直接使用数组接受,也可以使用对象形式来判别数据类型,是否是必传值,与默认值
props:{
name:{
type:string, //数据类型
default:'000',//默认值,只有值是undefined或者不传属性的时候才会生效
required:true, //是否必须传递 }
}
注意:引用数据类型的默认值需要通过一个函数返回才能使用 ,否则vue的逻辑会报错
default: () => { return []/{} }
prop的特性
1 prop形成的数据是单向流,父组件数据改变会影响子组件,但子组件不会改变父组件
2 不能在子组件中,直接修改prop传递过来的值,不然会报警告
3 prop的验证就是定义传递的数据类型,是否是必传值与默认值
4 prop没有验证时,组件可以接受任意的值,如果名称相同,prop数据会顶替原本的数据
2 provide传值
父组件使用 provide:{ } 定义数据
子组件使用 inject:[ ] 接受数据
注意:provide提供的数组 在它的任意后代组件中都可以使用
3 this.$parent
子组件中通过 this.$parent 找到父组件,获取父组件所有的数据以及方法
子传父
1 自定义事件
在父组件的子组件标签上通过绑定自定义事件,接受子组件传递过来的事件。子组件通过$emit触发父组件上的自定义事件,发送参数
2 this.$children
父组件中通过 this.$children 找到子组件,获取的是一个包含所有子组件的类数组集合
非父子组件传值
1 在main.js定义一个全局的bus,bus为一个Vue的实例化对象
Vue.prototype.$bus = new Vue()
2 需要传递的数据的页面调用事件并传递数据
this.$bus.$emit('getData', data:this.data)
3 需要接受数据的页面创建事件并获取数据
this.$bus.$on('getData', (data) => { this.msg = data })
4 在beforeDestory中关闭监听事件
beforeDestory(){
this.$bus.$off('getData')
}
注意:运行的顺序是,先在接受数据的页面创建事件,然后才是传递数据的页面调用事件
每次的传值都会创建一个事件,过多会消耗内存
slot插槽
插槽分为匿名插槽,具名插槽,作用域插槽
匿名插槽
子组件内部,设置 <slot /> ,就可以使用匿名插槽,数据将方式在插槽对应的位置,如果有多个匿名插槽,将会每个插槽都有一份数据渲染
匿名插槽其实也是有名字的,为 default ,通常都是不写的
具名插槽
通过给插槽命名,以此来区分多个插槽
命名:v-slot:name 可简写为 #name
V2的使用
<div slot=‘left’> </div>
或
<template v-slot:left> </template>
V3的使用
<template v-slot:left> </template>
作用域插槽
通过自定义属性,来预备插槽所使用的数据
子组件中
<slot name="z" :say1="say1" :say2="say2"></slot>
子组件标签中
<template #z="{say1, say2}"> //解构作用域插槽的数据
<div>{{say1}}</div>
</template>
vue的data为什么是一个函数
根实例对象data可以是对象也可以是函数(根实例是单例),不会造成数据冲突问题
而组件实例的data都是同一个引用数据,当该组件作为公共组件共享使用,如果它是一个对象,会报错,并且一个地方的data更改,所有的data一起改变,相反,如果data是一个函数,每个实例的data都在闭包中,就不会各自影响了。
vue的常用指令
v-if | 根据条件判断元素的添加与删除,是惰性的,一开始条件不成立,就不会创建, 有更高的切换消耗,适应于条件不太可能改变的情况 |
v-else | 是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用 |
v-show | 根据条件判断元素的显示隐藏,他是通过添加与删除 display:none 来控制元素 有更高的初始渲染消耗,更适用于频繁的状态切换 |
v-for | v-for是根据遍历数据来进行渲染,要配合key使用。要注意的是当v-for和v-if同处于一个节点时,v-for的优先级比v-if更高,不能与 v-if 同时使用,除非使用 template 空标签包裹 |
v-on | 用来绑定一个事件或者方法,可以简写为@click |
v-bind | 用来绑定元素的属性,一般用于绑定class值或style样式,可以简写为 : 绑定class的三种方式: 1、对象型 ‘{red:isred}’ 2、三元型 ‘isred?“red”:“blue”’ 3、数组型 ‘[{red:“isred”},{blue:“isblue”}] |
v-model | 使用与表单元素,实现双项数据绑定,本质为自定义事件@input和自定义属性:value的集合 |
v-html | 用于渲染富文本,即带有html的字符串 |
vue的自定义指令
vue允许我们设置自定义指令,自定义指令分为局部自定义指令与与全局自定义指令
局部自定义指令directives,定义在组件内部,只能在当前组件使用
全局自定义指令Vue.directive作用在全局
自定义指令的参数
el | 所绑定的元素,可以直接操作 |
binding | 一个对象,包含以下属性: name: 指令名,不包括 v- 前缀 |
Vnode | 当前节点信息 |
v2的自定义指令钩子函数
bind | 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置 |
inserted | 被绑定元素插入父节点时调用 |
update | 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前 |
componentUpdated | 指令所在组件的 VNode 及其子 VNode 全部更新后调用 |
unbind | 只调用一次,指令与元素解绑时调用 |
v3的自定义指令钩子函数
created | 创建后调用 |
beforeMount | 自定义指令绑定到 DOM 后调用. 只调用一次 |
mounted | 自定义指令所在DOM, 插入到父 DOM 后调用 |
beforeUpdate | 自定义指令所在 DOM, 更新之前调用 |
updated | 组件VNode 和子VNode 更新后执行 |
beforeUnmount | 销毁前 |
unmounted | 销毁后 |
使用场景
输入框自动聚焦
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
<input v-focus>
相对时间的转换 例:5分钟前
<span v-relativeTime="time"></span>
new Vue({
el: '#app',
data: {
time: 1565753400000
}
})
Vue.directive('relativeTime', {
bind(el, binding) {
// Time.getFormatTime() 方法,自行补充
el.innerHTML = Time.getFormatTime(binding.value)
el.__timeout__ = setInterval(() => {
el.innerHTML = Time.getFormatTime(binding.value)
}, 6000)
},
unbind(el) {
clearInterval(el.innerHTML)
delete el.__timeout__
}
})
传入一个颜色,自动为全局背景色
let app = createApp(App)
// 全局自定义指令
app.directive('color', {
created(el, binding) {
el.style.background = binding.value;
},
updated(el, binding) {
el.style.background = binding.value;
}
})
vue中的修改符
事件修饰符
.stop | 阻止事件冒泡 |
.capture | 触发事件捕获 |
.self | 当事件作用在元素本身时才会触发 |
.once | 只触发一次 |
.prevent | 阻止默认事件 |
·passive | 告诉浏览器你不想阻止事件的默认行为 |
·trim | 自动过滤用户输入的首尾空格 |
v-model的修饰符
.lazy | <input v-model.lazy="msg"> 转变为在change事件再同步 |
.number | 用法同上 自动将用户的输入值转化为数值类型 |
.trim | 用法同上 自动过滤用户输入的首尾空格 |
键盘事件修饰符,支持多个按键
keyup.13 / enter | 点击回车键 |
.delete | 删除或退格键 |
.up .down .left .right | 上下左右按键 |
@keydown.ctrl.13 | 同时按下Ctrl + enter时触发 |
element或子组件的修饰符
对于组件库与子组件来说,都是封装好的事件,书写事件将会成为一个子传父
使用 @click.native ,可以解决这个问题
vue的生命周期
beforeCreate | 实例创建前,目前只有一些实例本身的事件和生命周期函数,无法拿到dom和data数据 |
created | 实例创建后,是最早可以使用data和methods中数据的钩子函数。仍然无法拿到dom |
beforeMount | 在created与beforeMount之间,是用来形成虚拟dom DOM挂载前,但挂载的是之前的虚拟dom |
mounted | DOM挂载后,在此刻dom渲染完毕,页面和内存的数据已经同步,最早的可以获取DOM结构的位置 |
beforeUptate | 更新前,当data的数据被渲染在DOM上,并且发生改变才会执行这个钩子函数 |
updated | 更新后,在此刻内存和页面都是最新的 |
beforeDestroy | 销毁前,即将销毁data和methods中的数据,但再此时还是可以使用的,可以做一些释放内存的操作,同时也是最后可以获取实例与dom的钩子函数 |
destroyed | 已经销毁完毕 |
activated | 被包含在 keep-alive 中创建的组件才会有这个钩子,组件激活时触发 |
deactivated | 被包含在 keep-alive 中创建的组件才会有这个钩子,组件失活时触发 |
vue中的watch
watch,数据监听,用于监听数据变化,当对应数据变化时触发
应用场景
watch{
str:{
handler(newVal){}, //引用数据类型的新旧值是一致的,所以是只有一个新数据就可
deep:ture, //是否深度监听
immediate:true //是否开启初始监听
}
}
如果不需要开启深度监听与初始监听,可简写为
watch{
str(newVal){}
}
computed,methods,watch的区别
computed是计算属性,对应数据变化,自动计算,需要return返出最终的值,拥有缓存,数据不变时,调用缓存
methods是vue中的事件与方法,需要手动调用才能触发
watch是数据监听,不需要return返回值,数据变化自动监听,可判别变化前后的差异
过滤器filter
过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据,需要使用return返回最终的值
过滤器分为局部过滤器和全局过滤器,使用方法是在要过滤的数据后添加 |
keep-alive
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated(组件激活时使用) 与 deactivated(组价离开时调用)
如果需要缓存整个项目,直接在app.vue中用keep-alive包裹router-view即可。要缓存部分页面,需要在路由地址配置中,在meta属性中添加一个状态,在app.vue中判断一下包裹的router-view即可
v-for为什么要使用key
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,如果不使用key的话,不影响正常的使用,但是会在控制台报警告
$nextTick()的作用
作用
$nextTick()是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
使用场景
你在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中,因为在此时还拿不到dom节点
如果是一个input框,在让他显示之后,立即让他自动获取焦点,是无法实现的,因为vue的render()函数将虚拟dom转换真实dom的时候,是异步的,无法在当时直接获取到dom,因此,可以使用this.$nextTick(),再里面书写获取焦点的代码,就可以实现这个功能了
vue修改数据后页面不刷新
<template>
<div>
<p v-for="(value,key) in item" :key="key">
{{ value }}
</p>
<button @click="addProperty">动态添加新属性</button>
</div>
</template>
<script>
export default {
data:()=>{
item:{
oldProperty:"旧值"
}
},
methods:{
addProperty(){
this.item.newProperty = "新值" // 为items添加新属性
console.log(this.items) // 输出带有newProperty的items
}
}
};
</script>
问题:同上所属,在直接给一个对象添加新属性并赋值时,是可以在控制台打印出最新的信息的,但是,这个数据,是无法自动更新至视图的
原因:vue2是使用Object.defineProperty来实现数据的双项数据绑定的,他是给所有的属性添加了set和get,而这样直接的添加新属性并赋值的操作,这个新的属性上,是没有绑定set的,不能触发set,就不能实现双项数据的绑定
Vue 不允许在已经创建的实例上动态添加新的响应式属性
解决方法
1 使用 Vue.$set() 添加新属性 适用于添加少量属性
this.$set(this.item, "newProperty", "张三");
2 使用 Object.assign() ,创建一个新的对象,合并原对象和一个包含新增属性的对象 适用于添加大量属性
this.item = Object.assign({},this.item,{newProperty:'新值'})
3 $forceUpdate 强制刷新,但不建议这样
this.item.newProperty = "新值"
this.$forceUpdate();