本文共 12883 字,大约阅读时间需要 42 分钟。
官方文档:
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
流程:
核心概念:
使用Vue CLI 创建项目的时候,如果选择了 Vuex,会自动生成 Vuex 代码的基本结构。
// src/store/index.jsimport Vue from 'vue'// 导入 Vueximport Vuex from 'vuex'// 注册插件Vue.use(Vuex)// 创建并导出 Vuex中的 Store 对象并导出export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } // getters})
// src/main.jsimport Vue from 'vue'import App from './App.vue'import router from './router'// 导入 store 对象import store from './store'Vue.config.productionTip = falsenew Vue({ router, // 创建 Vue 实例时传入 store 选项 // store 选项会被注入到 Vue 实例中:this.$store store, render: h => h(App)}).$mount('#app')
State 是单一状态树,用一个对象存储了全部的应用层级状态(所有的状态数据)。
并且 State 是响应式的。
在仓库(Store)中设置的状态都可以在组件中直接使用,获取数据时,直接从 State 中获取。
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, mutations: { }, actions: { }, modules: { }})
// src/app.vueVuex - demo
count:{ {$store.state.count}} msg:{ {$store.state.msg}}
Vuex 内部提供了 mapState 函数,自动生成状态对应的计算属性。
// src/app.vueVuex - demo
count:{ { count }} msg:{ { msg }}
mapState 还可以接收对象作为参数,使用对象方式可以修改生成的计算属性的名称(解决命名冲突):
// src/app.vueVuex - demo
count:{ { num }} msg:{ { message }}
Vuex 中的 Getter 类似计算属性,可以对 state 中的数据进行处理再展示。
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { }, actions: { }, modules: { }})
// src/app.vueVuex - demo
count:{ { num }} msg:{ { message }}Getter
reverseMsg:{ {$store.getters.reverseMsg}}
它同样有简化的函数:mapGetters,使用和 mapState 类似。
// src/app.vueVuex - demo
count:{ { num }} msg:{ { message }}Getter
reverseMsg:{ {reverseMsg}}
Vuex 约定,更改 store 中状态的唯一方法是提交 Mutation,目的是方便在 devtools 中调试。
Mutation 必须是同步执行的,这样可以保证能够在 Mutation 中收集到所有的状态修改。
不要在 Mutation 中执行异步操作修改 State,否则调试工具无法正常的观测到数据的变化。
如果想要执行异步的操作,需要使用 Action。
Vuex 中的mutation非常类似于事件:每个mutation都有一个字符串的 事件类型(type) 和一个 回调函数(handler)。
这个回调函数就是我们实际进行状态更改的地方,它接收两个参数:
调用 Store 中的 Mutation 类似于事件,它需要通过 $store.commit 提交,它接收:
使用 mutation 改变状态的好处是:集中的一个位置对状态修改,不管在什么地方修改,都可以追踪到状态的修改。可以实现高级的 time travel 调试功能。
可以通过 mapMutations 把 Mutation 映射到组件的 methods 中,mapMutations 返回的方法封装了 commit 的调用。
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { }, modules: { }})
// src/app.vueVuex - demo
count:{ { num }} msg:{ { message }}Getter
reverseMsg:{ {reverseMsg}}Mutation
打开安装好的 chrome 浏览器插件: vue devtools,查看 Vuex:
mutation 每次提交记录中提供3个操作,目的是为了方便调试,从右向左依次是:
在Action中可以执行异步操作,在异步操作之后,如果需要修改状态,可以通过提交 Mutation 来修改 State(所有的状态更改都要通过 Mutation)。
Action 接收两个参数:
调用 Store 中的 Action 和 通过 commit 调用 Mutation 一样,它需要通过 $store.dispatch 提交,它接收:
可以通过 mapActions 把 Action 映射到组件的 methods 中,mapActions 返回的方法封装了 dispatch的调用。
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { increateAsync (context, payload) { setTimeout(() => { context.commit('increate', payload) }, 1000) } }, modules: { }})
// src/app.vueVuex - demo
count:{ { num }} msg:{ { message }}Getter
reverseMsg:{ {reverseMsg}}Mutation
Action
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。
当应用变得非常复杂时,store 对象就有可能变得非常臃肿。
Vuex 可以把 单一状态树拆(store )分成多个模块(module)。
每个模块都可以拥有自己的 state、mutations、actions、getters,甚至嵌套子模块。
定义模块:
// src/store/products.jsconst state = { products: [ { id: 1, title: 'iPhone 11', price: 8000 }, { id: 1, title: 'iPhone 12', price: 10000 } ]}const getters = { productCount (state) { return state.products.length }}const mutations = { setProducts (state, payload) { state.products = payload }}const actions = { }export default { state, getters, mutations, actions}
// src/store/cart.jsconst state = { }const getters = { }const mutations = { }const actions = { }export default { state, getters, mutations, actions}
注册模块:
// src/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'// 导入模块import products from './products'import cart from './cart'Vue.use(Vuex)export default new Vuex.Store({ state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { increateAsync (context, payload) { setTimeout(() => { context.commit('increate', payload) }, 1000) } }, // 注册模块 modules: { products, cart }})
注册完(可以打印 store 看看):
$store.state.[模块名].[模块中state的属性名]
方法_mutations
和 _actions
中 _mutations
和 _actions
以对象形式存放 Store 中所有的 mutations 和 actions // src/app.vueVuex - demo
Module
products:{ {$store.state.products.products}} products count:{ {$store.getters.productCount}}
当前所演示的模块中的 mutations、actions、getters 都直接存储在 Store 中。
如果想要模块有更好的封装度和复用性,可以给模块开启命名空间。
将来在视图中使用模块中的成员的时候,看起来更清晰一些。
推荐使用命名空间的用法。
在模块导出的对象中,通过namespacing
开启命名空间。
// src/store/cart.jsconst state = { }const getters = { }const mutations = { }const actions = { }export default { // 开启命名空间 namespaced: true, state, getters, mutations, actions}// src/store/products.js 一样
打印 Store 对比,发现 mutations、actions、getters 中模块的成员名都添加了模块名/
作为前缀:
在视图中通过 mapXxxx 方法映射模块的成员:
// src/app.vueVuex - demo
Module
products:{ {products}} products count:{ {productCount}}
Vuex 约定所有状态的变更都应该通过提交 Mutation。
但是语法上,可以直接访问和修改 $store.state 中的属性。
这破坏了Vuex的约定。
如果在组件中直接修改 state,devtools 无法跟踪到状态的修改。
开启严格模式之后,在组件中直接修改 state ,虽然还是会生效,但是会抛出错误:
[vuex] do not mutate vuex store state outside mutation handlers.# 不要在 mutation 之外修改 vuex 中的状态
在创建 Store 的时候通过 strict 选项开启:
// 创建并导出 Vuex中的 Store 对象并导出export default new Vuex.Store({ strict: true, // ...})
{ {msg}}
注意:不要在生产环境开启严格模式。
严格模式会深度检查状态树,检查不合规的状态改变,会影响性能
可以在开发环境启用严格模式,在生产环境关闭严格模式。
export default new Vuex.Store({ // 打包时根据环境变量进行处理 // npm run serve 时 NODE_ENV = development // npm run build 时 NODE_ENV = production strict: process.env.NODE_ENV !== 'production')}
以购物车为例,用户的购物车商品信息是记录在本地 localStorage 中,以保证刷新页面,还能保留记录。
这样就需要在每个修改(增、删等)购物车商品信息的 mutation 中更新 localStorage 中的数据。
可以使用 Vuex 的插件简化这个操作。
例如:
const myPlugin = store => { // 当 store 初始化后调用 store.subscribe((mutation, state) => { // subscribe 用于订阅 mutation // 注册的回调函数,会在每个 mutation 完成之后调用 // mutaion -> {type, payload} // type:'命名空间/mutaion的名字' // payload:mutation接收的参数 // state:store的state,不是模块的 })}
Vuex 模块包含:
// src/myvuex/index.jslet _Vue = nullclass Store { }function install (Vue) { _Vue = Vue}export default { Store, install}
install 的工作是把创建Vue实例时传入的 store 对象,注入到Vue原型上的$store。
在所有组件中都可以通过 this.$store 获取到Vuex中的仓库,从而共享状态。
let _Vue = nullfunction install (Vue) { _Vue = Vue // 在install中获取不到Vue的实例 // 所以需要混入 beforeCreate 来获取Vue实例 // 从而拿到选项中的 store 对象 _Vue.mixin({ beforeCreate () { // 判断$options中是否有store选项 // 组件实例没有store,不需要处理 if (this.$options.store) { _Vue.prototype.$store = this.$options.store } } })}
初始化:
class Store { constructor(options) { const { state = { }, getters = { }, mutations = { }, actions = { } } = options // 对 state 进行响应式处理 this.state = _Vue.observable(state) // this.getters 不需要原型 this.getters = Object.create(null) // 将 getters 中的方法转换成 this.getters 对象的get 范文其 Object.keys(getters).forEach(key => { Object.defineProperty(this.getters, key, { // 获取 getters 中方法的执行结果 get: () => getters[key](state) }) }) // mutations和actions 在commit 或 dispatch 方法中获取 // 所以它们应该是内部私有成员,下划线前缀标识私有,不希望外部访问 this._mutations = mutations this._actions = actions } commit (type, payload) { this._mutations[type](this.state, payload) } dispatch (type, payload) { this._actions[type](this, payload) }}
到此Vuex模拟完成,替换vuex的模块地址为 myvuex的地址,可以测试效果。
转载地址:http://dfzp.baihongyu.com/