vuex使用笔记

332 阅读5分钟

vuex简介

vuex是个状态管理库;简单理解就是给多个组件提供共享的全局变量,这个全局的变量是个对象,对象中的属性都是多组件共享的;并且vuex有一系列的机制对这个全局对象进行状态的管理。

既然vuex提供的是全局共享对象,那么和普通的全局变量有什么区别?

普通全局变量只提供共享,但是不具有响应式;vuex提供的全局变量对象,除了供组件共享之外,并且具有响应式。


试想如果想让多个组件共享对象,那么只需要将某个对象挂载到Vue.prototype上就可以,但是为什么我们不这么做,因为只挂载是没有响应式的。由此可知vuex的实现应该是:将共享对象挂载到vue的根原型 + 并且将共享对象加入响应式系统

vuex相当于全局单例大管家,将组件需要共享的属性放在vuex中进行统一管理

vuex适用场景

如果只是单纯的父子组件通信传递直接采用props就可以;vuex是针对比较中大型的项目,并且是复杂组件之间通信跨多层级,并且组件间关联性不大,但是又需要通信,vuex就比较适合这种场景。

如果只是单一组件之间通信,就引入vuex这种集中式的状态存储管理机制,反而影响项目质量

vuex使用

1. vuex属性state的使用

// /src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// 2. 创建对象
const store = new Vuex.Store({
  state: {
    counter: 10
  },
});

// 3. 导出store对象
export default store
// main.js
import Vue from "vue";
import App from "./App.vue";
import store from "./store";

new Vue({
  // 为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。
  // Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制
  store,    
  render: h => h(App)
}).$mount("#app");
<!-- HelloVuex组件 -->
<template>
  <h1>{{ $store.state.counter }}</h1>
</template>

<script>
export default {
}
</script>

<style>
</style>
<!-- App组件 -->
<template>
  <div id="app">
    <p>----------app组件内容-------</p>
    <h1>{{ $store.state.counter }}</h1>
    <button @click="++$store.state.counter">+</button>
    <button @click="--$store.state.counter">-</button>
    <p>----------helloVuex组件内容-------</p>
    <hello-vuex />
  </div>
</template>

<script>
import HelloVuex from "./components/HelloVuex.vue";

export default {
  name: "App",
  components: {
    HelloVuex
  }
};
</script>
<style>
</style>

2. vuex属性Mutations的使用

mutations中可以做同步操作,结合devtools跟踪组件的commit提交记录

2.1 mutations基本使用

// /store/index.js

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

// 2. 创建对象
const store = new Vuex.Store({
  state: {
    counter: 10
  },
  mutations: {
    // mutations中的方法默认接受state参数
    increment (state) {
      ++state.counter
    },
    decrement (state) {
      --state.counter
    }
  }
});
// 3. 导出store对象
export default store
<!-- App.vue -->
<template>
  <div id="app">
    <p>----------app组件内容-------</p>
    <h1>{{ $store.state.counter }}</h1>
    <button @click="addCount">+</button>
    <button @click="subCount">-</button>
    <p>----------helloVuex组件内容-------</p>
    <hello-vuex />
  </div>
</template>

<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
  name: "App",
  components: {
    HelloVuex
  },
  methods: {
    addCount () {
      // 通过commit发送给Mutations,commit参数的名字就是Mutations中定义increment方法的名字
      this.$store.commit('increment')
    },
    subCount () {
      this.$store.commit('decrement')
    }
  }
};
</script>
<style>
</style>

devtools跟踪每次commit记录示意图:

2.2 mutations传参问题

语法

mutations中的属性由两部分组成:

  1. 字符串事件类型(type)
  2. 针对字符串事件类型所对应的回调函数(handle)
  • 该回调函数的第一个参数默认是state
  • 该回调函数的其他参数用户可以传入,用户传入的参数被称为payload 负载

mutations: { increment: function(state, arg) { // code },

increment:就是字符串type function:是increment这个type对应的handle handle第一个参数:默认是state handle的arg:是用户传入payload

// 代码示例1:
mutations: {
  // count是组件传入payload
  add (state, count){
    state.counter += count
  }
},

methods: {
  addCount () {
    // this.$store.commit('increment')
    // 10就是传递到mutations中的add这个type多对应的回调函数的payload
    this.$store.commit('add', 10)
  },
}

2.3 mutations的commit提交风格

普通提交: 普通方式提交除了指定mutations中的type字符串之外,只能再额外接受一个参数,这个参数可以是任何类型。

// 普通提交
mutations: {
  // 这里count是10,但是age是undefined
  add (state, count, age){
    state.counter += count
  }
},
methods: {
  addCount () {
    // 普通提交方式,10就对应add的参数count,而111无法传递到age中
    this.$store.commit('add', 10, 111)
  },
}

特殊提交: 特殊提交,不管提交多少参数都只映射到payload这个对象中

// 特殊提交
mutations: {
  // 特殊方式提交时,payload是一个对象,
  // payload: {
  //   type: 'add',
  //   count: 10,
  //   age: 1111
  // }
  add (state, payload){
    state.counter += payload.count
  }
},
methods: {
  addCount () {
    // 特殊方式的提交,不管提交多少参数都会传递到mutations的add的payload对象
    this.$store.commit({
      type: 'add',
      count: 10,
      age: 1111
    })
  },
}

2.4 mutations的类型常量

当mutations中的type太多时,有时候我们在组件中,进行commit提交如果没有从mutations中copy字符串type,手敲

// 这里如果手敲 'increment' 很容易出错,而且除了错,控制台是不报的,这时候错误不容易发现
// 所以建议当mutations中的type太多时,我们采用常量的方式引入
this.$store.commit('increment')
// mutations-type.js
export const IN_CREMENT = 'increment'
export const DE_CREMENT = 'decrement'


// store/index.js
import {
  IN_CREMENT,
  DE_CREMENT
} from 'mutations-type.js'
mutations: {
  [IN_CREMENT](state){
    // code
  },
  [DE_CREMENT](state){
      // code
  },
}

// app.vue组件中使用
import {
  IN_CREMENT,
  DE_CREMENT
} from 'mutations-type.js'

this.$store.commit(IN_CREMENT)

3. vuex属性getters的使用

Getters属性类似于vue中的计算属性,将state中的属性进行一系列加工之后进行返回;比如这里外界需要获取的是state中counter属性的平方,那么就可以写到Getters中。

当多个组件都需要获取state中counter属性的平方时就可以写在getters中,如果只是某一个组件使用,则可以直接写在组件的computed中,不需要提到getters中。

3.1 getters基本使用

// /store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// 2. 创建对象
const store = new Vuex.Store({
  state: {
    counter: 10
  },
  mutations: {
    // mutations中的方法默认接受state参数
    increment (state) {
      ++state.counter
    },
    decrement (state) {
      --state.counter
    }
  },
  getters: {
    // getters中的方法默认接受state参数
    // 外面的组件可以通过 $store.getters.powerCounter 进行访问
    powerCounter (state) {
      return state.counter * state.counter
    }
  },
  actions: {},
  modules: {}
});

// 3. 导出store对象
export default store

3.2 getters传参问题

语法

//  语法示例1:
// 参数1:默认是state
// 参数2:默认是getters对象本身,这样用于再使用getters对象调用getters对象内部的的其他属性
getters: {
  xxx(参数1, 参数2){
    // code
  }
}

// 语法示例2:
// getters中的属性不能接受外界组件参数,如果外界组件想传参进getters的属性中,可以让getters中的属性返回闭包函数。
getters: {
  xxx(参数1, 参数2){
    return function(arg){
      // code
    }
  }
}

代码示例

// /store/index.js示例:

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
  state: {
    students: [
      { 'name': '令狐冲', 'age': 300 },
      { 'name': '东方不败', 'age': 18 },
      { 'name': '任我行', 'age': 100 },
      { 'name': '鸠摩智', 'age': 200 },
    ]
  },
  getters: {
    moreToAge (state) {
      return state.students.filter(item => item.age > 18)
    },
    moreToAgeLength (state, getters) {
      return getters.moreToAge.length
    },
    // 根据外部组件传递的age参数筛选获取学生信息,这里返回闭包函数
    studentById (state) {
      return function (age) {
        return state.students.filter(item => item.age == age)
      }
    }
  },
});
export default store
<!-- App.vue示例 -->
<template>
  <div id="app">
    <p>----------store的getters使用-------</p>
    <p>学生信息:{{ $store.state.students }}</p>
    <p>大于18岁:{{ $store.getters.moreToAge }}</p>
    <p>大于18岁学生个数:{{ $store.getters.moreToAgeLength }}</p>
    <!-- 注意这个studentById属性返回闭包函数接受age参数 -->
    <p>等于18岁:{{ $store.getters.studentById(18) }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>
<style>
</style>

4. vuex属性actions的使用

actions的使用类似于mutations,区别在于,异步操作放在actions中,其次actions中的属性方法对应的默认参数不是state,而是context,context是一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;但是context不是store实例本身

4.1 actions基本使用

// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    info: {
      'up': 'upup',
      'down': '笑书神侠倚碧鸳'
    }
  },
  mutations: {
    updataInfo (state) {
      state.info.up = '飞雪连天射白鹿'
    }
  },
  actions: {
    actionUpdataInfo (context, arg) {
      return new Promise((resolved, rejected) => {
        // 使用setTimeout模拟异步调用
        // commit之后相当于异步调用完成,使用resolved通知外部组件异步请求成功
        // 这里关于promise的一个知识点,同时resolved和rejected,谁先就先返回谁,另外一个就不调用了。
        setTimeout(() => {
          context.commit('updataInfo')
          resolved(111)
          rejected(222)    // 不执行
          console.log(arg) // 执行:我是携带的信息
        }, 1000);
      })
    }
  },
});
export default store
<!-- App.vue -->
<template>
  <div id="app">
    <p>----------store的actions使用-------</p>
    <p>info: {{ $store.state.info }}</p>
    <button @click="updateInfo">修改</button>
  </div>
</template>

<script>
import HelloVuex from "./components/HelloVuex.vue";

export default {
  name: "App",
  components: {
    HelloVuex
  },
  methods: {
    updateInfo () {
      // 组件流向action通过dispatch方法
      // 这个promise的返回针对actionUpdataInfo这个action的,所以在其他地方调用actionUpdataInfo时,也是promise返回
      this.$store.dispatch('actionUpdataInfo', '我是携带的信息')
        .then(res => console.log(res))
        .catch(e => console.log(e))
    }
  }
};
</script>
<style>
</style>

4.2 actions的dispatch分发风格

actions的dispatch类似于mutations的commit提交风格

普通dispatch分发: 普通方式分发,除了dispatch指定ations中的type字符串之外,只能再额外传递一个参数,这个额外的参数可以是任何类型

// 见4.1 actions基本使用

特殊dispatch(带payload)分发:

// store/index.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);
const store = new Vuex.Store({
    info: {
      'up': 'upup',
      'down': 'downdown'
    }
  },
  mutations: {
    // mutations中的方法默认接受state参数
    updataInfo (state, payload) {
      // payload: {
      //   type:"updataInfo",
      //   obj: {
      //     down: "笑书神侠倚碧鸳"
      //     type: "actionUpdataInfo"
      //     up: "飞雪连天射白鹿"
      //   }
      // }
      console.log(payload);
      state.info.up = payload.obj.up
      state.info.down = payload.obj.down
    }
  },

  actions: {
    actionUpdataInfo (context, payload) {
      return new Promise((resolved, rejected) => {
        setTimeout(() => {
          // payload:
          // {
          //   type: 'actionUpdataInfo',
          //   up: '飞雪连天射白鹿',
          //   down: '笑书神侠倚碧鸳'
          // }
          console.log('payload====', payload)
          // 这里也采用mutations的payload的提交
          context.commit({
            type: 'updataInfo',
            obj: payload
          })
          resolved(111)
          rejected(222)
        }, 1000);
      })
    }
  },
});
export default store
<!-- App.vue -->
<template>
  <div id="app">
    <p>----------store的actions使用-------</p>
    <p>info: {{ $store.state.info }}</p>
    <button @click="updateInfo">修改</button>
  </div>
</template>

<script>
import HelloVuex from "./components/HelloVuex.vue";

export default {
  name: "App",
  components: {
    HelloVuex
  },
  methods: {
    updateInfo () {
      // 组件流向action通过dispatch方法
      // this.$store.dispatch('actionUpdataInfo', '我是携带的信息')
      //   .then(res => console.log(res))
      //   .catch(e => console.log(e))

      // payload对象方式提交
      this.$store.dispatch({
        'type': 'actionUpdataInfo',
        'up': '飞雪连天射白鹿',
        'down': '笑书神侠倚碧鸳'
      })
        .then(res => console.log(res))
        .catch(e => console.log(e))
    },
  }
};
</script>
<style>
</style>

4.3 组合actions(官网示例)

// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

5. vuex属性modules的使用

vuex对state的管理保持单一状态树的原则,就是坚持一个全局的store便于管理,但是当应用变得非常复杂时state中的变量越来越多而且会变的臃肿,vuex在设计的时候也考虑到这个问题,所以在store中添加modules这个属性模块,当状态不利于维护时,可以在modules属性中再维护store,并且每个store模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

// 这里暂不展开具体操作

vuex多页面状态管理

1. vuex的state状态值修改问题

在App组件中直接修改store的state中的属性值++$store.state.counter,页面不会报错,并且正常执行,但是官方不推荐这么做,原因见vuex的状态管理图

<!-- App组件 -->
<template>
  <div id="app">
    <p>----------app组件内容-------</p>
    <h1>{{ $store.state.counter }}</h1>
    <button @click="++$store.state.counter">+</button>
    <button @click="--$store.state.counter">-</button>
    <p>----------helloVuex组件内容-------</p>
    <hello-vuex />
  </div>
</template>

<script>
import HelloVuex from "./components/HelloVuex.vue";

export default {
  name: "App",
  components: {
    HelloVuex
  }
};
</script>
<style>
</style>

2. vuex状态管理理论

问题:vue组件直接和state交互不报错,并且正常显示;那为什么官方不推荐直接vue组件直接和state交互呢?

试想一种场景A,B,C三个组件都对state的同一个count属性进行修改,万一谁改错了,那我们怎么知道这次错了是被哪一个组件修改的呢?浑水摸鱼,无从查证;所以vue给vuex提供了一个devtools的浏览器插件,这个插件的作用就是为了跟踪state状态修改的记录,这样不管A,B,C哪个组件需要修改state的count属性都需要各自提commit到Mutations模块,然后Mutations模块再对commit进行同步处理和devtools交互,最终将修改更新到State模块。


问题:既然了解了Mutations模块的作用,那么Action模块呢?

Actions模块的主要作用是进行异步操作,如果vue组件修改state状态需要进行异步操作,那么就需要Actions模块;Backend(后端)API调用。Actions将异步处理完的结果commitMutations中,Muations再进行同步操作;如果没有异步操作那么Actions模块也可以不要了;vue组件就直接跟Mutations模块交互了,这个官方也是允许的。


问题:为什么vuex要求mutations中的方法必须是同步方法?

主要原因是因为当我们使用devtools时,devtools可以帮助我们捕捉mutations中的每次一快照,如果是异步操作的话,devtools将不能很好的追踪这个异步操作什么时候会被完成。

其实在mutations中可以进行异步请求,并且不报错。只是我们人为规范为了借助devtools追踪状态,方便开发调试。

谈谈store中state的响应式

state的响应式和vue的data属性的响应式一模一样,没有声明在state中的属性不具有响应式

如果想使state中的属性具有响应式,通过$set或者直接用新的对象给旧对象赋值