vue学习之vuex

 

父组件通过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)、保存状态快照、历史回滚/时光旅行的先进的调试工具。

 

posted @ 2019-02-11 17:28  中二的羊  阅读(365)  评论(0编辑  收藏  举报