父组件通过props传数据到子组件,子组件通过事件回传数据给父组件,那么几个不相关的组件想共享一个数据,就可以用到vuex。
概念
state
存放要共享的数据。在组件中,state的属性没有直接放在组件的data部分,一般是在computed里,依赖于state属性的改变而更新页面数据。
mutations
定义操作state属性的方法,mutation 非常类似于事件,组件不能直接操作state的属性,需要通过mutations操作,注意:mutation 都是同步事务。
触发 mutation 事件的方式不是直接调用,比如 increment(state) 是不行的,而要通过 store.commit 方法:
store.commit('increment')
actions
通过actions里各方法的commit(‘mutations方法名’),与mutations里的方法关联起来,Vuex 加入了 Action 来处理异步,想法是把同步和异步拆分开,异步操作想咋搞咋搞,但是不要干扰了同步操作。
Vuex 把同步和异步操作通过 mutation 和 Action 来分开处理,是一种方式。但不代表是唯一的方式,还有很多方式,比如就不用 Action,而是在应用内部调用异步请求,请求完毕直接 commit mutation,当然也可以。
页面接收到用户的交互行为分发事件到actions的时候,执行对应的mutations里的方法改变state,,从而更新页面使用到的state数据部分
Getter
Vuex 还引入了 Getter,这个可有可无,只不过是方便计算属性的复用。
实践
大概过程就是:创建一个管理state的文件——导出vuex实例——根实例中注册实例——在组件中使用实例
安装vuex:npm install vuex --save
创建一个store.js文件
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) //声明state const state={ count:1 }
//声明mutations,定义方法操作state的属性 const mutations={ add(state){ state.count++ }, reduce(state){ state.count-- } } //声明actions,通过commit与mutations的方法关联起来 const actions={ onAdd:({commit})=>{//通过解构的形式,使用对应的commit方法 commit('add'); }, onReduce:({commit})=>{ commit('reduce'); } } //导出模块 export default new Vuex.Store({state,mutations,actions})
在组件中使用state
在main.js里引入模块
import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
创建一个使用state数据的组件count.vue
<template lang="html"> <div class="count"> {{$store.state.count}} <button type="button" name="button" @click="onAdd"></button><!--onAdd:methods里的方法--> <button type="button" name="button" @click="onReduce"></button> </div> </template> <script> import {mapActions} from 'vuex' export default{ methods:mapActions([ 'onAdd',//和store.js中action里的increment关联起来 'onReduce' ]) } </script> <style lang="css"> </style>
部分解说:
1、在组件中分发action:可以使用 this.$store.dispatch('actions里的方法名称')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
)
上面代码中,事件使用的都是methods里的方法,直接写method的方法是没法和action关联上的,这里通过mapActions使它的onAdd和action里的onAdd关联了起来。、
2、{{$store.state.count}}中$store来自于main里引入的store。
前面提到过state的属性没有直接放在组件的data部分,一般是在computed里,每当 store.state.count
变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
要是对每个属性进行计算,下面这样的写法就不方便了
const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }
使用mapState函数,mapState 函数返回的是一个对象
当映射的计算属性的名称与 state 的子节点名称相同时,可以给 mapState 传一个字符串数组。 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])
或使用对象展开符混合mapSate的对象到computed里:
computed: { localComputed () { /* ... */ }, // 使用对象展开运算符将此对象混入到外部对象中 ...mapState({ // ... }) }
在App.vue中引入这个组件
<template> <div id="app"> <count/> <router-view></router-view> </div> </template> <script> import count from 'components/count.vue' export default { name: 'App', components:{ count } } </script>
state应用再更复杂的场景
Vuex 单一状态树并不影响模块化,把 State 拆了,最后组合在一起就行。Vuex 引入了 Module 的概念,每个 Module 有自己的 state、mutation、action、getter,其实就是把一个大的 Store 拆开。
比如a,b,c三个页面,a页面由多个组件构成,这些组件共享一个state。b页面也由多个组件构成,这些组件又共享一个state。此时如果只有一个state去管理a和b页面的所有state属性,可能会导致命名冲突或者操作失误,也不方便管理。这个时候就想要他们管理各自的state。
创建一个store目录
a和b.js里面声明各自的state,mutations,actions
//a.js和b.js的代码
const state = { money: 1
//money:10 b的money初始值
} const mutations = { add(state){ state.money++ }, reduce(state){ state.money-- } } const actions = { onAdd: ({commit})=> { commit('add'); }, onReduce: ({commit})=> { commit('reduce'); } } export default { namespaced:true,//开启命名空间 state, mutations, actions }
在index.js中引入a和b再通过modules导出
import Vue from 'vue' import Vuex from 'vuex' import moneya from './modules/a' import moneyb from './modules/b' Vue.use(Vuex) export default new Vuex.Store({ modules:{ moneya, moneyb } })
在main.js中引入index.js
import Vue from 'vue' import App from './App' import router from './router' import store from './store/index' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
在a.vue和b.vue中使用各自的state
a.vue
<template lang="html"> <div> pagea:{{$store.state.moneya.money}}//$store.state和index.js中的modules对应 </div> </template> <script> export default{} </script> <style lang="css"> </style>
b.vue
<template lang="html"> <div> pagea:{{$store.state.moneyb.money}} </div> </template> <script> export default{} </script> <style lang="css"> </style>
在App.vue中加载a和b组件
<template> <div id="app"> <pagea></pagea> <pageb></pageb> </div> </template> <script> import pagea from "./components/a.vue" import pageb from "./components/b.vue" export default { name: 'App', components:{ pagea, pageb } } </script>
结果如下:
那么a和b组件在交互方面怎么样呢?
以a.vue为例,增加两个按钮操作a.js里的money
<template lang="html"> <div> pagea:{{$store.state.moneya.money}} <button type="button" name="button" @click="onAdd">增加</button> <button type="button" name="button" @click="onReduce">减少</button> </div> </template> <script> import {mapActions} from 'vuex' export default{ methods:mapActions('moneya',["onAdd","onReduce"])//注意这里的写法 } </script> <style lang="css"> </style>
点击a组件的按钮,b的money不受影响
补充
给mutations传参
从交互的地方触发action的时候传进来参数。以a.vue和a.js为例
<template lang="html"> <div> pagea:{{$store.state.moneya.money}} <button type="button" name="button" @click="onAdd(2)">增加</button> <button type="button" name="button" @click="onReduce">减少</button> </div> </template> <script> import {mapActions} from 'vuex' export default{ methods:mapActions('moneya',["onAdd","onReduce"]) } </script>
a.js中
const mutations = { add(state,params){ console.log(params); state.money++ }, reduce(state){
state.money-- } } const actions = { onAdd: ({commit},params)=> { commit('add',params);//接收参数 }, onReduce: ({commit})=> { commit('reduce'); } }
结果,点击增加按钮,输出2
关于store的使用
不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。
什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。
父子组件之间,兄弟组件之间共享状态,往往需要写很多没有必要的代码,比如把状态提升到父组件里,或者给兄弟组件写一个父组件。
每一个 Vuex 里面有一个全局的 Store,包含着应用中的状态 State,这个 State 只是组件中共享的数据,不用放所有的 State,没必要。
Vuex通过 store 选项,把 state 注入到了整个应用中,这样子组件能通过 this.$store 访问到 state 了。
在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等。
隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。根据这个思路,产生了很多的模式和库:
比如:Vuex、Flux、Redux、Redux-saga、Dva、MobX
约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到 filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。
关于修改store的state:
需要规定一下,组件不允许直接修改属于 store 实例的 state,组件必须通过 action 来改变 state,也就是说,组件里面应该执行 action 来分发 (dispatch) 事件通知 store 去改变。
因为没有限制组件里面不能修改 store 里面的 state,万一组件瞎胡修改,不通过 action,那我们也没法跟踪这些修改是怎么发生的。
通过action 来分发 (dispatch) 事件通知 store 去改变我们能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。