Vuex

2020-08-07  本文已影响0人  Angel_6c4e

1.什么是Vuex?

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


2.Vuex能解决什么问题?

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

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

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



3.创建Vuex的步骤:

const store = new Vuex.Store({
       state:{
              msg:"鱿小鱼"
        },
        mutations:{},
        actions:{},
        getters:{}
});
Vue.component("father",{
                template:"#father",
                //在祖先组件中添加store的key保存Vuex对象
                store:store,
                data:function(){
                    return{}
                },
                methods:{},
                computed:{},
                components:{}
})
<template id="father">
    <div>
        <p>{{this.$store.state.msg}}</p>
        <son></son>
    </div>
</template>

4.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
                    }
                }
            }
        }
 });

这样引入就方便了很多。

怎么使用?
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

补充:

  • 什么是计算属性?
     将计算结果缓存起来,只要数据没有发生变化就只会计算一次,以后使用的都是缓存>起来的数据
  • 函数和计算属性的区别?
     函数的特点:每次调用都会执行
     计算属性的特点:只要返回的结果没有发生变化,那么计算属性只会被执行一次
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方法只会被调用一次

第一个参数就是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 }

怎么使用?
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

<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>

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 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

怎么使用?
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)
    }
}

比如这样:

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

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

原因如下:

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

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

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

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


7.Actions

不同在于:

代码如下:

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 实例本身了。

actions: {
//用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候)
  setchangecount ({ commit } , payLoad) {
    commit(CHANGE_COUNT , payLoad)
  }
}
actions: {
 setchangecount ({ commit }) {
    setTimeout(() => {
      commit(CHANGE_COUNT)
    }, 1000)
  }
}

分发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')
    }
  }
// 以载荷形式分发
store.dispatch('setchangecount', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'setchangecount',
  amount: 10
})
上一篇 下一篇

猜你喜欢

热点阅读