Vuex

1.什么是Vuex?

官方回答:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。


2.Vuex能解决什么问题?

当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。



3.创建Vuex的步骤:

  • 下载并引用vuex.js文件,插入到vue.js文件后面
  • 创建Vuex实例对象
const store = new Vuex.Store({
       state:{
              msg:"鱿小鱼"
        },
        mutations:{},
        actions:{},
        getters:{}
});
  • 在祖先组件中添加store的key保存Vuex对象; 只要祖先组件中保存了Vuex对象 , 那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据了
Vue.component("father",{
                template:"#father",
                //在祖先组件中添加store的key保存Vuex对象
                store:store,
                data:function(){
                    return{}
                },
                methods:{},
                computed:{},
                components:{}
})
  • 引用vuex中的共享数据方法
<template id="father">
    <div>
        <p>{{this.$store.state.msg}}</p>
        <son></son>
    </div>
</template>

4.State

  • state属性和组件的data属性类似;state作用:用来保存全局共享数据

在引入vuex 以后,我们需要在创建Vuex对象的state中定义变量:

const store = new Vuex.Store({
        // 这里的state就相当于组件中的data, 就是专门用于保存共享数据的
        state:{
            msg:"鱿小鱼"
        }
});

组件中内容:

<div id="app">
    <father></father>
</div>
<template id="father">
    <div>
        <!--4.在使用Vuex中保存的共享数据的时候, 必须通过如下的格式来使用-->
        <p>{{this.$store.state.msg}}</p>
        <son></son>
    </div>
</template>
<template id="son">
    <div>
        <p>{{this.$store.state.msg}}</p>
    </div>
</template>

有了vuex,我们不必在考虑组件之间的传值,直接就可以通过$store来获取不同的数据,,但是如果需要vuex中的多个数据的这时候,这样写就太啰嗦了,我们可以将它定义在computed中。

<template id="son">
    <div>
        <p>{{msg}}</p>
    </div>
</template>
 Vue.component("father", {
        template: "#father",
        // 在祖先组件中添加store的key保存Vuex对象
        // 只要祖先组件中保存了Vuex对象 , 那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据了
        store:store,
        // 子组件
        components: {
            "son": {
                template: "#son",
                computed: {
                    nickname(){
                        return this.$store.state.msg
                    }
                }
            }
        }
 });

这样引入就方便了很多。

  • mapState 辅助函数
    mapState辅助函数作用:可以辅助获取到多个state的值

怎么使用?
1.Vuex实例对象部分:

state:{
  msg:"鱿小鱼"
}

2.在.vue组件中引入,在js块中引入

import { mapState } from 'vuex'

3.在vue组件computed中定义一个数组

 computed:{ 
    /*通过mapState将 store 中的  state 的全局共享数据名称 “映射”到“局部计算属性”*/
    ...mapState([  //mapState本是一个函数,在里面写一个数组,记得加...
        'msg'  //存的数据,把 `this.msg` 映射为 `this.$store.state.msg`
    ])
}

这一句代码就相当于下面这句:

computed:{
    msg(){
        return this.$store.state.msg
    }
}

4.然后就可以不用$store.state.msg引用了,直接插值

{{msg}}

5.Getters

  • getters和computed相似;getters的作用:用来保存获取共享数据的计算属性方法
    就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

补充:

  • 什么是计算属性?
     将计算结果缓存起来,只要数据没有发生变化就只会计算一次,以后使用的都是缓存>起来的数据
  • 函数和计算属性的区别?
     函数的特点:每次调用都会执行
     计算属性的特点:只要返回的结果没有发生变化,那么计算属性只会被执行一次
data:{
           message:"abcdefg"
       },
       //5.专门用于储存监听事件回调函数
       methods:{
           /*函数的特点:每次调用都会执行*/
           msg1(){
               console.log("msg1被执行了");
               let res = this.message.split('').reverse().join('');
               return res;
           }
       },
       //6.专门用于定义计算属性的
       computed: {
           /*计算属性的特点:只要返回的结果没有发生变化,那么计算属性只会被执行一次
            计算属性应用的场景:由于计算属性会将返回的结果缓存起来
                              如果返回的数据不会频繁的发生改变,
                              使用计算属性会比函数的性能高
           */
           // 计算属性的 getter
           reversedMessage: function () {
               console.log("reversedMessage被执行了");
               // `this` 指向 vm 实例
               let res = this.message.split('').reverse().join('');
               return res;
           }
       }
  • 如何把数据缓存起来?
     这个数据如果是组件中的,就使用computed来缓存
     这个数据如果是Vuex中的,就使用getters来缓存
<div id="app">
          <father></father>
      </div>
      <template id="father">
          <div>
              <p>{{this.$store.getters.format}}</p>
              <p>{{this.$store.getters.format}}</p>
              <p>{{this.$store.getters.format}}</p>
          </div>
      </template>
<script type="text/javascript">
  const store = new Vuex.Store({
              state:{
                  name:"鱿小鱼"
              },
              mutations:{},
              getters:{
                  format(state){
                      console.log("getters被调用了")
                      return state.name + "2541873074@qq.com"
                  }
              }
          })
          Vue.component("father",{
              template:"#father",
              store:store,
          })
          let vue= new Vue({
              el:"#app",
          })      
</script>

效果:
数据没用发生改变,format方法只会被调用一次

  • getters相当于vue中的计算属性,通过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)
    }
  }
})

通过属性访问:Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter 也可以接受其他 getter 作为第二个参数:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

通过属性访问:

store.getters.doneTodosCount // -> 1

通过方法访问:可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
  • mapGetters 辅助函数
    mapGetters 辅助函数的作用:仅仅是将 store 中的 getter 映射到局部计算属性

怎么使用?
1.Vuex实例对象部分:

state:{
  count:0
},
 // getters:获取全局共享的数据
getters: {
    count (state) {
      return state.count
    }
 }

2.在.vue组件中引入,在js块中引入

import { mapGetters} from 'vuex'

3.在vue组件computed中定义一个数组:

computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'count ', // 把 `this.count` 映射为 `this.$store.getters.count`
      // ...
    ])
  }

以上代码就相当于下面的代码:

computed: {
      count(state){
          return this.$store.getters.count
      }
  }

6.Mutations

  • mutations和组件中的methods属性类似;mutations作用:用来保存修改全局共享数据的方法
<div id="app">
   <father></father>
</div>
<template id="father">
   <div>
      <son1></son1>
   </div>
</template>
<template id="son1">
   <div>
       <button @click="add">增加</button>
       <button @click="sub">减少</button>
       <input type="text" :value="this.$store.state.count">
   </div>
</template>
<script>
const store = new Vuex.Store({
       // 这里的state就相当于组件中的data, 就是专门用于保存共享数据的
       state: {
           count:0
       },
       // mutations:用于保存修改共享数据的方法的
       mutations: {
           /*
           注意点:在执行mutations中定义的方法的时候, 系统会自动给这些方法传递一个state参数
           state中就保存了共享的数据
           */
           mAdd(state){
               state.count = state.count + 1;
           },
           mSub(state){
               state.count = state.count - 1;
           }
       }
   });
Vue.component("father", {
       template: "#father",
       store:store,
       //子组件
       components: {
           "son1": {
               template: "#son1",
               methods:{
                   add(){
                       //注意点:在Vuex中不推荐直接修改共享数据
                       // this.$store.state.count = this.$store.state.count + 1;
                       this.$store.commit("mAdd");
                   },
                  sub(){
                       // this.$store.state.count = this.$store.state.count - 1;
                      this.$store.commit("mSub");
                   }
               }
           }
       }
});
let vue = new Vue({
       el: '#app',
      });
</script>
  • mutations需要通过commit来调用其里面的方法,它也可以传入参数,第一个参数是state,第二个参数是载荷(payLoad),也就是额外的参数

Vuex实例对象部分:

state:{
  count:0
},
mutations: { //类似于methods
  changecount(state,payLoad){ //第一个参数是`state`,第二个参数是`载荷(payLoad)`,也就是额外的参数
    state.count += payLoad.number
  }
}

template模板部分:

<div class="home">
   <div><button @click="test">我是按钮</button></div>
</div>

Vue实例对象部分:

methods:{
 test(){
   this.$store.commit(' changecount',{   //第二个参数最好写成对象形式
     number:5
   })
 }
}

调用的时候第二个参数最好写成对象形式,这样我们就可以传递更多信息。

但是,这样写还是会遇到同样的问题,就是如果需要操作多个数据,就会变的麻烦,这时候我们就需要mapMutations辅助函数,通过它将mutations中方法映射到组件的methods属性中

这里是通过mapMutationsmutations中方法映射组件的methods属性,需要提前做的准备须知

  • 使用常量替代 Mutation 事件类型

① 新建一个mutations-type.js文件:

// mutation-types.js
export const CHANGE_COUNT = 'CHANGE_COUNT'

② store.js文件中:

// store.js
import Vuex from 'vuex'
import { CHANGE_COUNT } from './mutation-types'

const store = new Vuex.Store({
 state: { 
       count:0
 },
//这里在做项目时也可以把mutation中代码单独提取到一个文件中,命名为mutatons.js文件
 mutations: {
   // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
   [CHANGE_COUNT] (state,payLoad) { // payLoad 传递的值
     // mutate state
        state.count = payLoad
   }
 }
})

为什么要使用常量替代 Mutation 事件类型?
 因为在Actions中通过commit('changecount', playLoad)触发Mutationschangecount修改数据的方法时,用的是 “”changecount包裹成字符串,由于字符串在书写过程中不会报错,所以将Mutations中的方法名定义为常量,这样在书写不正确时会报错,方便维护

使用常量之actions中调用mutations中方法:

//这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件
actions: {
//用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候)
setchangecount({ commit },playLoad){
      commit('changecount',playLoad)
   }
}

使用常量之actions中调用mutations中方法:

//这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件
import {CHANGE_COUNT} from './mutations-type'
actions: {
//用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候)
setchangecount({ commit },playLoad){   
      commit(CHANGE_COUNT,playLoad)
   }
}
  • Mutation 必须是同步函数
    一条重要的原则就是要记住 mutation 必须是同步函数
    下面Vue官网原话:
mutations: {
 someMutation (state) {
   api.callAsyncMethod(() => {
     state.count++
   })
 }
}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

  • mapMutations辅助函数
    mapMutations辅助函数的作用:在组件中调用 mutaions 中的方法;
    原理: 就如同,通过 store.commit(mutations中保存的修改state数据的方法) 调用mutaions 中的方法

怎么使用?
1.Vuex实例对象部分:

state:{
  count:0
},
mutations: { //类似于methods
  changecount(state,payLoad){
    state.count  = payLoad
  }
}

2.在.vue组件中引入,在js块中引入

import { mapMutations } from 'vuex'

3.在vue组件methods中定义一个数组(Vue实例对象部分):

methods: {
    ...mapMutations([
      'changecount', // 将 `this.changecount()` 映射为 `this.$store.commit('changecount')`
    ])
  }

以上代码就相当于下面的代码:

methods: {
   changecount(playLoad){
        this.$store.commit('changecount',playLoad)
    }
}

5.参数我们可以在调用这个方法的时候写入

<button @click="changecount({number:5})">我是按钮</button>

5.使用常量之actions中调用mutations中方法:

//这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件
actions: {
   setchangecount({ commit },playLoad){
       commit('changecount',playLoad)
    }
}
  • 为什么要绕一圈,从mutations里面去改state呢?我能不能直接改state呢?

比如这样:

changecount(){
 this.$store.state.count +=5;
}

实际看结果也可以,那我为什么从mutations里面中转一下呢?以下的解释是我看别人写的,写的不错就在这里利用一下

原因如下:

① 在mutations中不仅仅能做赋值操作

② 作者在mutations中做了类似埋点操作,如果从mutations中操作的话, 能被检测到,可以更方便用调试工具调试,调试工具可以检测到实时变化,而直接改变state中的属性,则无法实时监测

注意:mutations只能写同步方法,不能写异步,比如axios、setTimeout等,这些都不能写,mutations的主要作用就是为了修改state的。

原因类似如果在mutations中写异步,也能够调成功,但是由于是异步的,不能被调试工具追踪到,所有不推荐这样写,不利于调试,这是官方的约定。


7.Actions

  • action类似于mutation;actions的作用:保存触发Mutations中修改全局共享数据的方法

不同在于:

  • action 提交(commit)的是 mutation ,而不是直接变更状态

  • action 包含异步操作,类似于axios请求,可以都放在action中写

  • action中的方法默认的就是异步,并且返回promise

代码如下:

sate.js

export default {
    count: 0
  }

mutations-typa.js

export const CHANGE_COUNT = 'CHANGE_COUNT'

mutations.js

import {CHANGE_COUNT} from './mutations-type'
export default {
    [CHANGE_COUNT] (state,payLoad) {
      state.count = payLoad
    }
  }

actions.js

import {CHANGE_COUNT} from './mutations-type'

export default {
    setchangecount (context) {
      context.commit(CHANGE_COUNT)
    }
  }

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

  • 实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):
actions: {
//用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候)
  setchangecount ({ commit } , payLoad) {
    commit(CHANGE_COUNT , payLoad)
  }
}
  • 在action内部可以执行异步操作:
actions: {
 setchangecount ({ commit }) {
    setTimeout(() => {
      commit(CHANGE_COUNT)
    }, 1000)
  }
}
  • mapActions 辅助函数
    mapActions 辅助函数的作用: 将actions中的方法映射组件中;
    原理:就如同,通过 store.dispatch(actions保存的触发mutation中的方法) 映射actions中的方法

分发Action的两种方式:
分发Action:也就相当于在界面中触发Action方法

1.Action 通过 store.dispatch 方法触发:

store.dispatch('setchangecount')

2.在组件中分发 Action:使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用

如何使用mapActions 辅助函数?
1.Vuex实例对象部分:

state:{
  count:0
},
//这里并没有使用常量替代 Mutation 事件
mutations: { //类似于methods
  changecount(state,payLoad){
    state.count  = payLoad
  }
}
actions: {
  setchangecount ({ commit } , payLoad) {
    commit('changecount' , payLoad)
  }
}

2.在.vue组件中引入,在js块中引入

import { mapActions } from 'vuex'

3.在vue组件methods中定义一个数组(Vue实例对象部分):

  methods: {
    ...mapActions([
      'setchangecount', // 将 `this.setchangecount()` 映射为`this.$store.dispatch('setchangecount')`
    ]),
  }

上面的代码就相当于下面的代码:

  methods: {
     setchangecount(){
          return this.$store.dispatch('setchangecount')
    }
  }
  • Actions 支持同样的载荷方式和对象方式进行分发
// 以载荷形式分发
store.dispatch('setchangecount', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'setchangecount',
  amount: 10
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350