分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 前端开发

Vuex-一个专为 Vue.js 应用程序开发的状态管理模式

发布时间:2023-09-06 02:22责任编辑:赖小花关键词:js

为什么会出现Vuex

非父子关系的组件如何进行通信?(Event Bus)
bus.js

import Vue from 'vue';export default new Vue();

foo.vue

import bus from './bus.js';export default { ???methods: { ???????changeBroData() { ???????????bus.$emit('changeBarData'); ???????} ???}}

bar.vue

import bus from './bus.js';export default { ???created() { ???????bus.$on('changeBarData',() => { ???????????this.count++; ???????}); ???}}

查看效果

但是当我们需要修改这个操作的时候,我们需要动3个地方,倘若项目小的话还倒好说,但是对于大项目组件间交互很多的概况,Event Bus就会表现的很吃力。Vuex的出现就是为了解决这一状况。

Vuex介绍

安装:
script标签引入
https://unpkg.com/vuex
NPM
npm install vuex --save-dev

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

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex的状态存储是响应式的。当Vuex的状态属性发生变化时,相应的组件也会更新。
  2. 无法直接修改Vuex的状态。只能通过显式的提交(commit)。

简单的Store

const store = new Vuex.Store({ ?state: { ???count: 0 ?}, ?mutations: { ???increase(state) { ?????state.count++; ???} ?}});store.commit('increase');console.log(store.state.count); // 1

State

从上文我们知道Vuex是响应式的,我们如何在Vue实例中使用Vuex中的实例呢,自然离不开计算属性computed了。

在 Vue 组件中获得 Vuex 状态

//仓库const store = new Vuex.Store({ ?state: { ???count: 1 ?}});//foo组件const foo = { ?template: ` ???????????<div> ???????????????{{ count }} ???????????<div> ???????????`, ?computed: { ???count: () => store.state.count ?}};

这时候问题就来了,如果这样进行引入store的话,组件就会以来全局状态单例。
Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

//挂载Appnew Vue({ ?el: '#app', ?//注入store选项 ?store, ?render: h => h(App)});//foo组件const foo = { ?template: ` ???????????<div> ???????????????{{ count }} ???????????<div> ???????????`, ?computed: { ???count() { ?????// 不能使用箭头函数,不然this就不是Vue实例了 ?????// 通过this.$store获取到store实例 ?????return this.$store.state.count ???} ?}};

如果有很多状态需要映射,我们岂不是要写好多代码,这时候需要用到mapState辅助函数,使用 mapState 辅助函数帮助我们生成计算属性。
辅助函数 实例1

const foo = { ?template: ` ???????????<div> ???????????????count: {{ count }} ???????????????countAlias: {{ countAlias }} ???????????????countPlusLocalCount: {{ countPlusLocalCount }} ???????????<div> ???????????`, ?data() { ???return { ?????localCount: 2 ???}; ?}, ?computed: Vuex.mapState({ ???//只处理仓库中的count ?????count: state => state.count*2, ???//映射 ???countAlias: 'count', ???//需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例 ???countPlusLocalCount(state) { ?????return state.count + this.localCount; ???} ?})};

当然当映射名与state中属性名相同时,可以通过mapState([‘count‘])这种形式书写。
因为组件本身也有许多计算属性直接使用mapState的话无法扩充computed了,这时候我们可以使用ES新属性: 对象展开运算符

computed: { ???localComputed() { ???????return this.count; ???}, ???...Vuex.mapState({ ???????//只处理仓库中的count ?????????????count: state => state.count*2, ???????//映射 ???????countAlias: 'count', ???????//需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例 ???????countPlusLocalCount(state) { ?????????return state.count + this.localCount; ???????} ???})}

ATT:使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

Getter

有时候我们需要过滤/处理state中的属性值,就像Vue实例中我们需要处理data数据一样,Vue有computed。Vuex同样提供给我们一个属性getter,getter就是Vuex的“计算属性”。
Getter接收state作为第一个参数

//仓库const store = new Vuex.Store({ ?state: { ???users: [{ ?????name: 'jason', ?????id: 1, ?????female: false ???}, { ?????name: 'molly', ?????id: 2, ?????female: true ???}, { ?????name: 'steven', ?????id: 3, ?????female: false ???}] ?}, ?getters: { ???// 过滤所有属性中female是true的对象 ???getFemaleUsers: state => state.users.filter(user => user.female) ?}});console.log(store.getters.getFemaleUsers);//[{ "name": "molly", "id": 2, "female": true }]

当然Getter同样有辅助函数 mapGetters将 store 中的 getter 映射到局部计算属性
直接上对象展开运算符了
辅助函数 实例1

//foo组件const foo = { ?template: ` ???????????<div> ???????????????femaleList: {{ femaleList }} ???????????<div> ???????????`, ?computed: { ???// 属性名相同的不再阐述 ?????...Vuex.mapGetters({ ?????femaleList: 'getFemaleUsers' ???}) ?}};

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
提交时额外的参数被称为载荷
普通风格提交方式

store.commit('mutationName', { ???params: '参数'});

对象风格提交方式

store.commit({ ???type: 'mutationName', ???params: '参数'});

注意点:

  1. 设置对象新属性时
    ??方法1:Vue.set(obj, ‘newVal‘, 100)
    ??方法2:obj = {...obj, newVal: 100};
  2. Mutations的事件类型尽量使用常量,并用一个文件进行维护。方便协同开发以及维护。

    ```// 存储事件名mutations-types.jsexport const MUTATIONS_GETDATA = ‘MUTATIONS_GETDATA‘;export const MUTATIONS_SUCCESS = ‘MUTATIONS_SUCCESS‘;``````import { MUTATIONS_GETDATA ,MUTATIONS_SUCCESS } from ‘path/mutations-types.js‘;const store = new Vuex.Store({ ???mutations: { ???????[MUTATIONS_GETDATA]() { ???????????// todo ???????}, ???????[MUTATIONS_SUCCESS]() { ???????????// todo ???????} ???}});```
  3. Mutations中的函数必须都是同步函数,异步函数都要写在Actions中。

在组件中提交Mutation
$store.commit(‘xxx‘,‘yyy‘)
当然也有对应的辅助函数帮助我们映射到对应的methods方法中

import { mapMutations } from 'vuex';export default { ???methods: { ???????...mapMutations([ ???????????'event1', ???????????'event2' ???????]), ???????...mapMutations({ ???????????eventAlias: 'event3' //将this.eventAlias()映射为this.$store.commit('event3') ???????}) ???}};

Action

Action与Mutation的不同在于Action不直接修改状态只是做commit一个mutation、然后就是可以做异步操作。
简单的Action

const INCREASE_COUNT = 'INCREASE_COUNT';const store = new Vuex.Store({ ?state: { ???count: 0 ?}, ???mutations: { ???[INCREASE_COUNT](state) { ?????state.count++; ???} ?}, ?actions: { ???add({ commit }) { ?????commit(INCREASE_COUNT); ???} ?}});

看到这我们或许以为Actions的作用与Mutations不一样么,对到现在为止作用是一样的,但是Actions可以执行异步操作

const INCREASE_COUNT = 'INCREASE_COUNT';const store = new Vuex.Store({ ?state: { ???count: 0 ?}, ???mutations: { ???[INCREASE_COUNT](state) { ?????state.count++; ???} ?}, ?actions: { ???add({ commit }) { ???????setTimeout(() => { ???????????commit(INCREASE_COUNT); ???????}, 1000); ???} ?}});

Action的分发也有两种方式
普通风格分发方式

store.dispatch('actionName', { ???params: '参数'});

对象风格分发方式

store.dispatch({ ???type: 'actionName', ???params: '参数'});

辅助函数mapActions用法与mapMutations无异
代码链接

methods: { ???...mapActions({ ???????add: 'add' ???})}

组合Action

actions: { ?actionA ({ commit }) { ???return new Promise((resolve, reject) => { ?????setTimeout(() => { ???????commit('someMutation') ???????resolve() ?????}, 1000) ???}) ?}, ?// ... ?actionB ({ dispatch, commit }) { ???return dispatch('actionA').then(() => { ?????commit('someOtherMutation') ???}) ?}}store.dispatch('actionA').then(() => { ?// ...})或者store.dispatch('actionB');

Module

如果项目庞大的话我们需要维护很多状态,这时候store会变得非常庞大,我们就需要store分割成很多模块(Module),每个模块同样拥有自己的state,getters,mutations,actions以及modules

const moduleA = { ?state: { ???a: 'A' ?}};const moduleB = { ?state: { ???a: 'B' ?}};const store = new Vuex.Store({ ?modules: { ???moduleA, ???moduleB ?}});console.log(store.state.moduleA.a); //Aconsole.log(store.state.moduleB.a); //B
  1. 模块内部的mutationgetter接收的第一个参数都是局部状态对象

    ```例如const moduleA = { ???state: { ???????price: 50, ???????count: 2 ???}, ???getters: { ???????totalPrice: state => state.price*state.count ???}, ???mutations: { ???????decreaseCount(state) { ???????????state.count--; ???????} ???}};```
  2. 模块内部的getter,根节点的状态会在第三个参数展示出来。
    注:根节点的状态就是指的store.state

    ```const moduleB = { ?state: { ?????count: 2 ?}, ?getters: { ???sum: (state, getters, rootState) => state.count+rootState.count ?}};const store = new Vuex.Store({ ?state: { ???count: 1 ?}, ?modules: { ???moduleB ?}});console.log(store.getters.sum);// 3```
  3. 模块内部的action,局部状态:context.state根部状态:context.rootState

    ```const moduleC = { ?state: { ?????count: 2 ?}, ?mutations: { ???increaseCount(state) { ???????state.count++; ???} ?}, ?actions: { ???addCount({ commit, state, rootState }) { ???????let sum = state.count + rootState.count; ???????if(sum % 3 === 0) { ???????????commit(‘increaseCount‘); ???????} ???} ?}};const store = new Vuex.Store({ ?state: { ???count: 1 ?}, ?modules: { ???moduleC ?}});console.log(store.state.moduleC.count);// 2store.dispatch(‘addCount‘);console.log(store.state.moduleC.count);// 3```

命名空间
默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。(摘自官网)
注1:特别提出:模块内的state是嵌套的,namespaced属性不会对其产生丝毫影响。
注2:没加namespaced: true的模块会继承父模块的命名空间
代码链接

const store = new Vuex.Store({ ???modules: { ???moduleA: { ?????namespaced: true, ?????getters: { ???????count: () => 0 // store.getters['moduleA/count'] ?????}, ?????modules: { ???????moduleB: { ?????????getters: { ?????????????count1: () => 1 //继承了父模块的命名空间 store.getters['moduleA/count1'] ???????????} ???????}, ???????moduleC: { ?????????namespaced: true, ?????????getters: { ?????????????count2: () => 2 //继承了父模块的命名空间 store.getters['moduleA/moduleC/count1'] ???????????} ???????} ?????} ???} ?}});console.log(store.getters['moduleA/count']);// 0console.log(store.getters['moduleA/count1']);// 1console.log(store.getters['moduleA/moduleC/count2']);// 2

在命名空间模块内部访问全局内容

模块内部希望使用全局state与全局getter

  1. rootStaterootGetters会作为getter第三个第四个参数传入

    ```someGetter: (state, getters, rootState, rootGetters) => { ???//todo}``````const store = new Vuex.Store({ ?getters: { ???someOtherGetter: state => ‘ROOT‘ ?}, ?modules: { ???moduleA: { ?????namespaced: true, ?????getters: { ???????someGetter(state,getters,rootState,rootGetters) { ?????????return getters.someOtherGetter; // INSIDE ?????????// return rootGetters.someOtherGetter; // ROOT ???????}, ???????someOtherGetter: state => "INSIDE" ?????} ???} ?}});console.log(store.getters[‘moduleA/someGetter‘]);```
  2. 也会通过context.rootStatecontext.rootGetts传入
    ??如

    ```someAction:(context) => { ???//todo}或者someAction:({ commit, dispatch, rootState, rootGetters }) => { ???//todo}```

模块内部希望使用全局mutation与全局action,只需要在执行分发或者提交的时候在第三个参数位置传入{ root: true }

dispatch('someOtherAction', null, {root: true});commit('someOtherMutation', null, {root: true});

栗子(没有写mutation与state的可以自行尝试)

const store = new Vuex.Store({ ?getters: { ???someOtherGetter: state => 'ROOT' ?}, ?actions: { ???someOtherAction() { ?????console.log('ROOT_ACTION'); ???} ?}, ?modules: { ???moduleA: { ?????namespaced: true, ?????getters: { ???????someGetter(state,getters,rootState,rootGetters) { ?????????return getters.someOtherGetter; // INSIDE ?????????// return rootGetters.someOtherGetter; // ROOT ???????}, ???????someOtherGetter: state => "INSIDE" ?????}, ?????actions: { ???????someAction({ dispatch, getters, rootGetters }) { ?????????console.log(getters.someOtherGetter);//INSIDE ?????????console.log(rootGetters.someOtherGetter);//ROOT ?????????dispatch('someOtherAction');//INSIDE_ACTION ?????????dispatch('someOtherAction', null, {root: true});//ROOT_ACTION ???????}, ???????someOtherAction() { ?????????console.log('INSIDE_ACTION'); ???????} ?????} ???} ?}});console.log(store.getters['moduleA/someGetter']);store.dispatch('moduleA/someAction');

当我们将store里的状态映射到组件的时候,会出现下面的问题

computed: { ???...mapState({ ???????b1:state => state.moduleA.moduleB.b1, ???????b2:state => state.moduleA.moduleB.b2 ???})},methods: { ???...mapActions({ ???????'moduleA/moduleB/action1', ???????'moduleA/moduleB/action2' ???})}是不是比较繁琐,代码太过冗余。

当然mapStatemapGettersmapMutationsmapActions这些辅助函数都可以将空间名称字符串作为第一个参数传递,这样上面的例子可以简化成

computed: { ???...mapState('moduleA/moduleB', { ???????b1:state => state.b1, ???????b2:state => state.b2 ???})},methods: { ???...mapActions('moduleA/moduleB', { ???????'action1', ???????'action2' ???})}

当然还有一个方法,使用Vuex提供的createNamespacedHelpers创建基于某个命名空间函数,它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。

import { createNamespacedHelpers } from 'vuex';const { mapState , mapActions } = createNamespacedHelpers('moduleA/moduleB');export default { ???computed: { ???????...mapState({ ???????????b1:state => state.b1, ???????????b2:state => state.b2 ???????}) ???}, ???methods: { ???????...mapActions({ ???????????'action1', ???????????'action2' ???????}) ???}}

模块重用
有时我们可能需要创建一个模块的多个实例:

  1. 创建多个 store,他们公用同一个模块
  2. 在一个 store 中多次注册同一个模块

如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态

const MyReusableModule = { ?state () { ???return { ?????foo: 'bar' ???} ?}, ?// mutation, action 和 getter 等等...}

原文地址:https://segmentfault.com/a/1190000012645384

Vuex-一个专为 Vue.js 应用程序开发的状态管理模式

原文地址:https://www.cnblogs.com/lalalagq/p/9960288.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved