VueX总结

本文介绍Vue.js应用程序的状态管理模式Vuex,包括其基本概念、核心功能和使用方法。Vuex通过集中式存储管理组件状态,确保状态变化的可预测性。

一、Vuex是什么?

Vuex是一个专为vue.js应用程序开发的状态管理模式。他采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

二、什么是状态管理模式?

让我们从一个简单的vue计数应用开始

new Vue({
  //state
  data(){
    return {
      count:0
    }
  },
  //view
  template:`
    <div>{{count}}</div>
  `,
  //actions
  methods:{
    increment(){
      this.count++
    }
  }
})

这个状态自管理应用包含以下几个部分:

(1)state,驱动应用的数据源

(2)view,以声明方式将state映射到视图

(3)actions,响应在view上的用户输入导致的状态变化

以下是一个表示单向数据流理念的示意图:

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

(1)多个视图依赖同一个状态(传参的方法对于多层嵌套的组件非常繁琐,对于兄弟组件的间的状态传递无能为力)

(2)来自不同视图的行为需要变更同一状态()

三、开始

每一个vuex应用的核心就是store(仓库)。store基本上就是一个容器,他包含着你的应用中大部分的状态。vuex和单纯的全局对象有以下两点不同:

1.vuex的状态存储是响应式的。当vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新

2.你不能直接改变store中的状态,改变store中的状态的唯一途径就是显示的提交(commit)mutation。这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地理解我们的应用。

最简单的store

const store = new Vuex.Store({
  state:{
    count:0
  },
  mutations:{
    increment(){
      state.count++
    }
  }
})

现在,你可以通过store.state来获取状态对象,以及通过store.commit方法触发状态变更:

store.commit("increment")
console.log(store.state.count)//1

四、核心概念

state

1.单一状态树

Vuex使用单一状态树,用一个对象就包含了全部的应用层级状态。每个应用仅仅包含一个store实例

2.如何在vue组件中获得vuex状态?

由于vuex的状态存储是响应式的,从store实例中读取状态最简单的方法就是在计算属性中返回某个状态:

const Conter = {
  template:`<div>{{count}}</div>`,
  computed:{
    count(){
      return store.state.count
    }
  }
}

每当store.state.count变化的时候,都会重新求取计算属性,并且出发更新相关联的DOM.

然而,这种模式导致组件依赖全局状态单例,在模块化的构建中,每个需要使用state的组件中需要频繁的导入,因此Vuex通过store选项,提供一种机制将状态从根组件注入到每一个子组件中

const app = new Vue({
  el:"#app",
  //把store对象提供给store选项,这可以把store的实例注入所有的子组件
  store,
  components:{Counter},
  template:`<div class="app">
    <counter></counter>
   </div>  
  `
})

通过在根实例中注入store选项,该store实例会注入到根组件下的所有子组件,且子组件可以通过this.$store访问到

const Counter = {
  template :`<div>{{count}}</div>`,
  computed:{
    count(){
      return this.$store.state.count
    }
  }
}
3.什么是mapState辅助函数?做什么用的?

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余,mapState辅助函数就是为了解决这个问题,该辅助函数可以帮我我们生成计算属性,让你少按几次键:

import { mapState } from 'vuex'

export default {
  computed: mapState({
    count:state => state.count,
    countAlias:'count',
    countPlusLocalState(state){
      return state.count + this.localCount
    }
  })
}

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

computed:mapState([
//映射this.count为store.state.count
  'count'
])

Getter

1.什么getter?做什么用的?怎么用?

getter相当于store的计算属性,getter的返回值会根据它的依赖被缓存起来,且只有当他的依赖值发生变化时才会被重新计算

有时候我们需要从state中派生一些状态,例如对列表进行过滤

Getter接受state作为其第一个参数:

const store = new Vuex.Store({
  state:{
    todos:[
      {id:1,text:'111',done:true},
      {id:2,text:'222',done:false}
    ]
  },
  getters:{
    doneTodos:state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

2.如何通过属性访问?

Getter会暴露为store.getters对象,你可以以属性的形式访问这些值:

store.getters.doneTodos

Getter也可以接受其他getter作为第二个参数

getters:{
  doneTodoCount: (state,getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount 

我们可以很容易在任何组件使用它:

computed:{
  doneTodosCount(){
    return this.$store.getters.doneTodosCount
  }
}
注意:getter在通过属性访问时是可以作为vue的响应式系统的一部分缓存其中的


3.如何通过方法访问

你也可以通过getter返回一个函数,来实现给getter传参,在你对store里的数组进行查询时非常有用

getters:{
  getTodoById:(state)=>(id){
    return state.todos.find(todo => todo.id => id)
  }
}

4.mapGetters辅助函数是做什么用的?怎么用?

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

import { mapGetters } from 'vuex'

export default {
  computed:{
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter'
    ])
  }
}
Mutation

1.Mutation的用处?怎么用?

Mutation是更改vuex的store中的状态的唯一方法。用于更改store状态,类似于事件:首先在store中加入选项,每个mutation都有一个字符串的事件类型和一个回调函数(handler)。这个函数就是我们实际更改状态的地方,默认传入state作为参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutation:{
     increment(state){
      state.count++ //变更状态
    }
  }
})

你不能直接调用一个mutation handler。这个选项更像是事件注册:当触发一个类型为increment的mutation时,调用此函数。要唤醒一个mutation handler,你需要以相应的type调用store.commit方法

store.commit("increment")

2.什么是提交载荷(payload)?怎么用?

提交载荷是向store.commit传入的一个额外参数,可以是字符串,也可以是对象(大多数情况为对象)

mutations: {
//在store中注入mutations选项,并注册increment方法
increment: (state, payload) { state.count += payload.amount }}store.commit('increment', {//子组件中提交 amout:10})

3.对象风格的提交方式

store.commit({//此处整个对象都作为载荷传给mutation函数
  type:"increment",
  amount:10
})

4.Mutation需遵守Vue的响应规则

既然vuex中store的状态是响应式的,那么当我们变更状态时,监视状态的vue组件也会自动更新

(1)最好提前在你的store中初始化好所有所需属性

(2)当需要在对象上添加新属性时,可以使用

Vue.set(obj,"newProp",123)

5.使用常量替代Mutation事件类型?

把常量放在单独的文件中

//mutation-types.js
export const SOME_MUTATION = 'some_mutation'

//store.js
import Vuex from 'vuex'
import {SOME_MUTITION} from './mutation-types'

const store = new Vuex.Store({
  state:{...},
  mutations: {
    [SOME_MUTATION] (state) {
  
   }
  }
})

6.Mutation必须是同步函数

7.如何在组件中提交Mutation?

(1)在组件中使用this.$store.commit("xxx")提交

(2)使用mapMutation辅助函数将组件中的methods映射为store.commit调用

import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations([
      'increment',
      'incrementBy',
      add: 'increment'
    ])
  }
}

Action

1.Action类似于mutation,不同在于:

(1)action提交的是mutation,而不是直接变更状态

(2)action可以包含任意异步操作

const store = new Vue.Store({
  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

2.如何分发action

action通过store.dispatch方法触发:

store.dispatch('increment')

我们可以在store内部执行异步操作

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

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

//以载荷形式分发
store.dispatch('incrementAsync',{
  count: 0
])

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

3.如何在组件中分发action

(1)使用this.$store.dispatch("xxx")分发

(2)使用mapActions辅助函数将组件的methods映射到store.dispatch

import {mapActions} from "vuex"
export default {
  methods: {
    ...mapActions([
      'increment',
      'incrementBy'
    ])
  }
}

4.什么是组合action?如何组合action?

action通常是异步的,store.dispatch可以处理被触发的action的处理函数返回的promise,并且store。dispatch仍旧返回promise

actions: {
  actionA({commit}) {
    return new Promise(() => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      },1000)
    })
  }
}

现在你可以:

store.dispatch('actionA').then(() => {
  //...
})

在另一个action中也可以:

actions: {
  actionB({dispatch, commit}){
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
})
  }
}

Module

1.module作何用?怎么用?

由于使用单一状态树,应用所有状态集中到一个对象,当应用非常复杂时,store对象变的非常臃肿,为了解决这个问题,我们将store分割成module(模块)

const moduleA = {
  state: {...},
  mutations: {...},
  actions: {...},
  getters: {...},
}
const moduleB = {
  state: {...},
  mutations: {...},
  actions: {...},
  getters: {...},
}
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a
store.state.b

2.什么是模块的局部状态?

模块的局部状态即state

对于模块内部的mutation和getter,接收的第一个参数是模块的局部状态

const moduleA = {
  mutations: {
    increment (state) {//state就是模块的局部状态
     state.count++
    }
  },
  getters: {
    doubleCount (state) {
      return state.count*2
    }
  }
}

同样,对于模块内部的action,局部状态通过context.state暴露出来,根节点状态则为context.rootstate:

const moduleA = {
  actions: {
    incrementIfOddOnRootSum ({state,commit,rootstate}) {
      if((state.count + rootState.count %2 ===1)){
        commit('increment')
      }
    }
  }
}

对于模块内部的getter,根节点状态会作为第三个参数暴露出来

const moduleA = {
  getters: {
    sumWithRootCount (state,commit,rootState) {
      return state.count + rootState.count
    }
  }
}

3.命名空间

默认情况下,模块内部的action,mutation,getter,是注册在全局命名空间的,这样使得多个模块能够同一mutation或action做出响应

如果你希望你的模块具有更高的封装度和复用性,你可以通过添加namespaced:true的方式使其成为带命名空间的模块。当模块被注册后,他的getter,action及mutation都会自动根据模块注册的路径调整命名

const store = new Vue.Store({
  modules: {
    account: {
      namespaced: true,
      state:{},
      getters:{},
      modules: {
        myPage: {
          state:{}
        }
      }
    }
  }
})

4.在带命名空间的模块内访问全局内容(global assets)

如果你希望使用全局state和getter,rootState和rootGetter会作为第三和第四参数传入getter,若需要在全局命名空间内分发action或提交mutation,将{root:true}作为第三参数传给dispatch或commit即可

modules: {
  foo: {
    namespaced: true,
    getters: {
      someGetter (state,getters,rootState,rootGetter) {
         getters.someOtherGetter //foo/someOtherGetter
         rootGetter.someOtherGetter //someOtherGetter
      },
      someOtherGetter: state => {...}
    },
    actions: {
      someAction ({dispatch,commit,getters,rootGetters}) {
        getters.someGetter //foo/someGetter
        rootGetters.someGetter //someGetter
        dispatch('someOtherAction')//foo/someOtherAction
        dispatch('someOtherAction',null,{root:true})
        commit('someMutation')//foo/someMutation
        commit('someMutation',null,{ root: true})
      }
    }
  }
}

5.如何在带命名空间的模块注册全局action?能解决什么问题?

若需要在带命名空间的模块注册全局action,你可添加root:true,并将这个action的定义放在函数handler中

modules: {
  foo: {
    namespaced: true,
    action: {
      someAction: {
        root: true,
        handler (namespaceContext, payload) {
        }
      }
    }
  }
}

6.带命名空间的绑定函数


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值