VueX 学习(一)

648 阅读6分钟

vuex学习

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

安装VueX

我们一般就是npm 安装或者 yarn 安装VueX

npm安装

npm install vuex --save

yarn安装

yarn add vuex

使用Vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

Promise

Vuex依赖Promise,如果浏览器并没有实现Promise(如IE),我们班可以使用一个polyfill的库解决这个问题

安装polyfill

npm install es6-promise --save # npm
yarn add es6-promise # Yarn

使用

import 'es6-promise/auto'

每一个Vuex 应用的核心是store(仓库)。“store”基本上是一个容器,它包含着应用中大部分的状态

  1. Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么响 应的组件也会响应的得到更新。
  2. 我们不能直接改变store中的状态。改变stroe中的状态的唯一途径就是显式的提交(commit)mutation

state 单一状态树

创建一个state用来定义要使用的状态数据 当一个组件需要获取多个状态的时候我们可以使用mapState辅助函数帮助我们生成计算属性

使用

import {mapState} from 'vuex'

export default {
	computed: mapState({
		count: state => state.count
	})
}
// 或者我们可以用对象展开运算符
computed: {
	...mapState({
	})
}

Getter

Vuex允许我们在store中定义’getter‘(一种store的计算属性)。getter的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算。

一般写法
// Getter 会暴露为 store.getters 对象我们可以以属性的形式访问这些值
store.getters.doIt

getters: {
	doSomething: (state,getter) => {
		return getter.doIt
	}	
}

在组件中使用getter

computed: {
	doSomething () {
		return this.$store.getters.doSomething
	}
}

通过方法访问

我们可以通过让getter返回一个函数来实现给getter传参。在我们对store里的数组进行查询是非常有用。

官方的例子
getters:{
	getTodoById: (state) => (id) => {
		return state.todos.find(todo => todo.id === id)
	}
}

store.getters.getTodoById(2) // { id: 2, text: '...', done: false }

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

mapGetters 辅助函数

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

import {mapGetters} from 'vuex'

export default{
	computed: {
		...mapGetters([
			'doSomething'
		])
	}
}
// 如果我们想将一个getter属性另取一个名字,使用对象形式
mapGetters({
	// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
	doneCount: 'doneTodosCount'
})

Mutation

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

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

我们不能直接调用一个mutation handler。我们要唤醒一个mutation handler,我们需要以相应的type调 用store.commit方法:

store.commit('increment')

提交载荷(Payload)

我们可以向store.commit 传入额外的参数,即mutation的载荷(payload)

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

store.commit('increment',10)

大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的mutation会更易读

mutations: {
	increment (state,value){
	state.count = state.count + value.amount
	}
}

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

对象风格的提交方式

提交mutation的另一种方式是直接使用包含type属性对象

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

Mutation需遵守Vue的响应规则

既然Vuex的store中的状态是响应式的,那么当我们变更状态时,监视状态的Vue组件也会更新。 这意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项:

  1. 最好提前在你的store中初始化所有所需属性。
  2. 当需要在对象上添加新属性时,我们应该:
  • 使用Vue.srt(obj,'newProp',123)或者
  • 以新对象替换老对象。例如,利用stage-3 的对象展开运算符我们可以写成这样:
state.obj = { ...state.obj,newProp:123}

使用常量替代Mutation事件类型

使用常量替代mutation

export const SOME_MUTATION = 'SOME_MUTATION'

import Vuex from 'vuex'
import {SOME_MUTATION} from './type'

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

Mutation 必须是同步函数

mutations (state) {
	api.callAsyncMethod(() => {
		state.count++
	})
}

在组件中提交Mutation 我们可以在组件中使用this.$store.commit('xxx') 提交mutation, 或者使用mapMutations 辅助函数 将组建中的methods映射为store.commit调用

import {mapMutations} from 'vuex'

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

Action

Action 类似于 mutation 但是不同 不同在于:

  • Action 提交的是mutation,而不是直接变更状态
  • Action 可以包含任意异步操作
注册一个简单的action
const store = new Vuex.Store({
	state:{
		count: 0
	},
	mutations: {
		increment(state){
			state.count++
		}
	},
	actions: {
		increment(context) {
			context.commit('increment')
		}
	}
})

分发Action

Action通过 store.dispatch 方法触发

store.dispatch('increment')

与mutation必须同步执行这个限制不同!Action就不受到约束,我们可以在action内部执行异步操作 Action 同样支持载荷方式和对象方式进行分发

store.dispatch('increment'{amount:10})

在组建中分发Action

我们可以在组件中使用this.$store.dispatch('xxx') 分发action,或者使用mapActions辅助函数将 组件的methosd映射为store.dispatch调用

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

组合Action

Action 通常是异步的,我们为了获取action什么时候结束 我们可以这样写

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

我们如果利用async/await 我们可以这样组合action:

	//假设getData() 和 getOtherData() 返回的是Promise
	actions: {
	  async actionA ({ commit }) {
		commit('gotData', await getData())
	  },
	  async actionB ({ dispatch, commit }) {
		await dispatch('actionA') // 等待 actionA 完成
		commit('gotOtherData', await getOtherData())
	  }
	}

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就 有可能变得相当臃肿。为了解决以上问题,Vuex允许我们将store分割成模块。每个模块都拥有自己的state、 mutation、action、getter、甚至是嵌套子模块——————从上到下进行同样方式的分割:

const moduleA = {
	state: {....},
	mutations:{...},
	actions:{...},
	getters:{...}
}
const moduleB = {
	state: {....},
	mutations:{...},
	actions:{...},
	getters:{...}
}

store.state.a // moduleA
store.state.b // moduleB

模块的局部状态

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

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

命名空间

默认情况下,模块内部的action、mutation和getter是注册在全局命名空间(这样使得多个模块能够对同一mutation或action做出响应) 如过我们希望我们的模块具有更高的封装度和复用性,我们可以通过添加 namespaced:true 的方式使其成为带命名空间的模块。

例子
const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 模块内容(module assets)
      state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 嵌套模块
      modules: {
        // 继承父模块的命名空间
        myPage: {
          state: { ... },
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // 进一步嵌套命名空间
        posts: {
          namespaced: true,

          state: { ... },
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

在命名空间的模块注册全局action

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

例如

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

带命名空间的绑定函数

当时用当使用 mapState,mapGetters,mapActions和 mapMutations这些函数绑定带命名空间的模块时。写起来可能比较繁琐:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

我们可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名 空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

给插件开发者的注意事项

如果我们开发的插件提供了模块并允许用户将其添加到Vuex store, 可能需要考虑模块的空间名称问题。对于这种情况,我们可以通过 插件的参数对象来允许用户指定空间名称

// 通过插件的参数对象得到空间名称
// 然后返回 Vuex 插件函数
export function createPlugin (options = {}) {
  return function (store) {
    // 把空间名字添加到插件模块的类型(type)中去
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

模块动态注册

在store创建之后,我们可以使用 store.registerModule 方法注册模块:

// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

我们可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。

模块重用

我们有时候我们可能需要创建一个模块的多个实例,

  • 创建多个store,他们公用同一个模块(例如当 runInNewContext 选项是false 或 'once' 时,为了在服务端渲染中避免有状态的单例)
  • 在一个store中多次注册同一个模块