vue2-vuex基础知识

236 阅读3分钟

vuex

vuex官网

一、vuex是什么

vue官方提供的状态管理器用于处理全局的数据共享问题的

二、vuex的仓库中的五大核心概念

  • state 专门存共享数据的地方
  • getter 可以针对现有的 state 数据或者其余的 getter 做的一个二次计算。可以理解为仓库的计算属性
  • mutation 唯一能够修改 state 数据的东西,不允许写异步代码
  • action 它里面可以写异步代码,它如果要修改 state 数据,是通过去调用 mutation
  • module 仓库模块的拆分

三、如何使用

  1. 项目中安装 vuex

    $ npm install vuex
    
  2. 创建 src/store.js 文件。这个文件用来生效仓库的实例

  3. 需要在 src/main.js 文件的 new Vue() 的位置去配置 store 选项,选项值是上一个步骤中暴露出来的 store 的实例

四、如何分辨是否已经配置好 vuex 的仓库了呢

查看 vueDevtools 就可以

五、组件中如何使用 state 与 getter

方案一、使用挂载到 Vue原型上的 $store 对象。这个 $store 就是 new Vuex.Store() 生成的仓库实例对象

方案二 (推荐)、使用 computed

<script>
export default {
  computed: {
    curCity() {
      return this.$store.state.curCity;
    },
    cart() {
      return this.$store.state.cart;
    }
  }
}  
</script>

方案三(推荐、就是方案二的一个优雅写法)、使用 vuex 提供的辅助函数

  1. 组件中从 vuex 中引入你需要的辅助函数

  2. 调用辅助函数,并将其返回的值赋给组件的 computed 选项

 <script>
 // 引入 辅助函数 。采用类似 解构赋值的方式
 import { mapState } from 'vuex'
 
 export default {
   // mapState 返回值是一个 对象
   computed: mapState(['curCity', 'cart'])
 }  
 </script>
mapStatemapGetters 的语法
// mapState 接收一个数组做为参数,参数中的每一项,就是在仓库中的 state 数据
mapState([state1, state2, state3])

// mapGetters  接收一个数组做为参数,参数中的每一项,就是在仓库中的 getter 数据
mapGetters([getter1, getter2])
方案二与方案三效果一样,但是在 VueDevtools 中的表现有稍微一点不同

方案二时,插件中显示的是 computed

方案三时,插件中显示的是 vuexbindings

方案二与方案三效果一样,一般我们更推荐大家使用方案三。但是在什么情况下要使用方案二呢?

希望组件中的数据与仓库中的数据用不同的名字的时候。采用方案二

使用方案三时,我们组件自身还有一些自己的computed数据,该如何办?

将 mapState() 使用 ... 做展开

六、组件中如何去修改仓库中的 state 与 getter 数据呢

首先明确一点。state 可以修改。getter 不能修改

步骤:

1. 仓库中要提供 对应 state 修改的 mutation 函数
2. 在组件中去调用 mutation

调用 mutation 的三套方案:

方案一、直接使用 vuex 绑定到 vue 原型上的 $store 这个对象的 commit() 方法

方案二、在组件中先定义一个函数,函数名跟后续要调用的mutaion名字保持一致,函数内部使用方案一

方案三、使用 mapMutations 辅助函数

// mapMutations的语法
// 接收一个数组作为参数,数组中的每一项是一个 mutation 的名字
mapMutations([mutation1, mutation2, mutation3])

示例代码

{
	methods: mapMutations(["SETCURCITY", "ADDCART"])
}

示例代码转换就是如下的代码效果

{
  methods: {
    SETCURCITY(payload) {
			this.$store.commit('SETCURCITY', payload)
    },
    ADDCART(payload) {
			this.$store.commit('ADDCART', payload)
		}
  }
}
为了结合组件自身的函数,所以 mapMutations 也需要使用展开运算符来使用
所有的 mutation 执行,都能够在 VueDevtools 去看到
为什么 vuex 中的 state 必须使用 mutation 来修改呢
  1. 为了以一种可以预见的方式去修改数据,不至于让数据难以理解。
  2. 为了实现时间旅行。

七、使用 action 异步的修改 state 数据

首先需要知道,mutation 里面只允许同步的去修改 state 数据。(虽然在mutation中可以异步的去修改state数据不会报错,但是会导致时间旅行等机制没有效果)

如果异步的修改的化,有两个大方案

  1. 不涉及action。在组件上异步代码走完之后再去调用 mutation
  2. 使用 action

使用 action。首先需要在 actions 选项中定义 action 函数

注意:action 中不能直接去修改 state,要修改是通过 context.commit() 去执行某个 mutation 来修改

方案一、直接使用 vuex 绑定到 vue 原型上的 $store 这个对象的 dispatch() 方法

// dispatch 语法
//     actionName - 要调用action的名字
//     payload    - 要传递这个 action 的参数
this.$store.dispatch(actionName, payload)

方案二、在组件中先定义一个函数,函数名跟后续要调用的 action 名字保持一致,函数内部使用方案一

方案三、使用 mapActions 这个辅助函数

// mapActions 的语法
// 接收一个数组作为参数,数组中的每一项是一个 action 的名字
mapActions([action1, action2, action3])

示例代码

{
  methods: mapActions(["SYNCSETCURCITY"])
}

转换之后如下所示

{
  methods: {
    SYNCSETCURCITY(payload) {
      this.$store.dispatch('SYNCSETCURCITY', payload)
    }
  }
}

vuex 的 module

一、什么时候需要在 vuex 中使用 module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

项目越做越大,功能点越写越多。需要使用 vuex 共享的数据越来越庞大时,就需要使用 module 来进行仓库模块拆分啦。

示例代码

// 拆分的仓库子模块A
const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... },
  // 仓库子模块也可以继续去做拆分,但是没必要搞这么复杂
  modules: {
    aa,
    ab, 
  }
}
            
// 拆分的仓库子模块B
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

// 仓库根模块
const store = new Vuex.Store({
	// 通过 modules 选项配置子模块
	modules: {
		// key: value
    //    key - 仓库子模块的名字
    //    value - 对应的仓库子模块的对象
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

二、仓库拆分子模块之后,没有设置命名空间。有一些问题的

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间

  1. 多个仓库子模块中的 getter 不能同名,否则会报错
  2. 多个仓库子模块中的 mutation 如果同名的化,组件调用这个 mutation 时。都会被触发, 会有污染的风险
  3. 多个仓库子模块中的 action 如果同名的化,组件调用这个 action 时。都会被触发, 会有污染的风险

三、由于有上面的这种问题存在,所以推荐仓库子模块都设置上命名空间。

1. 如何设置呢?

给 仓库子模块的 那个对象配置一个 namespaced 属性。属性值为 true

2. 设置之后的改变是什么?

模块内部的 action、mutation 和 getter 是注册在自己命名空间

3. 设置了命名空间之后如何在组件中使用呢

基本使用步骤不变,只是要处理处理命名空间

下面的 xx 代表着某个仓库子模块的名字

1. 获取某个仓库子模块中的 state
// 1. 直接通过 $store
this.$store.state.xx
// 2. computed
computed: {
  name () {
		return this.$store.state.xx.name
  }
}
// 3. mapState
computed: {
  ...mapState('xx', [state1, state2, state3])
}
// 4. mapState 的转换
computed: {
 	state1() {
  	return this.$store.state.xx.state1
  }
}

// !如果要在组件中同时拿到多个仓库子模块的同名state数据,不要使用 mapState 请使用方案二 !
2. 获取 某个仓库子模块中的 getter
// 1. 直接通过 $store
this.$store.getters['ma/firstName']
// 2. computed
computed: {
  firstName() {
    return this.$store.getters['ma/firstName']
  }
}
// 3. mapGetters
computed: {
  ...mapGetters('xx', [getter1, getter2])
}
// 4. mapGetters 的转换
compouted: {
  getter1() {
		return this.$store.getters['xx/getter1']
  }
}
3. 提交 某个仓库子模块中的 mutation
// 1. 直接通过 $store
this.$store.commit('ma/SET_NAME', payload)
// 2. methods
methods: {
  SET_NAME(payload) {
    this.$store.commit('ma/SET_NAME', payload)
  }
}
// 3. mapMutations
methods: {
  ...mapMutations('xx', [mutation1, mutation2])
}
// 4. mapMutations 的转换
methods: {
	mutation1(payload) {
    this.$store.commit('xx/mutation1', payload)
  }
}
4. 派发 某个仓库子模块中共的 action
// 1. 直接通过 $store
this.$store.dispatch('ma/SYNC_SET_NAME', payload)
// 2. methods
methods: {
  SYNC_SET_NAME(payload) {
    this.$store.dispatch('ma/SYNC_SET_NAME', payload)
  }
}
// 3. mapActions
methods: {
  ...mapActions('xx', [action1, action2])
}
// 4. mapActions 的转换
methods: {
    action1(payload) {
        this.$store.dispatch('xx/action1', payload)
    }
}

各部分内容在文件夹中的显示

image.png

modules/a.js

// 拆分出来的仓库子模块A
// 仓库子模块其实就是一个包含有 state 、getters、mutations、actions 的对象

export default {
  // 命名空间
  namespaced: true,

  state: {
    name: "张三",
  },

  mutations: {
    SET_NAME(state, payload) {
      console.log("a");
      state.name = payload;
    },
  },

  actions: {
    SYNC_SET_NAME(context, payload) {
      console.log("a-a");
      setTimeout(() => {
        // 调用 SET_NAME 这个 mutation 来修改 state 数据
        context.commit("SET_NAME", payload);
      }, 2000);
    },
  },

  getters: {
    firstName(state) {
      return state.name.substr(0, 1);
    },
  },
};

modules/b.js

// 拆分出来的仓库子模块B

export default {
  // 命名空间
  namespaced: true,

  state: {
    name: "李四",
  },

  mutations: {
    SET_NAME(state, payload) {
      console.log("b");
      state.name = payload;
    },
  },

  actions: {
    SYNC_SET_NAME(context, payload) {
      console.log("b-b");
      setTimeout(() => {
        // 调用 SET_NAME 这个 mutation 来修改 state 数据
        context.commit("SET_NAME", payload);
      }, 2000);
    },
  },

  getters: {
    firstName(state) {
      return state.name.substr(0, 1);
    },
  },
};

index.js

// 仓库根模块

// 1. 引入 Vue
import Vue from "vue";

// 2. 引入 Vuex
import Vuex from "vuex";

// 引入拆分出来的仓库子模块
import moduleA from "./modules/a";
import moduleB from "./modules/b";

// 3. 调用
Vue.use(Vuex);

// 4. 生成 仓库 实例对象
const store = new Vuex.Store({
  // 只配置 modules
  modules: {
    ma: moduleA,
    mb: moduleB,
  },
});

// end. 暴露 store
export default store;


父模块state、mutations、getters、actions内容过多时,将这四部分抽离出去,方便管理 image.png

子模块state、mutations、getters、actions内容过多时,将这四部分抽离出去,方便管理 image.png


学习笔记

学习