108、Vuex 以及 vuex与redux的区别

原理请参考:(20条消息) 遇见面试--vuex原理_凌晨四点半er的博客-CSDN博客_vuex原理面试

在这里插入图片描述

一、什么是vuex

注意:在导入·vuex之前,必须先导入vue

vuex解决的问题:

二、获取vuex对象中state的共享数据(state)

vuex中的state相当于组件中的data,就是专门用于保存共享数据的

 (1)访问vuex中共享数据的前提,在祖先组件中保存vuex对象

(2)访问state中的属性值

访问state中msg属性值,不能通过普通的插值方式{{}}访问

访问state中msg属性值的正确方式:{{this.$store.state.msg}}

  • this.$store: 表示当前组件的vuex对象;
  • this.$store.state: 从当前vuex对象中找到state.

(3)共享数据的步骤总结:

  1. 导入vuex :import Vuex from 'vuex'
  2. 创建vuex对象:const store = new Vuex.Store(...)
  3. 在祖先组件中添加创建的vuex对象: store: store
  4. 在祖先及其后代中使用vuex中保存的共享数据:{{this.$store.state.msg}}

 三、使用mutations修改Vuex中的共享数据(mutations)

mutations:修改Vuex中state共享数据的唯一方法,用于保存 修改共享数据的方法。

(1)主要是为了解决下面问题:

注意:在Vuex中不推荐直接修改state中的共享数据

原因:如果多个组件都修改了共享的数据,那么后期数据发生了错误,我们如果需要去调试错误,就需要把每一个修改了共享数据的组件都检查一遍,这样非常低效,不利于我们去维护。

(2)在Vuex对象中添加mutations

在执行mutations中定义的方法时,系统会自动将Vuex中的state作为参数,传入所有的方法中。

(3)在组件中调用修改共享数据的方法

调用方式:this.$store.commit("方法名")。

通过this.$store.commit 获取Vuex中的mutations。

例如:执行mutation中的mAdd方法 this.$store.commit("mAdd")

也可在组件的methods或computed中,通过...mapMutations 来调用(将组件中的 methods 映射为 store.commit 调用)(需要在根节点注入 store

(4)Payload :传入mutations方法中的参数(不包含state),是一个对象。

例如:调用mutations中的setCurIdx方法,并传参{index,text}

四、Vuex中的action(执行异步操作)

起到通过派发指令给mutation修改state的数据状态的作用

一般在action中进行数据请求。

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

(1) 在Vuex中添加actions,并定义方法

例如:getData方法用来请求数据。

获取数据后,通过commit调用mutations中的方法,将获取的数据赋值给state中的共享变量。

  • 第一个参数ctx:表示上下文,可以获取Vuex中的commit,调用mutations中的方法
  • 第二个参数payload:表示传的参数对象

(2)调用actions中的方法:this.$store.dispatch('调用的方法名',传的参数)

Action 通过 store.dispatch 方法触发

(3)也可以在组件的methods中通过...mapActions 辅助函数来调用actions中的方法

五、Vuex中的getters(getters)

  • getters 类似于计算属性computed,会将返回值缓存,只有当依赖的数据发生变化才会重新计算
  • 系统会自动将Vuex中的state作为参数,传入getters的所有方法中

(1)向Vuex对象中添加getters

(2)调用getters中的方法:{{this.$store.getters.方法名}}

六、Vuex中的modules

当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter。

然后在store/index.js文件中导入所有模块,添加到modules中

七、mutation和action的详细区别

 
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
 

1、流程顺序

“相应视图—>修改State”拆分成两部分,视图触发Action,Action再触发Mutation,Mutation修改State

2、角色定位

基于流程顺序,二者扮演不同的角色。

Mutation:专注于修改State,理论上是修改State的唯一途径。

Action:业务代码、异步请求。

3、限制

角色不同,二者有不同的限制。

Mutation:必须同步执行。

Action:可以异步,但不能直接操作State。

4、Vuex为什么不允许在actions中修改状态state

dispatch代码大致如下:

dispatch (_type, _payload) {
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]

  // ...
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  // ...
  }
  1.  某种类型的action只有一个声明时,action的回调会被当作普通函数执行;
  2. 如果有多个声明时,它们是被视为Promise实例,并且用Promise.all执行
  • Promise.all在执行Promise时是不保证顺序的,也就是说,假如有3个Promise实例:P1、P2、P3,它们3个之中不一定哪个先有返回结果;
  • 如果同时在多个action中修改了同一个state。很有可能每个action赋给state的新值都有所不同,并且不能保证最后一个有返回结果action是哪一个action,所以最后赋予state的值可能是错误的
  • Vuex为什么要使用Promise.all执行action呢?其实也是出于性能考虑,这样我们就可以最大限度进行异步操作并发;

八、为什么不能直接修改state?要用mutation修改state,而不能用action修改?  

若将vue创建 store 的时候传入 strict: true, 开启严格模式,那么任何修改state的操作,只要不经过 mutation的函数都会报错。

(1)不能直接修改state的原因:

如果多个组件都修改了共享的数据,那么后期数据发生了错误,我们如果需要去调试错误,就需要把每一个修改了共享数据的组件都检查一遍,这样非常低效,不利于我们去维护。

(2)不用action修改state的原因:

如果同时在多个action中修改了同一个state。很有可能每个action赋给state的新值都有所不同,并且不能保证最后一个有返回结果action是哪一个action,所以最后赋予state的值可能是错误的

(3)用mutation修改state的原因:

通过commit 提交 mutation 的方式来修改 state 时,vue的调试工具能够记录每一次state的变化,这样方便调试。但是如果是直接修改state,则没有这个记录。

九、vuex的应用场景?

Vuex是通过全局注入store对象,来实现组件间的状态共享。 

  1.  总之,假如你需要 数据 和 组件 分离,分别处理,那么使用 Vuex 是非常合适的。
  2. 相反,如果不需要分离处理,那么不使用 Vuex 也没关系。

比如某个数据只跟某组件打交道,是强耦合的。那么这个数据就应该存放在该组件的 data 属性中。

  1. 如果只是简单的父子组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。
  2. 在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。
  3. 涉及到非父子关系的组件,例如兄弟关系、祖孙关系,甚至更远的关系;
  4. 他们之间如果有数据交互,需要数据和组件的分离,那么应该使用Vuex来实现;
  5. 如果页面复杂度比较低的话,也可以考虑使用 global-event-bus 来实现;
  6. 如果只是父子关系的组件数据交互,那么应该考虑使用props进行单向传递;
  7. 如果涉及到子组件向父组件的数据传递,那么应该考虑使用 $emit 和 $on

例如,在以下场景里,我们应当使用 Vuex:

1、组件会被销毁

我们可以假设这样一个场景:

  1. 假如有这样一个组件,他是弹窗,有一些复选框和输入框,用户会选择和填写信息;
  2. 然后这个弹窗会被关闭和打开,由于业务需要,这个弹窗输入的内容,希望关闭后可以保留,在重新打开后,内容依然存在。

解决办法:

  1. 我们可以考虑将值存在父组件中,也就是说,实际修改的是父组件的值;
  2. 存在比如 sessionStorage、cookies之类的里面,在 created 时从中读取,destroyed的时候写入其中;
  3. 可以存到 global-event-bus 里面;

但事实上,最好的还是存在 Vuex 里:

  1. 可以直接通过 $store.state 来调用,通过 commit() 来修改值;
  2. 也可以在 created 的时候,读取存在 state 里面的值,在 destroyed 的时候,写回 state;

这样处理的优点是解耦,不跟其他组件打交道。

2、组件基于数据而创建

我们可以假设这样一个场景:

  1. 用户登录后,读取权限配置表,这显然是一个异步操作;
  2. 这个配置表可能会影响很多页面。比如被影响的组件的加载条件,例如是 v-if="$store.state.userInfo.superVIP;

那么:

  1. 因为读取权限配置表这个异步操作,可能影响多个组件,而这些组件之间的关系,显然是不可预料的(即不一定是在同一个父组件下面);
  2. 那么这个异步操作,写在某一个组件里就不太合适(因为其他组件读取这个组件很不方便,即使他是根组件);

解决办法:

  1. 一个妥协的解决办法,是写在 global-event-bus 里面来实现;
  2. 但是显然,更好的解决办法是写在 vuex 里面更专业一些;

3、多对多事件——多处触发,影响多处

我们可以假设这样一个场景:

  1. 假如有一个事件,比如:切换页面显示风格,他将改变某一个变量的值;
  2. 当该变量为 true 时,那么页面风格为白天(主要影响 v-bind:style 的值);
  3. 当该变量为 false 时,那么页面风格为晚上(同上);
  4. 在多个地方可以切换这个页面风格开关;
  5. 毫无疑问,这个变量将影响多个地方的 v-bind:style 的值;
  6. 这就是 多对多 场景;

那么:

  1. 无论这个变量放在哪个组件里,其他组件调用他都是很麻烦的事情;
  2. 即使存于根组件,然后通过 this.$root.xx 来获取这个变量,也是很麻烦的,而且很丑陋;

解决办法:

  1. 如果不使用 Vuex,那么我们可能会去考虑使用 global-event-bus 来存储这个变量,并使用它;
  2. 这不是不可以,但不优雅,而且管理麻烦;
  3. 而使用 Vuex,那么这就是一件很方便的事情了;
  4. 我们可以通过 $store.state.xxx 来获取这个变量的值;
  5. 通过 $store.getters.yyy 来获取某些基于这个值的,表示通用样式(例如黑底白字)的对象;
  6. 通过 $store.commit() 来提交修改(比如在某些情况下可以禁止修改);
  7. 甚至可以通过 $store.dispatch() 来获取其他风格的样式,并通过 $store.state 和 $store.getters 来返回新风格的样式;

参考:详细分析Vuex 的应用场景_qq20004604的博客-CSDN博客_vuex使用场景

十、vuex的优缺点

  1. 为什么出现:vue一般是单项数据流,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:多个视图依赖于同一状态、来自不同视图的行为需要变更同一状态。
  2. 作用:多个组件共享数据或者是跨组件传递数据

 1)什么是 vuex ?

Vuex采用MVC模式中的Model层,规定所有的数据必须通过action—>mutaion—>state这个流程进行来改变状态的。再结合Vue的数据视图双向绑定实现页面的更新。统一页面状态管理,可以让复杂的组件交互变的简单清晰。

虽然vue中提供了props(父传子)commit(子传父)兄弟间也可以用localstorage和sessionstorage。但是这种方式在项目开发中带来的问题比他解决的问题(难管理,难维护,代码复杂,安全性低)更多。

  1. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
  2. 它采用集中式存储管理应用的所有组件的状态,
  3. 并以相应的规则保证状态以一种可预测的方式发生变化。(要按照规定的方式改变数据)

面对一个储存状态数据的工具时, 有几个方面的问题我们马上可以想到的

  1. 数据放在哪里: store state
  2. 如何存入/更新数据: commit mutation
  3. 如何获取数据: this.$store.state.模块.数据字段

2)vuex的优点:

  1. 限定了一种可预测的方式改变数据, 避免大项目中, 数据不小心的污染
  2. js 原生的数据对象写法, 比起 localStorage 不需要做转换, 使用方便
  3. 属于 vue 生态一环, 能够触发响应式的渲染页面更新 (localStorage 就不会)

3)vuex的缺点:

  1. 刷新浏览器,vuex中的state会重新变为初始状态
  2. 解决方案-插件vuex-persistedstate
     

十一、 vuex的运行过程

  1. 组件派发任务到actions(点击事件);
  2. actions触发mutations中的方法;
  3. 然后mutations来改变state中的数据;
  4. 一旦state数据发生更改,getters把数据反映到视图

十二、vuex 与 module的冲突? 

  • vuex中使用多模块时,不同模块中有命名冲突;
  • 定义module另外命名时,需要在module中加一个命名空间namespaced: true属性,否则命名无法暴露出来,导致报[vuex] module namespace not found in mapState()等错误。

解决: 

  1. vuex中的store分模块管理,需要在store的index.js中引入各个模块,为了解决不同模块命名冲突的问题,将不同模块的namespaced:true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名。
  2. 当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
     
// store.js
const moduleE = {
  namespaced: true,
  state: {
    name: 'xiaoming',
    age: 1
  }
}
export default new Vuex.Store({ // 将模块挂载到根store
  modules: { 
    moduleE, // 等同于moduleE : 上面模块的命名空间是moduleE
    // eee: moduleE, // 下面模块的命名空间是 eee
 }
});
// 带命名空间的绑定函数
computed: {
  // ...mapState('命名空间名', ["name"])   在辅助函数mapState的第一参数上,填写上模块的命名空间名

...mapState('moduleE', { // 命名空间名用法1
       name: 'name'
    })

...mapState('moduleE', ['name']) // 命名空间名用法2
  1. …mapState([ ‘age’]) 会执行一个函数,返回一个对象,通过…解构到computed上
  2. 执行函数时会判断传入的是字符串,还是对象或数组?

① 如果是对象或数组,都去根实例的state上找(所有module.state都挂载在store.state上)
对象{ age: state => state.age }:执行函数并传入根state作为参数,让它返回对应value
数组[ ‘age’ ]:通过key找到根state上的对应的value
② 如果是字符串,说明是用命名空间来获取值,则通过第一个参数(命名空间名)去根实例store._modulesNamespaceMap上找到对应的module模块,再通过第二个参数(key)找到state上对应的value返回

总结:都是通过key值在state上找到value值,组装成对象返回,然后再解构赋值到computed上
 

十三、vuex的原理

1)vuex的核心api:
install函数:用来注册插件到vue里(说白了就是在vue中执行这个函数,并把vue当作参数传入此函数,使用vue的方法和绑定store到各个组件上)
store类:state、getters、mutations、actions、modules、plugins
辅助函数:mapState、mapActions、mapMutations

2)围绕这些问题实现

  1. 怎么让每个vue组件都能拿到$store?
  2. 怎么实现state数据响应式?
  3. getters怎么实现?
  4. commit怎么去触发mutation,dispatch怎么触发actions?
     

1. 给每个实例注入$store,使每个组件都能拿到$store。

  • 在install方法中,使用vue.mixin方法,在beforeCreate的生命周期中(即创建前),给每个组件都增加$store属性。
  • 这使得当每个组件实例化的时候都会调用这个函数,给自己赋值一个store属性。
let Vue
const install = (_Vue) => { 
  Vue = _Vue
  // 使用vue的混入方法,在创建之前,给每个组件都增加$store属性
  Vue.mixin({
    // 创建之前会被执行
    beforeCreate () {
      // 根实例有store属性
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store  
      } else {
      // 根实例上没有的store属性,往父亲节点找
      // new Vue({store}) 这里已经在根组件挂载有store属性
        this.$store = this.$parent && this.$parent.$store 
      }
    }
  })
}
export default {
  install // 给用户提供一个install方法,默认会被调用
}

2. 设置state响应数据,实现state数据响应式。

利用vue的响应式原理,让state的修改都可以更新回视图,而不是单纯获取state数据

class Store {
  constructor (options) {
    // this.vm  = options.state   只是单纯获取state数据,但是数据修改不会更新界面
    /** 借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */
    this.vm = new _Vue({
      data: {
        state: options.state
      }
    })
  }
/* 类的属性访问器
    访问state对象时候,就直接返回响应式的数据
    Object.defineProperty get 同理
  */
  get state () {
    return this.vm.state
  }
}

 3、getters。getters从根本上就是computed,给你返回一些派生的状态(对数据进行过滤操作)

遍历用户传入的参数获取属性名,利用Object.defineProperty的get获取方法执行的结果,赋值到getters对象对应的属性名上,用户通过this.getters.myName就可以调用对应的值 

 在这里插入图片描述

参考:vuex实现原理_越思考越清晰-CSDN博客_vuex实现原理

十四、 vuex相比于redux做了什么改进?

  • VUEX是吸收了Redux的经验,放弃了一些特性并做了一些优化,代价就是VUEX只能和VUE配合。
  • Redux则是一个纯粹的状态管理系统,React利用React-Redux将它与React框架结合起来。
  • React-Redux:简单来说,它提供了一些接口,用于Redux的状态和React的组件展示结合起来,以用于实现状态与视图的一一对应。

redux的流程:

  1. 组件触发action,通过store.dispatch(action)将action传给reducer处理。action中只说明了要处理的数据state,没说明如何处理;
  2. reducer处理完action后,通过return,将数据state传给store;
  3. store通过subscribe监听reducer传的数据state。可通过getState()获取,setState()修改数据state;

 (1)vuex 与 redux对比

VuexRedux
核心对象storestore
数据存储statestate
状态state更新提交接口commit(触发mutation中的方法)store.dispatch(action)
 状态state更新提交参数带type和payload的mutation

action{

  type: '描述类型',

  value: ''

}

状态更新计算mutation handlerreducer(之前的state,action)
特性支持带缓存的getter,用于获取state经过某些计算后的值支持中间件
  1. VUEX弱化了dispatch的存在感:VUEX认为状态state变更的触发是一次“提交”而已,而调用方式是commit 接口。
  2. VUEX取消了Redux中Action的概念:不同于Redux认为状态变更必须是由一次"行为"触发,VUEX仅仅认为在任何时候触发状态变化只需要进行mutation即可。Redux的Action必须是一个对象,而VUEX认为只要传递必要的参数即可,形式不做要求。
  3. VUEX也弱化了Redux中的reducer的概念:reducer在计算机领域语义应该是"规约",在这里意思应该是根据旧的state和Action的传入参数,"规约"出新的state。在VUEX中,对应的是mutation,只是根据入参对旧state进行"转变"而已。
  • 总的来说,VUEX通过弱化概念,在任何东西都没做实质性削减的基础上,使得整套框架更易于理解了。
  • 另外VUEX支持getter,运行中是带缓存的,算是对提升性能方面做了优化工作。

(2)vuex 与 react-redux 对比

VuexReact-Redux
状态注入组件Vue.use(Vuex)将Vuex应用为全局的plugin,再将store对象传入根VUE实例

通过<Provider/>组件结合connect方法。provider组件:作为跟组件,包裹整个结构,让所有组件都能获取到store;connect方法:如果需要接收provider提供的store,就要通过connect方法对组件加强。

包含的内容

state: 存储数据;

mutations:更改数据;

action:执行异步操作,一般进行数据请求;

state:存储数据;

action: 是把数据从应用传到store的载体。组件触发action,action通过store.dispatch(action)传到reducer。只是描述了有事情发生,没描述如何去更新state;

reducer:相当于中转站,用来处理action,并把state发送给store()。描述了如何更新state;

store:通过监听函数,监听reducer发过来的数据;

特性VUEX提供mapState,mapGetter,mapMutation等方法,用于生成store内部属性对组件内部属性的映射connect支持mapStateToProps方法(将数据state作为props绑定到组件),mapDispatchToProps(将action作为props绑定到函数),用于自定义映射。

通过使用方式上的较大差异,也可以看出理念上的不同。
1)和组件结合方式的差异:

  • VUE通过VUEX全局插件的使用,结合将store传入根实例的过程,就可以使得store对象在运行时存在于任何vue组件中。
  • 而React-Redux则除了需要在较外层组件结构中使用<Provider/>以拿到store之外,还需要显式指定容器组件,即用connect包装一下该组件。
  • 这样看来我认为VUE是更推荐,在使用了VUEX的框架中的每个组件内部都使用store,而React-Redux则提供了自由选择性。VUEX即不需要使用外层组件,也不需要类似connect方式将组件做一次包装,我认为出发点应该是可能是为了避免啰嗦。

2)容器组件的差异:

React-Redux提倡容器组件和表现组件分离的最佳实践,而VUEX框架下不做区分,全都是表现(展示)组件。我觉得不分优劣,React-Redux的做法更清晰、更具有强制性和规范性,而VUEX的方式更加简化和易于理解。

总的来说,就是谁包谁,谁插谁的问题。Redux毕竟是独立于React的状态管理,它与React的结合则需要对React组件进行一下外包装。而VUEX就是为VUE定制,作为插件、以及使用插入的方式就可以生效,而且提供了很大的灵活性。
 

补充:

持久化插件plugins

作用:把state都存储在localStorage里面,刷新不会丢失数据

原理:发布订阅模式
实例store的时候,遍历plugins里面的函数,并执行 this.subscribe() 订阅到sote._subscribe数组上
当监测到mutation有变化的时候,依次执行所有的订阅

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值