Vuex-一个专为 Vue.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)):


//挂载App
new 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.js
    export 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); //A
console.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);// 2
    store.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']);// 0
console.log(store.getters['moduleA/count1']);// 1
console.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

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

时间: 2024-10-08 06:39:28

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

Vuex ~ 专门为vue.js设计的集中式状态管理架构

状态:data中的属性需要共享给其他vue组件使用的部分(即data中需要共用的属性) 1.初识vuex直接来个小demo 下面操作都是基于vue-cli,如果不了解先学习下vue-cli 利用npm包管理工具,进行安装 vuex. npm install vuex --save 新建一个vuex文件夹(这个不是必须的),并在文件夹下新建store.js文件,文件中引入我们的vue和vuex. import Vue from 'vue'; import Vuex from 'vuex';   使

理解Vue的状态管理模式Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 状态管理模式.集中式存储管理,一听就很高大上,蛮吓人的.在我看来 vuex 就是把需要共享的变量全部存储在一个对象里面,然后将这个对象放在顶层组件中供其他组件使用.这么说吧,将vue想作是一个js文件.组件是函数,那么vuex就是一个全局变量,只是这个"全局变量"包含了一些特定的规则而已. 在vue的组件化开发中,经常会遇到需要将

基于vue前端状态管理模式

本文仅介绍及区分localstorage.vuex以及vue的全局变量.组件. 一.localstorage简介 localStorage.sessionStorage以及cookie都是在浏览器用来存储数据的,只是作用于浏览器,不会存在与服务器交互的情况.sessionStorage只在当前的域名中有效,重新打开一个新窗口就会重新创建一个sessionStorage对象.而localstorage会一直存在,直到我们手动清除浏览器数据,否则会一直存在浏览器中. 二.vuex简介 vuex是专门

Flask Vue.js全栈开发

Flask Vue.js全栈开发 1. Flask Vue.js全栈开发教程系列 Flask Vue.js全栈开发|第1章:创建第一个Flask RESTful API Flask Vue.js全栈开发|第2章:Vue.js通过axios访问Flask RESTful API Flask Vue.js全栈开发|第3章:Flask设计User用户相关API Flask Vue.js全栈开发|第4章:Vue.js调用API实现用户注册/登录/退出 Flask Vue.js全栈开发|第5章:个人主页与

一周一个小demo — vue.js实现备忘录功能

这个vue实现备忘录的功能demo是K在github上找到的,K觉得这是一个用来对vue.js入门的一个非常简单的demo,所以拿在这里共享一下. (尊重他人劳动成果,从小事做起~  demo原github地址:https://github.com/vuejs/vue) 一.实现效果 二.代码展示 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>备忘录&l

Vuex内容解析和vue cli项目中使用状态管理模式Vuex

中文文档:vuex官方中文网站 一.vuex里面都有些什么内容? const store = new Vuex.Store({ state: { name: 'weish', age: 22 }, getters: { personInfo(state) { return `My name is ${state.name}, I am ${state.age}`; } } mutations: { SET_AGE(state, age) { commit(age, age); } }, acti

Vue.js项目的开发环境搭建与运行

写作背景:手上入一个用Vue框架写的微信公众号项目,根据公司安排,我负责项目源代码的验收工作(当然专业的工作检测会交给web开发人员,我只是想运行起来看一看). 1 开发环境安装步骤: (一)安装node.js(JavaScript运行环境runtime) 从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以完成. 完成之后,开发命令行工具,输入 node -v 如果出现相应的版本号,则说明安装成功. 另外,npm是node.js下的包管理器,npm能很好的和诸如webp

vue.js组件化开发实践

前言 公司以往制作一个H5活动,特别是有一定统一结构的活动都要每次用html.css.js滚一次重复的轮子,费时费力.后来接到了一个基于模板的活动发布系统的需求,于是就有了下面的内容. 开始 需求一到,接就是怎么实现,技术选型自然成为了第一个问题.鉴于目前web前端mvvm框架的流行,以及组件化开发方式的出现,决定采用vue进行开发. 这里首先简单说下web前端组件化开发方式的历程: 最早的组件化结构,或者叫做组件化1.0时代,代码结构可能如下: 1 - lib/components/calen

vue.js 2.0开发(4)

使用vue-cli,首先安装: npm install -g vue-cli 安装完了执行vue命令,会出现 vue init <template-name> <project-name> project-name就是我们项目目录的名字,vue init webpack my-project,这里的webpack就是我们的构建工具,下面我们运行: vue init webpack vuejs-2.0-cli 下面就是一些  项目名 啊 项目描述啊  作者啊 巴拉巴拉的 然后就弹出一