Vuex的知识点
1.vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
1.2 什么是“状态管理模式”?
这个状态自管理应用包含以下几个部分:
state,驱动应用的数据源;
view,以声明方式将 state 映射到视图;
actions,响应在 view 上的用户输入导致的状态变化。
2.State
2.1单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。
存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,例如状态对象必须是纯粹 (plain) 的
2.2在 Vue 组件中获得 Vuex 状态
那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
创建一个 Counter 组件constCounter={template:`
<div>{{ count }}</div>`,
computed:{count(){returnstore.state.count}}}
2.3mapState 辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
对象展开运算符
mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:
computed:{localComputed(){/* ... */},
// 使用对象展开运算符将此对象混入到外部对象中...mapState({// ...})}
3Getter
3.1通过属性访问
Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
store.getters.doneTodos// -> [{ id: 1, text: '...', done: true }]
2通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters:{// ...getTodoById:(state)=>(id)=>{returnstate.todos.find(todo=>todo.id===id)}}
4 Mutation
mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,
4.1对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({type:'increment',amount:10})
Mutation 需遵守 Vue 的响应规则
既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
最好提前在你的 store 中初始化好所有所需属性
5.Action
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
分发 Action
Action 通过 store.dispatch 方法触发:
store.dispatch('increment')
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:
actions:{incrementAsync({commit}){setTimeout(()=>{commit('increment')},1000)}}
6.Module
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
constmoduleA={state:{...},mutations:{...},actions:{...},getters:{...}}
constmoduleB={state:{...},mutations:{...},actions:{...}}
conststore=newVuex.Store({modules:{a:moduleA,b:moduleB}})
7.项目结构
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
应用层级的状态应该集中到单个 store 对象中。
提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
异步逻辑都应该封装到 action 里面。
8.插件
Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:
constmyPlugin=store=>{// 当 store 初始化后调用store.subscribe((mutation,state)=>{// 每次 mutation 之后调用// mutation 的格式为 { type, payload }})}
8.1在插件内提交 Mutation
在插件中不允许直接修改状态——类似于组件,只能通过提交 mutation 来触发变化。
9.严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true:
conststore=newVuex.Store({// ...strict:true})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
9.2开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
conststore=newVuex.Store({// ...strict:process.env.NODE_ENV!=='production'})
10表单处理
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会比较棘手:
<inputv-model="obj.message">
假设这里的 obj 是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model 会试图直接修改 obj.message。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
用“Vuex 的思维”去解决这个问题的方法是:给 <input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法:
<input:value="message"@input="updateMessage">
...computed:{...mapState({message:state=>state.obj.message})},methods:{updateMessage(e){this.$store.commit('updateMessage',e.target.value)}}
下面是 mutation 函数:
...mutations:{updateMessage(state,message){state.obj.message=message}}
双向绑定的计算属性
必须承认,这样做比简单地使用“v-model + 局部状态”要啰嗦得多,并且也损失了一些 v-model 中很有用的特性。另一个方法是使用带有 setter 的双向绑定计算属性:
<inputv-model="message">
...computed:{message:{get(){returnthis.$store.state.obj.message},set(value){this.$store.commit('updateMessage',value)}}}
11测试
1测试 Mutation
Mutation 很容易被测试,因为它们仅仅是一些完全依赖参数的函数。这里有一个小技巧,如果你在 store.js 文件中定义了 mutation,并且使用 ES2015 模块功能默认输出了 Vuex.Store 的实例,那么你仍然可以给 mutation 取个变量名然后把它输出去:
conststate={...}// `mutations` 作为命名输出对象exportconstmutations={...}exportdefaultnewVuex.Store({state,mutations})
2测试 Action
Action 应对起来略微棘手,因为它们可能需要调用外部的 API。当测试 action 的时候,我们需要增加一个 mocking 服务层——例如,我们可以把 API 调用抽象成服务,然后在测试文件中用 mock 服务回应 API 调用。
3测试 Getter
如果你的 getter 包含很复杂的计算过程,很有必要测试它们。Getter 的测试与 mutation 一样直截了当。
测试一个 getter 的示例:
// getters.jsexportconstgetters={filteredProducts(state,{filterCategory}){returnstate.products.filter(product=>{returnproduct.category===filterCategory})}}
12热重载
1使用 webpack 的 ,Vuex 支持在开发过程中热重载 mutation、module、action 和 getter。你也可以在 Browserify 中使用 插件。
importVuefrom'vue'importVuexfrom'vuex'importmutationsfrom'./mutations'importmoduleAfrom'./modules/a'Vue.use(Vuex)conststate={...}conststore=newVuex.Store({state,mutations,modules:{a:moduleA}})if(module.hot){// 使 action 和 mutation 成为可热重载模块module.hot.accept(['./mutations','./modules/a'],()=>{// 获取更新后的模块// 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`constnewMutations=require('./mutations').defaultconstnewModuleA=require('./modules/a').default// 加载新模块store.hotUpdate({mutations:newMutations,modules:{a:newModuleA}})})}