面试题【Day07】

发布于 2021-09-07 11:33 ,所属分类:2021面试经验技巧分享

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

Vuex的核心概念 store
  • store vuex最关键的是store对象,这是vuex的核心。创建store
conststore=newVuex.Store({...})

store是Vuex.Store这个构造函数new出来的实例。在构造函数中可以传一个对象参数,这个参数中可以包含5个对象:

1,state存放状态
2,gettersstate的计算属性
3,mutations更改状态的逻辑,同步操作
4,actions提交mutation,异步操作
5,modules将store模块化

关于store:

store中存储的状态是响应式的,当组件从store中读取状态时,如果store中的状态发生了改变,那响应的组件也会得到更新

不能直接改变store中的状态, 改变store中的状态的唯一途径是提交(commit)mutations

一个完整的 store的结构如下:

conststore=newVuex.Store({
state:{},//state就是一些变量,也就是所谓的状态
getters:{},
mutations:{},
actions:{},
modules:{}
})
state

组件中读取store中的状态方法:在组件的计算属性中返回某个状态

{{name}}

computed:{
name(){
returnthis.$store.state.userInfo.name
}
}

当一个组件需要获取多个状态时,将这些状态都声明为计算属性会有些重复和冗余。解决这个问题我们可以使用 mapState 辅助函数帮我们生成计算属性:

import{mapState}from'vuex'
computed:mapState({
name:state=>state.userInfo.name,//箭头函数形式,使代码更简练
name:'userInfo',//传字符串参数等同于state=>state.userInfo
name(state){//如果既要获取局部状态又要获取state状态,必须使用常规函数
returnstate.userInfo+this.jobs
}
})

当映射的计算属性名称 和 state的字节点名称相同时,可以给mapState传一个字符串数组

computed:mapState(['userInfo'])

mapState函数返回的是一个对象。我们可以使用展开运算符简化以上写法

computed:{
...mapState(['userInfo'])
}

使用vuex并不意味着所有的状态都要放入vuex,因为这样会使代码变得冗长、不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。可以根据应用开发需要进行权衡和确定。

getters

有时候我们需要从state中派生出一些状态,我们可以使用getters,getters可以看作是store的计算属性,其参数是state

computed:{
doneTodosCount(){
returnthis.$store.state.todos.filter(todo=>todo.done).length
}
}

vux允许我们在store中定义getter可以认为是store的计算属性,就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会重新计算

getter接受state作为它的第一个参数, 也可以接受其他 getter 作为第二个参数

state:{
todos:[
{id:1,text:'...',done:true},
{id:2,text:'...',done:false}
]
},
getters:{
doneTodos:state=>{
returnstate.todos.filter(todo=>todo.done)
}
}

//可以接受getter作为第二个参数

getter会暴露为store.getters对象,可以通过属性访问

this.$store.getters.doneTodos

也可以通过让 getter 返回一个函数,来实现给 getter 传参

getters:{
getTodoById:(state)=>(id)=>{
returnstate.todos.find(todo=>todo.id===id)
}
}
store.getters.getTodoById(2)//->{id:2,text:'...',done:false}

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果

getter的辅助函数:mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import{mapGetters}from'vuex'
exportdefault{
computed:{
//使用对象展开运算符将getter混入computed对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
])
}
}

如果想将一个 getter 属性另取一个名字,使用对象形式:

...mapGetters({
//把`this.doneCount`映射为`this.$store.getters.doneTodosCount`
doneCount:'doneTodosCount'
})
mutation

更改vuex的store中的状态的唯一方法是提交mutation。mutation类似事件:每个mutation都有一个事件类型type(字符串)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它接受state作为第一个参数

state:{
count:1
},
mutations:{
increment(state){
state.count++
}
}

当触发一个类型为 increment 的mutation时,调用词函数,要唤醒一个mutation handler,需要以相应的type调用store.commit方法

this.$store.commit('increment')

提交载菏:可以向commit传入额外的参数,即mutation的载菏

this.$store.commit('increment',10)
this.$store.commit('increment',{amount:10})

还可以使用type属性来提交mutation

this.$store.commit({
type:'increment',
amount:10
})

mutation必须是同步函数,不能是异步的

mutation的辅助函数:mapMutations

用mapMutations辅助函数将组件中的methods映射为store.commit调用

import{mapMutations}from'vuex'

使用常量替代mutation事件类型,这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

//mutation-types.js
exportconstSOME_MUTATION='SOME_MUTATION'
//mutations.js
import{SOME_MUTATION}from'./mutation-types'
[SOME_MUTATION](state){//使用ES6风格的计算属性命名功能来使用一个常量作为函数名
//...
}

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做

可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)

mutation是同步事务

action

action类似mutation,区别:

  • action提交的是mutation,而不是直接变更状态
  • action可以包含任意异步操作
state:{
count:0
},
mutations:{
increment(state){
state.count++
}
},
actions:{
increment(context){
context.commit('increment')
}
}

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters

实践中,我们会经常用到 ES6 的 参数解构 (opens new window)来简化代码(特别是我们需要调用 commit 很多次的时候):

actions:{
increment({commit}){
commit('increment')
}
}
分发 Action

action通过store.dispatch方法触发

store.dispatch('increment')

乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作

actions:{
incrementAsync({commit}){
setTimeout(()=>{
commit('increment')
},1000)
}
}

Actions 支持同样的载荷方式和对象方式进行分发:

//以载荷形式分发
store.dispatch('incrementAsync',{
amount:10
})

//以对象形式分发
store.dispatch({
type:'incrementAsync',
amount:10
})

看一个实际的购物车示例,涉及到调用异步API和分发多重mutation

actions:{
checkout({commit,state},products){
//把当前购物车的物品备份起来
constsavedCartItems=[...state.cart.added]
//发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
//购物API接受一个成功回调和一个失败回调
shop.buyProducts(
products,
//成功操作
()=>commit(types.CHECKOUT_SUCCESS),
//失败操作
()=>commit(types.CHECKOUT_FAILURE,savedCartItems)
)
}
}

在组件中分发 Action

在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)

import{mapActions}from'vuex'

exportdefault{
//...
methods:{
...mapActions([
'increment',//将`this.increment()`映射为`this.$store.dispatch('increment')`

//`mapActions`也支持载荷:
'incrementBy'//将`this.incrementBy(amount)`映射为`this.$store.dispatch('incrementBy',amount)`
]),
...mapActions({
add:'increment'//将`this.add()`映射为`this.$store.dispatch('increment')`
})
}
}


相关资源