一.Vue基础
- MVVM框架
MVVM是Model-View-ViewModel的缩写。MVVM借鉴了MVC的思想,可以看做是MVC的扩展。
Model是数据源,用JavaScript对象表示。
View负责显示,两者做到了最大限度的分离。
把Model和View关联起来的就是ViewModel,ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步到Model。
- Vue的优点是什么?
1.轻量级框架:只关注视图层,是一个构件数据的视图集合,大小只有几十KB。
2. 简单易学:国人开发,中文文档,不存在语言障碍,易于理解和学习。
3. 双向数据绑定:保留了angular的特点,在数据操作方面更简单。
4. 组件化:保留了react的特点,实现了html的封装和重用,在构建单页应用方面,有着独特的优势。
5. 视图,数据结构分离:使数据的更改更为简单,不需要再进行逻辑代码的修改,只需要操作数据就能完成相关操作。
6. 虚拟DOM:DOM操作是非常耗费性能的,不再使用原生的DOM节点操作,极大的解放DOM操作,但具体操作的还是DOM,不过是换了另外一种方式。
7. 运行速度更快:相比较与react而言,同样是操作虚拟DOM,就性能而言,vue存在很大的优势。
- Vue的两个核心点是什么?
1.数据驱动;
当我们把一个普通的JavaScript对象传入vue实例,作为data选项,vue将遍历此对象所有的property,并使用Object.defineProperty()把这些property全部转化为getter/setter
。
Object.defineProperty()是ES5中一个无法shim的特性,这也是vue不支持IE8以及更低版本浏览器的原因。
这些getter/setter
对用户来说是不可见的,但是在内部它们让Vue能够追踪依赖,在property被访问和修改时通知变更。
每个组件实例,都对应一个watcher实例,它会在组件渲染的过程中,把接触过
的数据property记录为依赖。之后当依赖的setter被触发时,会通知watcher,从而使他关联的组件重新渲染。
2.组件系统;
组件的核心选项
1 模板(template):模板声明了数据和最终展现给用户的DOM之间的映射关系
2 初始数据(data):一个组件的初始数据状态。对于可复用的组件来说,这通常是私有的状态。
3 接受的外部参数(props):组件之间通过参数来进行数据的传递和共享。
4 方法(methods):对数据的改动操作一般都在组件的方法内进行。
5 生命周期钩子函数(lifecycle hooks)
6 私有资源(assets):Vue.js当中将用户自定义的指令、过滤器、组件等统称为资源。一个组件可以声明自己的私有资源。私有资源只有该组件和它的子组件可以调用。
- 渐进式框架的理解
渐进式代表的含义:主张最少。
每个框架都不可避免会有自己的一些特点,从而对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强弱程度,会影响在业务开发中的使用方式。
比如:Angular ,它的两个版本都是强主张的。使用它必须接收一下东西:
1.必须使用它的模块机制
2.必须使用它的依赖注入
3.必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)
比如:React,它有一定程度的主张,它的主张主要是函数式变成的理念,比需要知道什么是副作用,什么是纯函数,如何隔离副作用,它的侵入性看似没有Angular那么强,主要因为它是软性侵入。
而Vue,是渐进式的,没有强主张,你可以在原有大系统上面,把一个两个组件改用它实现,当jQuery使用;也可以整个用它全家桶开发,当Angular用;还可以用它的视图,搭配自己设计的整个下层用;
它只是个轻量视图而已,只做自己该做的事情,没有做不该做的事情。
总之,渐进式框架,可以理解为没有多做职责之外的事
引用自
- 单页应用和多用应用的区别,及优缺点?
单页应用:只有一个web主页面的应用,公共资源(js,css等)仅需加载一次,所有的内容都包含在主页面,对每个功能模块组件化。单页应用跳转,就式切换组件,仅刷新局部资源,常用于PC端官网,购物网站等。
多页面应用:有多个独立页面的应用,每个页面的公共资源(js,css等)需选择性重新加载,多页面跳转刷新所有资源,常用于app和客户端
单页应用优点:1.用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点SPA对服务器压力比较小;2.前后端分离;3.视觉效果好,例如切换页面时的转场动画。
单页应用缺点:1.不利于SEO。2.初次加载时耗时多。3.导航不可用,如果要用导航,需要自行实现前进后退。4.页面复杂度提高很多
多页面应用的优点:SEO效果好;缺点:页面跳转较慢,需要重新加载公共资源
- SPA首屏加载慢,如何解决?
路由懒加载,一开始只加载部分资源。
第三方库通过CDN引入
加一个首屏的loading图,可以提高用户体验
- vue-cli项目中src目录每个文件夹和文件的用法?
assets 存放静态资源
components 存放组件
router定义路由相关的配置
view 是视图
App.vue 是应用主组件
main.js 是入口文件
- assets和static的区别
assets里的静态资源,会被webpack打包进你的代码里,而static里的就直接引用了,要用绝对路径;一般在assets里放属于该项目的资源文件,而static里放一些类库的文件。
- nextTick的使用
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后,立即使用这个方法,获取更新后的DOM;(修改数据后,vue并不会立即更新DOM,DOM的更新是异步的,无法通过同步代码获取,需要使用nextTick在下一次事件循环中获取)
<div id="app" ref="apps">
{{msg}}
</div>
var vm = new Vue({
el:'#app',
data:{
msg:'hello'
},
created(){
console.log(this.$refs['apps'].innerText) //这里会报错,因为created生命周期里,还无法操作DOM
},
mounted(){
console.log(this.$refs['apps'].innerText) // hello
this.msg = 'word' // 修改数据
console.log(this.$refs['apps'].innerText) // hello,可以看到,修改数据后,并不会立即更新DOM。
//如果想要获取更新后的DOM信息(比如动态获取宽高,位置信息等),需要nextTick
this.$nextTick(function(){
console.log(this.$refs['apps'].innerText) // word
})
},
})
- vue组件中,data为什么必须是一个函数?
组件是可以复用的,如果data是一个对象,那么当我们实例化多个组件的时候,它们的data实际上是引用的同一个对象,会相互影响。而当data是一个函数的时候,通过return返回对象的拷贝。那么我们再去实例化多个组件的时候,它们的data都是相互独立的,可以互不影响的去更改。
- 为什么使用key?
key主要用在vue的虚拟DOM算法,在新旧node对比时辨识VNodes。如果不使用key,vue会使用一种最大限度减少动态元素并且尽可能地尝试就地修改/复用相同类型的元素的算法。
使用key,它会基于key的变化,重新排列元素顺序,并且会移除key不存在的元素。
有相同父元素的子元素, 必须有独特的key,重复的key会造成渲染错误
- vue初始化页面闪动问题?
// css中
[v-cloak]{
display:none;
}
- vue为什么要求组件模板只能有一个根元素?
为了让组件能够生成一个正常的vue实例,必须确定一个根节点作为入口,通过这个根节点,来递归遍历整个vue树下的所有节点,并处理为vdom,最后再渲染成真正的HTML,插入正确的位置。
那么这个根节点,就相当于树的根,各个子元素,子组件就是这个树的枝叶。这棵树就是一个vue实例。
- v-show 和 v-if 的共同点和不同点?
共同点:控制元素的显示和隐藏
不同点:v-show:无论初始条件是什么,元素总是会被渲染,当条件变化时 ,只对css的diaplay进行切换。
v-if: 是真正的条件渲染,它会确保在切换的过程中,条件块内的事件监听器和子组件,适当的被销毁和创建。同时v-if也是惰性的,如果初始渲染时条件为false,则什么也不做,直到条件第一次变为true时,才开始渲染条件块。
- <keep-alive></keep-alive>的作用?
<keep-alive></keep-alive>可以时被包裹的组件,保留状态,避免被重新渲染。它有属性 include和exclude,是字符串或正则表达式
include: 匹配到的组件会被缓存。
exclude:匹配到的组件不会被缓存。
export default{
name:'a',
data(){}
}
<keep-alive include="a">
<component>
// name为a的组件,将被缓存
</component>
</keep-alive>
<keep-alive exclude="b">
<component>
// name为b的组件,将不被缓存
</component>
</keep-alive>
// router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
<keep-alive>
<router-view>
<!-- 所有路径匹配到的视图组件都会被缓存! -->
</router-view>
</keep-alive>
// 如果只想 router-view 里面某个组件被缓存,怎么办?
// 增加 router.meta 属性
// routes 配置
export default [
{
path: '/',
name: 'home',
component: Home,
meta: {
keepAlive: true // 需要被缓存
}
}, {
path: '/:id',
name: 'edit',
component: Edit,
meta: {
keepAlive: false // 不需要被缓存
}
}
]
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 这里是会被缓存的视图组件,比如 Home! -->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 这里是不被缓存的视图组件,比如 Edit! -->
</router-view>
16.说出几种vue中的指令和用法
v-show/v-if 条件渲染
v-for 列表渲染
v-on 绑定事件监听器
v-bind 动态的绑定一个或多个attribute
v-model 在表单空间或组件上创建双向绑定
v-slot 插槽
v-once 只渲染元素和组件一次,对低开销的静态组件使用v-once提示性能
v-pre 跳过这个元素和它的子元素的编译过程
- v-for和v-if的优先级
v-for优先级比v-if高
- computed和watch的区别和使用场景
computed 计算属性,是依赖其他的属性计算后得出一个新的属性;另外计算属性是基于它的依赖进行缓存的,计算属性只有当它的依赖发生变化时才会重新求值。如果依赖没有发生变化,多次访问计算属性,会立即返回之前的计算结果。
watch是监听值的变化,然后执行相应的函数。测重点是监听的数据发生变化时,我们可以做一些事情,比如我们可以监听分页组件当前页面的变化,当页面变化时,我们可以执行获取数据的函数
- vue常用的修饰符
<a v-on:click.stop="doSth"></a> // .stop 阻止单击事件继续传播
<form v-on:submit.prevent="onSubmit">...</form> // 告诉v-on指令对于触发的事件调用 event.preventDefault() 也就是取消事件的默认动作
<a v-on:click.stop.prevent="doThat"></a> //修饰符可以串联
<div v-on:click.self="doSth">...</div> // .self 当event.target是当前元素自身时,才触发处理函数
<div v-on:click.capture="doSth">...</div> // 添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处理,然后才交由内部元素进行处理
- 如何自定义指令
vue2.0中,代码复用和抽象的主要形式是组件,然而,有的情况下,你仍然需要对普通的DOM元素进行底层操作。这个时候就会用到自定义指令,举个聚焦输入框的例子。
// 当页面加载时,该元素获得焦点(注意:autofocus在移动版safari上不工作)
// 事实上只要打开页面后,还没有点击过任何内容,这个输入框就应该是处于聚焦状态的
// 现在我们用指令来实现这个功能
// 注册一个全局 自定义指令 v-focus
Vue.directive('focus',{
// 当被绑定的元素插入到DOM中时
inserted:function(el){
// 聚焦元素
el.focus()
}
})
// 如果想注册局部指令,组件也接收一个directives的选项
directives:{
focus:{
inserted:function(el){
el.focus()
}
}
}
传值问题
- 父组件向子组件传递数据
父组件内设置要传递的数据,在父组件内引用的子组件上绑定一个自定义属性并把数据绑定在自定义属性上,在子组件上添加参数props接受即可
- 子组件向父组件传递事件
子组件通过vue实例方法$emit进行触发并且可以携带参数,父组件通过v-on监听,然后进行方法处理
- 非父子组件传值--EventBus(事件总线)
要传值的组件,都引用一个新的vue实例,然后通过分别调用这个实例的事件触发和监听来实现通信和参数的传递
// 1. 新建bus.js文件 common/bus.js
import Vue form 'vue'
//使用event bus
const bus = new Vue()
export default bus
// 组件1接受通知信息
impot bus from '@/common/bus.js'
export default {
data(){
return msg:''
},
created(){
bus.$on('sendMsg',(msg) => {
this.msg = msg
})
}
}
// 组件2 发布信息
impot bus from '@/common/bus.js'
export default {
methods:{
sendData(){
bus.$emit('sendMsg','我是组件2的信息')
}
}
}
事件总线方式,有一个坑,就是当我们把事件总线,挂载到vue原型上,全局调用,当切换路由的时候,事件会重复的触发,这主要是因为我们的事件是全局的,并不会随着组件的销毁而自动注销,需要我们用手动销毁来注销
// 我们可以在组件的 beforeDestory或destory生命周期中执行注销方法来注销事件
beforeDestory(){
this.bus.$off('事件名')
}
- 非父组件组件的传值 ---Vuex
生命周期
- 什么是生命周期?
每个vue实例,在创建之前,都要经过一系列的初始化过程,这个过程就是生命周期。生命周期函数,就是vue实例,在某一个时间点,会自动执行的函数。
生命周期函数,并不会放在methods属性里,而是直接放在vue实例上的
- 都有哪些生命周期函数?
beforeCreate 此时已经初始化了事件和生命周期,此时数据还没有挂载,所以就无法访问到数据
created vue的实例创建完成后调用,此时已经可以使用数据,也可以更改数据,在这里更改数据,不会触发updated和其他钩子函数,一般可以做初始化数据的获取
beforeMount 模板和数据结合即将挂载到页面前的瞬间调用,此时虚拟DOM已经创建完成,马上就要渲染。这里可以更改数据,不会触发updated,渲染前最后一个更改数据的机会
mounted 实例被挂载后调用,此时页面已经渲染完毕了,可以操作真实DOM
beforeUpdate 数据改变,页面重新渲染之前调用
updated 数据改变,页面重新渲染之后调用
beforeDestory vue实例被销毁之前调用 可以通过vm.$destory()来销毁,一般在这里做一些善后事情,比如清除定时器,清除绑定事件等等
destoryed vue实例被销毁之后调用,该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
activated 被keep-alive缓存的组件,激活时调用
deactivated 被keep-alive缓存的组件,停用时调用
errorCaptured 当捕获一个来自子孙组件的错误时调用
- 第一次页面加载会触发哪几个钩子?
beforeCreate, created, beforeMount, mounted
- 简述每个周期具体适合哪些场景
beforeCreate 可以在此时加一些loading效果,在created时移除
created 需要异步请求数据的方法可以在这里执行,完成数据的初始化
mounted 可以在这里操作DOM
updated 当数据更新,要做统一业务处理的时候
- vue获取数据在哪个周期函数?
一般是放在created里,如果需要操作DOM,可以在mounted
vue-router
1.vue-router是什么? 它有哪些组件?
vueRouter是vue.js官方的路由管理器,它和vue.js是深度集成的,它可以让构建单页应用变得更加简单
// vueRouter的组件
// 路由声明式跳转,active-class是router-link 标签被点击时的样式
<router-link :to="" class="active-class">
<router-view> 渲染路由的容器
<keep-alive> 缓存组件
- 怎么定义vueRouter的动态路由,怎么获取传过来的值?
在router文件夹下的index.js里,对path属性加上 /:id
使用router对象的params.id 例如: this.$router.params.id
- vue-router有哪几种导航钩子?
全局钩子
// 主要包括 beforeEach,afterEach
// router.beforeEach 注册一个全局前置守卫:
const router = new VueRouter({...})
router.beforeEach((to,form,next) => {
...
})
// 守卫方法,接受三个参数
to :Route 即将要进入的模板路由对象
form: Router 当前导航正要离开的路由
next: Function 一定要调用该方法来resolve这个钩子。执行效果依赖next方法的参数
next() 进行管道中的下一个钩子,如果全部钩子执行完了,则导航状态就是confirmed(确认的)
next(false)中断当前导航,如果浏览器的URL改变了(可能是用户手动或浏览器后退按钮),那么URL地址会重置到from路由对应的地址
next('/'),或next({path:'/'}),跳转到一个不同的地址,当前导航被中断 ,然后进行到一个新的导航
next(error) 如果传入next的是一个Error实例,则导航会被终止,且该错误会被传递给,router.OnError注册过的回调
beforeEach 要确保调用next,否则钩子不会被resolved
// router.afterEach 全局后置钩子,跟前置守卫的区别是,这些钩子不会接受next参数,也不会改变导航本身
router.afterEach((to,form){
...
})
路由独享的守卫
// 你可以在路由配置上直接定义 beforeEnter 守卫,这个守卫与全局前置守卫的方法参数是一样的
const router = new VueRouter({
routes:[
{
path:'/foo',
component:Foo,
beforeEnter:(to,form,next){
// ...
}
}
]
})
组件内的守卫
// 可以直接在路由组件内定义 以下路由导航守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const Foo = {
template:`...`,
beforeRouteEnter(to,from,next){
// 在渲染该组件的对应路由被confirm前调用
// 不能获取组件实例 this
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to,from,next){
//在当前路由改变,但是该组件被复用时调用
// 举例来说,当一个带有动态参数的路径 /foo:id,在/foo/1和/foo/2之间跳转的时候
// 由于会渲染同样的Foo组件,因此组件实例会被复用,而这个钩子就会在这种情况下被调用
// 可以访问组件实例this
},
beforeRouteLeave(to, from, next){
// 导航离开该组件对应的路由时调用
// 可以访问组件实例this
}
}
beforeRouteEnter不能访问this,因为守卫在导航确认前被调用,因此即将登场的新组件还没有被创建。
不过,可以传一个回调给next来访问组件实例,在导航被确认的时候,执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter(to,from,next){
next(vm => {
//通过vm访问组件实例
})
}
注意:beforeRouteEnter是支持给next传递回调的唯一守卫,因为对于beforeRouteUpdate和beforeRouteEnter来说,this已经可用了,所以没有必要传递回调
这个离开守卫,通常用来禁止用户还未保存修改前突然离开,该导航可以通过next(false)取消
beforeRouteLeave(to, from, next){
const answer = window.confirm('Do you realy want to leave? you have unsaved changes!')
if(answer){
next()
}else{
next(false)
}
}
- $route 和 $router的区别
$router 是VueRouter的实例,主要是用来实现路由跳转,它是一个全局对象,里面包含了很多属性和子对象,例如history对象。
// 以 hisroty对象来举例
$router.push({path:'home'})
// 本质是向history栈中添加一个路由,在我们看来是切换路由,但本质上是添加一个history记录,和router-link跳转一样
$router.replace({path:'home'}) 替换路由,没有历史记录
$route 是一个当前正在跳转的路由对象,是一个局部对象,可以获取对应的,name,path,query,params等
- vue-router传参
vue导航的两种方式
1. 编程式导航 $router.push
2. 声明式导航 <router-link :to="">
这两种方式本质上是一致的,我们可以在导航的时候向目标页面传递参数。
我们以$router.push为例,它导航时接受的参数分两种,字符串和对象
// 字符串,这种导航方式很简单,但是不能传递参数
this.$router.push('home')
// 对象
this.$router.push({name:'user'})
// 命名路由
// 命名路由传参 需要 params传参
this.$router.push({name:'user',params:{id:'123'}})
// 目标页面接受参数时用params
this.$route.parmas.id
// 特别注意,命名路由参数这种方式,目标页面再次刷新,参数会丢失
// 查询参数
// 查询参数,其实就是在路由后面带上参数和传统的URL参数一致的
// 传递参数使用query,而且必须配合path来传递,不能使用name.
this.$router.push({path:'/news',query:{userId:123}})
// 目标页面接受参数,使用query
this.$route.query.userId
// 这种方式传参,目标页面再次刷新,参数不会丢失
// 声明式导航和编程式导航一样
<router-link to="news">click to news page</router-link>
<router-link to="{name:'user',params:{userId:123}}">click to news page</router-link>
<router-link to="{path:'/user',query:{userId:123}}">click to news page</router-link>
- vue-router的两种模式(hash,history)?
HTML5通过更新history对象为管理历史状态提供了方便
vue-router默认是hash模式,还有一种模式是 history模式;
在hash模式下,#后面的hash值发生变化导致的URL的改变,不会导致浏览器向服务器发送请求,不会导致页面重新加载。
但是hash值的改变,会触发hashchange事件,因为hash发生变化的url会被浏览器记录下来,从而你会发现浏览器的前进后退按钮都可以使用了。
这样一来,尽管浏览器没有请求服务器,但页面状态和URL已经关联起来了,这就是所谓的前端路由,单页应用的标配。
// hash模式背后的原理是 hashchange事件,可以在window对象上监听这个事件
window.onhashchange = function(event){
console.log(event.oldURL) // hash改变前的url
console.log(event.newURL) // hash改变后的url
let hash = location.hash.slice(1) // 获取#后的字符
document.body.style.color = hash // 我们可以通过改变hash值,来动态改变页面的状态,并且不需要刷新页面
}
history 路由
随着 history API的到来,前端路由开始进化了,hash模式下的hashchange事件,只能改变#后面的URL片段。
而history API则可以把URL改变为与当前URL同源的任意URL。
history API 可以分为两大部分,切换和修改
// 切换历史状态
history.go(-3) //后退3次
history.back() // 后退一次
history.go(1) // 前进一次
history.forward() //前进一次
// 修改历史状态,有两个方法 pushState,replaceState
// pushState接收3个参数,状态对象,新状态的标题,和可选的相对URL,例如:
history.pushState({name:"lisa"},"lisa' page",lisa.html)
执行pushState方法后,新的状态信息会被加入历史状态栈,而浏览器的地址也会变为新的相对URL。但是浏览器不会真的向服务器发送请求,即使状态改变之后,查询location.href也会返回与地址栏中相同的地址。
因为pushState方法会创建新的历史状态,所以你会发现后退按钮也能使用了。按下后退按钮,就会触发window对象的popState
事件。该事件的事件对象有一个state属性,这个属性就包含着当初以第一个参数传递给pushState
的状态对象
// 更新当前状态,可以调用 replaceState(),传入的参数与pushState方法前两个参数一样
history.replaceState({name:"lilei"},"Lilei 's page");
调用 replaceState()方法,不会在历史状态栈中创建新状态,指挥重写当前状态
注意:
在使用HTML5的状态管理机制时,请确保使用pushState()创造的每一个假
URL,在web服务器上,都有一个真的,实际存在的URL与之对应,否则,单击刷新按钮,会导致404错误。
vue-router history的优缺点
优点:可以让我们的URL避免出现丑陋的#
缺点:当刷新页面时,如果服务器没有与之对应的资源URL,会导致404.
hash模式下,前端路由修改的是#号后的信息,浏览器发请求的时候,是不带hash的
- vue-router实现路由懒加载
// 方式一 : vue异步组件
{
path:'/home',
name:'home',
component:resolve => require(['@/components/home'],resolve)
}
// 方式二: ES6的 import
const Home = imort('@/components/home')
{
path:'/home',
component:Home
}
// 方式三: webpack提供的 require.enture()
{
path:'/home',
name:'home',
component:r => require.enture([],()=>r(require('@/components/home')),'demo')
}
8 . vue-router 重定向
// redirect
{
path:'/',
name:'home',
redirect:'/home'
}
- vue-router怎么配置404页面?
import notfount from '@/components/404'
{
path:'*',
name:'notfount',
component:notfount
}
- vue跳转新路由滚动到固定位置
// 在router/index.js里加下边一段代码
// 表示页面跳转的时候,新页面始终在顶部
scrollBehavior(to,from,savePosition){
return {x:0,y:0}
}
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
}, {
path: '/city',
name: 'City',
component: City
}],
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
- vue路由去掉#
使用路由的history模式
const router = new VueRouter({
mode:'history',
routes:[...]
})
- vue-router跳转和location.href跳转的区别
location.href 跳转简单,但是会刷新页面。
vue-router跳转,本质上是使用history API,比如history.pushState,无刷新页面,静态跳转
vuex
- 什么是vuex?
vuex是一个专门为vue.js应用程序开发的状态管理插件。它采用集中式存储管理应用的所有组件的状态,而改变状态的唯一方法是提交
mutation
- vuex解决了什么问题?
多个组件依赖于同一状态时,对于多层嵌套的组件的传参将会变得非常繁琐,并且对于兄弟组件间的状态传递无能为力。
- 怎么使用vuex?
// 先安装 npm install vuex --save
// 在项目src目录下创建store文件夹
// 在store文件夹下新建index.js文件写入:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex )
const store = new Vuex.Store({
state:{},
getters:{},
mutation:{},
actions:{}
})
export default store
// 然后在main.js中引入 store
import Vue from 'vue'
import App from 'app'
import store from './store'
const vm = new Vue({
store:store,
render:h=>h(App )
}).$mount('#app')
- vuex的5个核心属性
state,getters,mutations,actions,modules
- vuex的状态存储在哪里,怎么改变它?
存储在state中,改变vuex中的状态的唯一途径时 显示地提交 mutaion
- vuex中状态是对象时,需要注意什么?
对象是引用类型,复制后改变属性,还是会影响原始数据,这样会改变state里的状态,是不允许的。所以需要先深度克隆复制对象,再修改。
- 怎么在组件中,批量使用vuex的状态
// 使用mapState 辅助函数,利用对象展开运算符,那state混入到computed对象中
import { mapState } from 'vuex'
export default {
computed:{
...mapState(['price','number'])
}
}
- vuex要从state派生一些状态出来,且多个组件使用它,该怎么办?
// 使用getter属性,相当于vue中的计算属性 computed,只有原状态改变,派生状态才会改变
// getter接收两个参数,第一个是state,第二个是getters(可以用来访问其他getter)
const store = new Vuex.Store({
state:{
price:100, // 单价
number:10, // 数量
discount:0.7 // 折扣
},
getters:{
total: state =>{
return state.price*state.number
},
discountTotal:(state,getters) => {
reutrn state.discount*getters.total
}
}
})
// 然后在计算属性中,可以用计算属性 computed中通过 this.$store.getters.total ,来访问这些派生状态
computed:{
total(){
return this.$store.getters.total
},
discountTotal(){
return this.$store.getters.discountTotal
}
}
- 怎么通过getter来实现在组件内可以通过特定条件来获取state的状态?
// 通过让getter返回一个函数来实现给getter传参。然后通过参数来进行判断从而获取满足要求的state
const store = new Vuex.Store({
state:{
todos:[
{id:1,text:'...',done:false}
{id:2,text:'...',done:true}
]
},
getters:{
getTodoById:(state) => (id) => {
return state.todos.find(todo => todo.id === id )
}
}
})
// 然后在组件中,可以用计算属性computed 访问 this.$store.getters.getTodoById(2)这样来访问这些派生状态
computed:{
getTodoById(){
return this.$store.getters.getTodoById()
}
},
mounted(){
console.log(this.getTodoById(2).done) // true
}
- 怎么在组件中批量使用 getter属性
//使用 mapGetters辅助函数,利用对象展开运算符符将getter混入computed对象中
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['total','discountTotal'])
}
- 怎么在组件中,批量给getter属性取别名使用
import {mapGetters} from 'vuex'
computed:{
...mapGetters({
mytotal:'total',
mydiscounttotal:'discountTotal'
})
}
- 在vuex的state中,有一个状态number,在组件怎么改变它?
// 首先要在mutations 中注册一个mutation
cont store = new Vuex.Store({
state:{
number:10
},
mutations:{
SET_NUMBER(state,data){
state.number = data
}
}
})
//然后 在组件中,使用 this.$store.commit 来提交mutation ,改变state
this.$store.commit('SET_NUMBER',100)
- 在vuex中使用mutation时要注意什么?
mutation必须是同步函数
- 在组件中多次提交同一个mutation,怎么写使用更方便?
// 借助 mapMutations辅助函数,在组件中这样写
methods:{
...mapMutations({
setNumber:'SET_NUMBER'
})
}
//然后调用 this.setNumber(100) 相当于调用 this.$store.commit('SET_NUMBER',100)
- Vuex中 action 和 mutation有什么区别?
1.action 提交的是mutation,而不是直接变更状态,mutation可以直接变更状态。
2.action可以包含任意的异步操作,而mutation只能是同步操作。
3.提交方式的不同:action是用 this.$store.dispatch('ACTION_NAME',data)来提交。而mutation是通过this.$store.commit('MUTATION_NAME',data)来提交。
4.接收参数不同:mutation接收的第一个参数是 state;而action接收的第一个参数是 context,其包含了以下属性
{
state, // 等同于 store.state ,若在模块中则为局部状态
rootState, //等同于 store.state,只存在模块中
commit, //等同于 store.commit
dispatch, //等同于 store.dispatch
getters, //等同于 store.getters
rootGetters //等同于 store.getters ,只存在于模块中
}
- Vuex中 action 和 mutation有什么相同点?
// 第二个参数,都可以接收外部提交时传来的参数
this.$store.commit('SET_NUMBER',10)
this.$store.dispatch('ACTION_NAME',data)
- 在组件中,多次提交同一个action,怎么写更方便?
// 借助 mapActions 辅助函数
methods:{
...mapActions({
setNumber:'SET_NUMBER'
})
}
// 然后调用 this.setNumber(10),相当于调用 this.$store.dispatch('SET_NUMBER',10)
- Vuex中 action通常是异步的,那么如何知道 action什么时候结束呢?
// 在action函数中,返回promise,然后在提交时用then处理
actions:{
SET_NUMBER_A({ commit },data){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
commit('SET_NUMBER',10)
resolve()
},2000)
})
}
}
this.$store.dispatch('SET_NUMBER_A',10).then(()=>{...})
- Vuex中有两个action,分别是 actionA和 actionB,其内都是异步操作,在actionB,提交actionA,需要在actionA处理结束后再处理其他操作,怎么实现?
actions:{
async actionA({commit}){
//....
},
async actionB({dispatch}){
await dispatch('actionA') // 等待actionA完成
// 。。。 再进行其他操作
}
}
- 有用过vuex模块吗? 为什么要用? 怎么用?
有,因为使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得非常臃肿,所以将store分割成模块(module)。每个模块拥有自己的state,getters,mutations,actions,甚至是嵌套子模块,从上至下进行同样方式的分割。
//创建module文件夹,然后在文件夹里创建moduleA.js,moduleB.js文件。在文件中写入
// moduleA.js
const state = {
//...
}
const getters = {
//...
}
const mutations = {
// ...
}
const actions = {
// ...
}
export default {
state,
getters,
mutations,
actions
}
// 然后在 store/index.js中引入模块
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import moduleA from './module/moduleA.js'
import moduleB from './module/moduleB.js'
const store = new Vuex.Store({
modules:{
moduleA,
moduleB
}
})
export default store
- 在模块中,getter和mutation接收的第一个参数state,是全局的还是模块的?
第一个参数state是模块的state,也就式局部的state
- 在模块中,getter和mutation和action,怎么访问全局的state和getter?
在getter中可以通过第三个参数rootState访问全局的state,通过第四个参数rootGetter访问全局getter
在mutation中不可以访问全局的state和getter,只能访问局部的state
在action中第一个参数 context的context.rootState可以访问全局的state,context.rootGetters访问到全局的getter
- 在组件中,怎么访问vuex模块中的state和getters,怎么提交mutation和action?
直接通过 this.$store.state和this.$store.getters来访问 模块中的 state和getter
直接通过 this.$store.commit('MUTATION_NAME',data) 来提交mutation
直接通过this.$store.dispatch('ACTION_NAME',data)来提交action
- 用过vuex的命名空间吗?为什么用?怎么用?
默认情况下,模块内部的action,mutation,getter都是注册在全局命名空间,如果多个模块中的action,mutation,getter的命名都是一样的,那么提交mutation,action时将会触发所有模块中命名相同的mutation和action。
这样有太多的偶荷,如果要使你的模块有跟高的封装度和复用性,你可以通过添加namespaced:true的方式,使其成为带命名空间的模块
export default {
namespaced:true
state,
getters,
mutations,
actions
}
- 怎么在带命名空间的模块内提交全局的mutation和action?
// 将{root:true}作为第三个参数,传给dispatch和commit即可
this.$store.dispatch('actionA',null,{root:true})
this.$store.commit('mutationA',null,{root:true})
- 怎么在带命名空间的模块内注册全局的action
actions:{
actionA:{
root:true,
handler(context,state){ ... }
}
}
- 在组件中,怎么提交modules中带命名空间的moduleA中的mutationA?
this.$store.commit('moduleA/mutationA',data)
- 怎么使用mapState,mapGetters,mapMutations,mapActions这些函数来绑定带命名空间的模块?
// 首先使用createNamespacedHelpers创建基于某个命名空间的辅助函数
imort {createNamespacedHelpers} from 'vuex'
const { mapState, mapActions} = createNamespacedHelpers('moduleA')
export default {
computed:{
// 在module/moduleA中查找
...mapState({
a:state => state.a,
b:state => state.b
})
},
methods:{
// 在module/moduleA中查找
..mapActions({
'actionA',
'actionB'
})
}
}
- vuex插件有用过吗?怎么用简单介绍一下
vuex插件就是一个函数,它接收store作为唯一的参数。在Vuex.Store构造器选项plugins中引入。在store/plugin.js文件中写入
export default function createPlugin(param){
return store=>{
// ...
}
}
// 然后在store/index.js文件中写入
import createPlugin from './plugin.js'
const myplugin = createPlugin()
const store = new Vuex.Store({
...
plugins:[myplugin]
})
- 在vuex插件中,怎么监听组件中提交mutation和action?
用Vuex.Store的实例方法subscribe监听组件中提交 mutation
用Vuex.Store的实例方法subscribeAction监听组件中提交action
export default function createPlugin(param){
return store =>{
store.subscribe((mutation,state)=>{
console.log(mutation.type) // 是哪个mutation
console.log(mutation.payload)
console.log(state)
})
store,subscribeAction((action,state) => {
console.log(action.type) // 是哪个action
console.log(action.payload) // 提交action的参数
})
store,subscribeAction({
before:(action,state) => { // 提交action之前
console.log(`before action ${action.type}`)
},
after:(action,state) => { //提交action之后
console.log(`after action ${action.type}`)
}
})
}
}
- 在v-model上,怎么使用Vuex中state的值?
<input v-model:"message">
export default {
computed:{
message:{
get(){
return this.$store.state.message
},
set(value){
this.$store.commit('updateMessage',value)
}
}
}
}
- vuex的严格模式是什么?有什么作用,怎么开启?
在严格模式下,无论什么时候发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到
// 在Vuex.Store构造器中开启,如下:
const store = new Vuex.Store({
strict:true
})