vuex是什么?
vuex 是一个专为 vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex 也集成到vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel调试、状态快照导入导出等高级调试功能。
安装
直接下载cdn 引用
<script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script>
npm
npm install vuex --save
在一个模块化的打包系统中,您必须显式地通过vue.use() 来安装vuex。
import vue from 'vue' import vuex from 'vuex' vue.use(vuex)
vuex 是一个专为vue.js 应用程序开发 的状态管理模式,集中式存储管理应用的所有组件状态。
状态管理包含以下几个部分状态:
state 驱动应用的数据源;
view 以生命方式将 state 映射到视图。
actions 响应在view 上的用户书输入导致的状态变化。
帮助我们管理共享状态,中大型单页面应用。
state
单一状态树 ,vuex使用单一状态树用一个对象就包含了全部的应用层级状态。
在vue 组件中获得vuex 状态。
由于vuex的状态存储是响应式的,从store 实例中读取状态最简单的方法
就是在计算属性中返回某个状态。
创建一个counter 组件
const counter = { template: '<div>{{ count }}</div>' computed: { count (){ return store.state.count } } }
每次 store.state.count 变化的时候,都会重新求取计算属性,并且触发更新相关的dom
vuex 通过 store 选项,提供了一种机制将状态从根组件『注入』到每一个子组件中(需调用 vue.use(vuex)):
const app = new vue({ el:'#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所 有的子组件 store, components: {counter}, template: ' <div class="app"> <counter></counter> </div> ' })
通过在根实例中注册 store 选项,该store 实例会注册入到跟组件下的所有子组件,且子组件能通过 this.$store 访问到。更新 counter 的实现:
const counter = { template : '<div>{{ count }}</div>', computed: { count this.$store.state.count } }
mapstate 辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些冗余。
为了解决这个问题,我们可以使用 mapstate 辅助函数帮助我们生成计算属性。
// 在单独构建的版本中辅助函数为 vuex.mapstate import { mapstate } from 'vuex' export default { computed: mapstate({ // 箭头函数可以使代码更简洁 count: state => state.count, // 传字符串参数 ‘count' 等同于 ‘state => state.count' countalias: 'count', // 为了能够使用 ‘this' 获取局部状态,必须使用常规函数 countpluslocalstate(state) { return state.count + this.localcount } }) }
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapstate 传一个字符串数组。
computed: mapstate([ // 映射 this.count 为 store.state.count 'count' ])
组件仍然保有局部状态。
getters
有时候我们需要从store 中的state 中 的state 中派生一些状态,列如对列表进行过滤并计算。
computed: { donetodoscount() { return this.$store.state.todos.filter(todo => todo.done).length } }
vuex 允许我们再store 中定义getters (可以认为是stroe 的计算属性)
getters 接受state 作为其第一个参数。
const store = new vuex.store({ state: { todos:[ {id:1, text: '...' ,done: true}, {id:2,text:'...',done: false} ] }, getters: { donetodos: state => { return state.todos.filter(todo=> todo.done) } } })
getters 会暴露为store.getters 对象:
store.getters.donetodos // [{id:1,text: '...',done:true}]
getter 也可以接受其他getters 作为第二个参数:
getters: { donetodoscount: (state,getters) => { return getters.donetodos.length } } store.getters.donetodoscount // -> 1
我们可很容易的在任何组件中使用
computed: { donetodoscount() { return this.$store.getters.donetodoscount } }
mapgetters 辅助函数
mapgetters 辅助函数仅仅是 store 中的getters 映射到局部计算属性。
import {mapgetter} form 'vuex' export default { computed: { // 使用对象展开运算符将 getters 混入 ...mapgetters([ ‘donetodoscount', 'anothergetter' ]) } }
如果你想讲一个getter 属性另取名字,使用对象性时
mapgetters({ // 映射 this.donecount 为 store.getters.donetodoscount donecount: 'donetodoscount' })
mutations
更改vuex 的store 中的状态的唯一方式就是提交 mutation vuex 中的mutation
非常类似于事件,每个 mutation 都有一个字符串的 事件类型 和回调函数。这个回调函数就是我们实际进行状态更改的地方。并且他会接受 state 作为第一个参数。
const store = new vue.store({ state: { count: 1 }, mutations: { inctement (state) { state.count++ } } })
当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个
mutation handler,你需要以相应的 type 调用 store.commit 方法
store.commit('increment')
提交载荷(payload)
你可以向store.commit 传入额外的参数,即mutation 的载荷:
mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10)
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录 mutation会更易读。
mutations: { increment (state,payload) { state.count += payload.amount } } store.commit('increment', { amount:10 })
对象风格的提交方式
提交mutation 的另一种方式直接使用包含 type 属性的对象:
store.commit({ type: 'increment', amount:10 })
当使用对象风格的提交方式,整个对象作为载荷传给mutation 函数,因此handler保持不变:
mutations: { increment (state, payload) { state.count += payload.amount } }
mutations 需遵守vue 的响应规则
既然vuex的store 中的状态是响应式的,那么当我们变更状态时,监视状态的vue更新 ,这也意味值vue 中的mutation 也需要与使用 vue 一样遵守一些注意事项。
1. 最好提前在你的store 中初始化好所有的所需要的属性。
2.当需要在对象上提交新属性时,你应该使用
vue.set(obj, 'newprop', 123)
使用新对象代替老对象 state.obj= {...state.obj ,newprop: 123}
使用常量替代 mutation 事件类型
使用常量替代 mutation 事件类型在各种 flux 实现中是很常见的模式
export const some_mutation = 'some_mutation'; import vuex from 'vuex' import {some_mutation } from './mutation-types' const store = new vuex.store({ state: {...} mutations: { // 我们可以使用 es2015 风格的计算属性命名功能来使用一个常量作为函数名 [some_mutation] (state) { // mutate state } } })
mutation 必须是同步函数
一条重要的原则是记住 mutation 必须是同步函数。
mutations: { somemutation (state) { api.callasyncmethod(() => { state.count++ }) } }
在组件中提交 mutations
你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使使用 mapmutations辅助函数将组建中的methods 映射为 store.commit 调用 (需要在根节点注入 store)
import {mapmutations} from 'vuex' expor default { methods: { mapmutations([ methods: { mapmutations([ 'increment' // 映射 this.increment() 为 this.$store.commit('increment') ]), mapmutations({ add: 'increment' // 映射 this.add() 为 this.$store.commit('increment') }) } ]) } }
actions
在mutation 中混异步调用会导致你的程序很难调试。
actions
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 函数接受一个与store 实例具有相同方法和属性的context 对象,因此你可以调用 context.commit 提交一个mutation,或者通过 context.state 和context.getters 来获取 state 和 getters 当我们在之后介绍到modules时,你就知道 context 对象为什么不是store 实例本身了。
actions: { increment({commit}){ commit('increment') } }
分发 action
action 通过 store.dispatch 方法触发:
store.dispatch('increment')
我们可以在 action 内部执行异步操作。
actions: { incrementasync({commit}){ settimeout(() => { commit('increment') },1000) } }
actions 支持同样的载荷方式和对象方式进行分发
// 以载荷形式分发 store.dispatch('incrementasync',{ amount:10 }) // 以对象形式分发 store.dispatch({ type: 'incrementasync', amount:10 })
在组件中分发 action
你在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用map actions辅助函数将组件的methods 映射为store.dispatch 调用
import {mapactions } from 'vuex' export default{ methods:([ 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment') ]) mapactions({ add: 'inctement' // 映射 this.add() 为 this.$store.dispatch('increment') }) }
组合 actions
action 通常是异步的,那么如何知道 action 什么时候结束。
你需要明白 store.dispatch 可以处理被处触发的action 的回调函数返回的promise并且 store.dispatch 仍旧返回promise
actions: { actiona({commit}){ return new promise((resolve)=>{ settimeout (() => { commit('somemutation') resolve() },1000) }) } }
现在你可以
store.dispatch('actiona').then(()=>{ //... })
在另一个 action 中也可以
actions: { actionb({dispath,commit}){ return dispatch('actiona').then(() => { commit('someothermutation') }) } }
我们利用async/ await
// 假设 getdata() 和 getother() 返回的是一个 promis actions:{ async actiona ({commit}){ commit('gotdata',await getdata()) }, async actionb({dispatch,commit}){ await dispatch('actiona') // 等待 actiona 完成 commit('gootherdata', await getotherdata()) } }
modules
使用单一状态树,当应用变的很大的时候,store 对象会变的臃肿不堪。
vuex 允许我们将store 分割到模块。每一个模块都有自己的state, mutation,action, getters, 甚至是嵌套子模块从上到下进行类似的分割。
const modulea = { state: {...}, mutations: {...} actions: {...} getters:{...} } const modulea = { state: {...}, mutations: {...} actions: {...} } const store = new vuex.store({ modules: { a:modulea, b:moduleb } }) 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, context.state 是局部状态,根节点的窗台石context.rootstate:
const modulea = { actions: { incrementifoddonrootsum ({state, commit ,rootstate}) { if((state.count + rootstate.count) %2 ===1){ commit('increment') } } } }
对于模块内部的getter,跟节点状态会作为第三个参数:
const modulea = { getters: { getters: { sumwithrootcount (state,getters,rootstate) { return state.count + rootstate.count } } } }
命名空间
模块内部的action, mutation , 和 getter 现在仍然注册在全局命名空间 这样保证了多个模块能够响应同一 mutation 或 action. 也可以通过添加前缀 或者 后缀的
方式隔离各个模块,以免冲突。
// 定义 getter, action , 和 mutation 的名称为常量,以模块名 ‘todo' 为前缀。 export const done_count = 'todos/done_count' export const fetch_all = 'todos/fetch_all' export const toggle_done = 'todos/toggle_done' import * as types form '../types' // 使用添加了解前缀的名称定义, getter, action 和 mutation const todosmodule = { state : {todo: []}, getters: { [type.done_count] (state) { } } actions: { [types.fetch_all] (context,payload) { } }, mutations: { [type.toggle_done] (state, payload) } }
模块动态注册
在store 创建之后,你可以使用 store.registermodule 方法注册模块。
store.registermodule('mymodule',{})
模块的状态将是 store.state.mymodule.
模块动态注册功能可以使让其他vue 插件为了应用的store 附加新模块
以此来分割vuex 的状态管理。
项目结构
vuex 并不限制你的代码结构。但是它规定了一些需要遵守的规则:
1.应用层级的状态应该集中到单个store 对象中。
2.提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
3.异步逻辑应该封装到action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation、和 getters 分割到单独的文件对于大型应用,我们会希望把 vuex 相关代码分割到模块中。下面是项目结构示例
├── ├── main.js ├── api │ └── ... # 抽取出api请求 ├── components │ ├── app.vue │ └── ... └── store ├── index.js # 我们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块
您可能感兴趣的文章:
如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!
网友评论