美文网首页
Vuex(一) —— 集中式的状态管理仓库

Vuex(一) —— 集中式的状态管理仓库

作者: 顽皮的雪狐七七 | 来源:发表于2020-12-31 21:40 被阅读0次

    目录

    • Vue组件间通信方式回顾
      • 组件内的状态管理流程
      • 组件间通信方式
        • 父组件给子组件传值 (最简单的一种方式)
        • 子组件给父组件传值
        • 不相关组件之间传值
        • 其他常见方式($ref)
    • 简易的状态管理方案
      • 上面组件间通信方式的问题
      • 集中式的状态管理方案
    • Vuex
      • 什么是Vuex?
      • 什么情况下使用Vuex?
        • 非必要的情况不要使用Vuex
        • 中大型单页应用程序使用更好
      • Vuex核心概念回顾
      • Vuex基本结构
      • State的使用
      • Getter的使用
      • Mutation的使用
        • Mutation的调试
          • 时光旅行
          • 状态回滚
          • 提交改变
      • Actions的使用
      • Modules的使用
        • 模块定义
        • 模块注册
        • 模块使用
        • 添加命名空间
      • Vuex严格模式
      • Vuex插件
        • 插件的使用

    年底最后一天来本来没有想更博客,但是留下我今年还在努力学习的印记哈哈。看了半天这居然是我第一个关于vue的博客,努力不算晚。先来对这个博客的内容进行一下梳理,有兴趣的小伙伴可以跳到自己感兴趣的地方,这个博客比较长,长文警告。

    image

    Vue组件间通信方式回顾

    组件内的状态管理流程

    Vue最核心的两个功能:数据驱动组件化

    使用基于组件化的开发,可以提高开发效率,带来更好的可维护性。

    new Vue({
        // state 组件内部都可以管理自己的内部状态
        data () {
            return {
                count: 0
            }
        },
        // view 视图,每个组件都有自己的视图,把状态绑定到视图上,当用户和视图交互的时候,可能会更改状态
        template: `<div>{{ count }}</div>`,
        // actions 行为,这里描述的是单个组件内部的状态管理,实际开发中可能多个组件可以共享状态
        methods: {
            increment () {
                this.count++
            }
        }
    })
    

    这里说的状态管理 —— 是通过状态,集中管理和分发,解决多个组件共享状态的问题。

    • state:状态,数据源。
    • view:视图。通过把状态绑定到视图呈现给用户
    • actions:用户和视图交互改变状态的方式
    image

    图中表明,状态绑定到视图上呈现给用户,用户通过与视图交互改变状态,之后改变了的状态再绑定到视图会后呈现给用户。
    单向的数据流程很简单清晰,但是多个组件共享数据会破坏这种简单的结构。

    组件间通信方式的回顾

    大多数情况下,组件都不是孤立存在的,他们需要共同协作构成一个复杂的业务功能,在Vue中,为不同的组件关系提供了不同的通信规则。

    常见的组件间通信的方式有:

    父组件给子组件传值 (最简单的一种方式)

    • 父组件中给子组件通过相应属性传值
    • 子组件通过props接受数据
    <!--子组件-->
    <template>
      <div>
        <h1>Props Down Child</h1>
        <h2>{{ title }}</h2>
      </div>
    </template>
    
    <script>
    export default {
      // 子组件中通过props来接收父组件传的值
      // props可以是数组也可以是对象
      // 如果想约定传值的类型用对象,这里title定了是string类型,如果传number类型会报错
      // props: ['title'],
      props: {
        title: String
      }
    }
    </script>
    
    <style>
    
    </style>
    
    <!--父组件-->
    <template>
      <div>
        <h1>Props Down Parent</h1>
        <!--2. 使用子组件的使用通过属性给子组件传值,这里也可以是表达式,绑定data中的成员-->
        <child title="My journey with Vue"></child>
      </div>
    </template>
    
    <script>
    import child from './01-Child'
    export default {
     // 1. 注册子组件
      components: {
        child
      }
    }
    </script>
    
    <style>
    
    </style>
    

    子组件给父组件传值

    • 子组件通过自定义事件,用$emit触发的时候携带参数给父组件传值
    • 父组件通过$on注册子组件内部触发的事件,并接收传递的数据,行内可以通过$event获取事件传递的参数 (事件处理函数中是不这么使用的)
    <!--子组件-->
    <template>
      <div>
        <h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
        <button @click="handler">文字增大</button>
      </div>
    </template>
    
    <script>
    export default {
     // 通过props接收父组件传的默认字体大小
      props: {
        fontSize: Number
      },
      methods: {
        handler () {
        // 当点击按钮的时候,触发自定义事件enlargeText放大字体,让字体放大0.1
        // this是当前子组件对象,this.$emit这个是由子组件触发的自定义事件,当注册事件的时候要给子组件注册该事件
          this.$emit('enlargeText', 0.1)
        }
      }
    }
    </script>
    
    <style></style>
    
    <!--父组件-->
    <template>
      <div>
      <!--父组件将fontSize进行绑定-->
        <h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>
    
        这里的文字不需要变化
        <!--使用子组件,通过v-on给子组件设置了自定义方法enlargeText-->
        <child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
        <child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
        <!--还有一种在行内获取值的方式,获取自定义事件传递数据的时候,直接通过$event获取,这个值是触发事件传递的0.1-->
        <child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
      </div>
    </template>
    
    <script>
    import child from './02-Child'
    export default {
      components: {
        child
      },
      data () {
        return {
          hFontSize: 1
        }
      },
      methods: {
      // 子组件把值传递给了父组件,父组件通过参数接收到了值,进行运算
        enlargeText (size) {
          this.hFontSize += size
        }
      }
    }
    </script>
    
    <style></style>
    

    不相关组件之间传值

    • 也是使用自定义事件的方式,但是因为没有父子关系,所以不能通过子组件触发传值,所以这里需要使用eventBus,即创建一个公共的Vue实例,这个实例的作用是作为事件总线,或者事件中心。
    • eventBus:创建一个Vue实例,这个实例并不是用来展示内容,所以没有传递任何的选项,我们使用他的目的是使用$emit$on,用来触发和注册事件。
    • 触发事件组件:通过$emit触发事件,并传递参数
    • 注册事件组件:通过$on注册事件,接收参数进行处理
    // eventbus.js
    import Vue from 'vue'
    export default new Vue()
    
    <!--组件一:触发事件-->
    <template>
      <div>
        <h1>Event Bus Sibling01</h1>
        <div class="number" @click="sub">-</div>
        <input type="text" style="width: 30px; text-align: center" :value="value">
        <div class="number" @click="add">+</div>
      </div>
    </template>
    
    <script>
    // 这个组件要触发事件,将事件中心导入
    import bus from './eventbus'
    
    export default {
    // props参数num
      props: {
        num: Number
      },
      // 因为props的值不能随便改动,所以传递给value
      created () {
        this.value = this.num
      },
      data () {
        return {
          value: -1
        }
      },
      methods: {
      // 减值操作,判断不能为0
        sub () {
          if (this.value > 1) {
            this.value--
            // 触发bus的自定义事件numchange,并把value当参数传递出去
            bus.$emit('numchange', this.value)
          }
        },
        // 加值操作,和减值类似
        add () {
          this.value++
          bus.$emit('numchange', this.value)
        }
      }
    }
    </script>
    
    <style>
    .number {
      display: inline-block;
      cursor: pointer;
      width: 20px;
      text-align: center;
    }
    </style>
    
    <!--组件二:定义-->
    <template>
      <div>
        <h1>Event Bus Sibling02</h1>
    
        <div>{{ msg }}</div>
      </div>
    </template>
    
    <script>
    // 因为要注册事件,所以将事件中心导入
    import bus from './eventbus'
    export default {
      data () {
        return {
          msg: ''
        }
      },
      created () {
        // 通过bus注册了numchange事件,事件处理函数中接收事件触发时候传递的参数,进行展示
        bus.$on('numchange', (value) => {
          this.msg = `您选择了${value}件商品`
        })
      }
    }
    </script>
    
    <style>
    
    </style>
    
    <!--App.vue-->
    <template>
      <div id="app">
        <h1>不相关组件传值</h1>
        <sibling0301 :num="num"></sibling0301>
        <sibling0302></sibling0302>
      </div>
    </template>
    
    <script>
    import sibling0301 from './components/03-event-bus/03-Sibling-01'
    import sibling0302 from './components/03-event-bus/03-Sibling-02'
    
    export default {
      name: 'App',
      components: {
        sibling0301,
        sibling0302,
      },
      data () {
        return {
          num: 1
        }
      }
    }
    </script>
    
    <style></style>
    

    其他常见方式

    • $root$parent$children$ref,通过这几种属性获取根组件成员,实现组件之间的通信。但这些都是不被推荐的方式。只有当项目很小,或者在开发自定义组件的时候,才会使用到。如果是大型项目的话,还是推荐使用Vuex管理状态。

    下面举例通过$refs获取子组件的状态,其他属性可以自己查看文档。

    ref的两个作用

    1. 在普通HTML标签上使用ref,用$refs获取到的是DOM对象
    2. 在组件标签上使用ref,用$refs获取到的是组件实例
    <!--子组件,一个简单的自定义组件,功能是能够获取焦点的自定义文本框。-->
    <template>
      <div>
        <h1>ref Child</h1>
        <!--这个input标签上设置了ref属性-->
        <input ref="input" type="text" v-model="value">
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
          value: ''
        }
      },
      methods: {
        focus () {
          // 通过this.$refs.input获取input的DOM对象,并调用其focus方法让文本框获取焦点
          this.$refs.input.focus()
        }
      }
    }
    </script>
    
    <style></style>
    
    <!--父组件,-->
    <template>
      <div>
        <h1>ref Parent</h1>
        <!--在子组件的标签上设置了ref-->
        <child ref="c"></child>
      </div>
    </template>
    
    <script>
    import child from './04-Child'
    export default {
      components: {
        child
      },
      mounted () {
        // 这里想要拿到子组件的话,必须要等组件渲染完毕,所以这里在mounted函数下
        // 这里通过this.$refs.c就是子组件对象,拿到这个对象就可以访问其属性和方法
        // 这里调用子组件方法让其内部获取焦点
        this.$refs.c.focus()
        // 通过value属性给文本框赋值
        this.$refs.c.value = 'hello input'
      }
    }
    </script>
    
    <style>
    
    </style>
    

    还是一句话不建议使用,如果滥用这种方式的话可以造成状态管理的混乱。

    简易的状态管理方案

    上面组件间通信方式的问题

    如果多个组件之间需要共享状态,使用之前演示的方式虽然都可以实现,但是比较麻烦,而且多个组件之间进行传值,很难跟踪到数据的变化。如果出现问题的话,很难定位问题。当遇到多个组件需要共享状态的时候,典型的场景如购物车,我们使用之前介绍的方案都不合适,可能会遇到以下的问题:

    • 多个视图依赖同一状态,如果多层嵌套的组件依赖同一状态,使用父子组件传值可以实现,但是非常麻烦而且不易管理。
    • 来自不同视图的行为需要变更同一状态,我们可以通过父子组件的方式对状态进行修改,或者通过事件机制来改变,或者同步状态的变化,以上这些方式非常的脆弱,通常会导致产生无法维护的代码。

    集中式的状态管理方案

    为了解决这些问题,我们把不同组件的共享状态抽取出来,存储到一个全局对象中并且将来使用的时候保证其实响应式的。这个对象创建好之后里面有全局的状态和修改状态的方法,我们的任何组件都可以获取和通过调用对象中的方法修改全局对象中的状态 (组件中不允许直接修改对象的state状态属性)。

    把多个组件的状态放到一个集中的地方存储,并且可以检测到数据的更改,这里先不使用Vuex,我们自己先进行一个简单的实现。

    1. 创建一个全局的store.js

    集中式的状态管理,所有的状态都在这里。这个模块中导出了一个对象,这对象就是状态仓库且全局唯一的对象,任何组件都可以导入这个模块使用

    这里面有state,还有actionsstate是用来存储状态,actions是用户交互更改视图用的。还有一个debug的属性,方便开发调试。

    // store.js
    export default {
      debug: true,
      state: {
        user: {
          name: 'xiaomao',
          age: 18,
          sex: '男'
        }
      },
      setUserNameAction (name) {
        if (this.debug) {
          console.log('setUserNameAction triggered:', name)
        }
        this.state.user.name = name
      }
    }
    
    1. 在组件中导入
    <!--组件A-->
    <template>
      <div>
        <h1>componentA</h1>
        <!--3. 可以在视图中直接用点的方式显示数据-->
        user name: {{ sharedState.user.name }}
        <button @click="change">Change Info</button>
      </div>
    </template>
    
    <script>
    // 1. 在组件中导入store
    import store from './store'
    export default {
      methods: {
        // 4. 当点击按钮的时候,调用store的方法,将值改为componentA
        change () {
          store.setUserNameAction('componentA')
        }
      },
      data () {
        return {
          // 当前组件还可以有自己的私有状态,存在privateState中
          privateState: {},
          // 2. 将store的state属性赋值给shareState
          sharedState: store.state
        }
      }
    }
    </script>
    
    <style></style>
    
    <!--组件B,用法与上面一样,就是修改名字的时候值为componentB-->
    <template>
      <div>
        <h1>componentB</h1>
        user name: {{ sharedState.user.name }}
        <button @click="change">Change Info</button>
      </div>
    </template>
    
    <script>
    import store from './store'
    export default {
      methods: {
        change () {
          // 修改名字的时候改成了componentB
          store.setUserNameAction('componentB')
        }
      },
      data () {
        return {
          privateState: {},
          sharedState: store.state
        }
      }
    }
    </script>
    
    <style></style>
    

    上面组件A和组件B都共享了全局的状态,并且用户都可以更改状态。调试的时候,按A组件的按钮两者都变成了componentA,点B组件的按钮两者都变成了componentB

    我们不在组件中直接修改状态的值而是通过调用storeactions来修改值,这样记录的好处是: 能够记录store中所以state的变更,当可以实现记录storestate的变更时候,就可以实现高级的调试功能。例如:timeTravel(时光旅行)和历史回滚功能。

    刚才使用的store,其实就类似于Vuex的仓库。

    当项目比较复杂,多个组件共享状态的时候,使用组件间通信的方式比较麻烦,而且需要维护。这个时候我们可以使用集中式的状态解决方案 —— Vuex

    Vuex

    好的终于进入了主题~~

    什么是Vuex?

    • Vuex官网
    • Vuex 是专门为 Vue.js 设计的状态管理库,从使用的角度其实就是一个JavaScript
    • 它采用集中式的方式存储需要共享的数据,如果状态特别多的话不易管理,所以Vuex还提供了一种模块的机制,按照模块管理不同的状态
    • 它的作用是进行状态管理,解决复杂组件通信,数据共享
    • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了time-travel时光旅行、历史回滚、状态快照、导入导出等高级调试功能

    什么情况下使用Vuex?

    非必要的情况不要使用Vuex

    Vuex 可以帮助我们管理组件间共享的状态,但是在项目中使用Vuex的话,我们需要了解Vuex中带来的新的概念和一些API,如果项目不大,并且组件间共享状态不多的情况下,这个时候使用Vuex给我们带来的益处并没有付出的时间多。此时使用简单的 store 模式 或者其他方式就能满足我们的需求。

    中大型单页应用程序使用更好

    中大型单页应用程序中,使用Vuex可以帮我们解决多个视图依赖同一状态来自不同视图的行为需要变更同一状态的问题。建议符合这些情况的业务,使用Vuex进行状态管理,会给我们提供更好的处理组件的状态,带来的收益会更好些。例如典型案例:购物车。

    注意:不要滥用Vuex,否则会让业务变得更复杂。

    Vuex核心概念回顾

    下面这张图展示了Vuex的核心概念并且展示了Vuex的整个工作流程

    image
    • Store:仓库,Store是使用Vuex应用程序的核心,每个应用仅有一个Store,它是一个容器,包含着应用中的大部分状态,当然我们不能直接改变Store中的状态,我们要通过提交Mutations的方式改变状态。
    • State:状态,保存在Store中,因为Store是唯一的,所以State也是唯一的,称为单一状态树,这里的状态是响应式的。
    • Getter:相当于Vuex中的计算属性,方便从一个属性派生出其他的值,它内部可以对计算的结果进行缓存,只有当依赖的状态发生改变的时候,才会重新计算。
    • Mutation:状态的变化必须要通过提交Mutation来完成。
    • Actions:与Mutation类似,不同的是可以进行异步的操作,内部改变状态的时候都需要改变Mutation
    • Module:模块,由于使用的单一状态树让所有的状态都会集中到一个比较大的对象中,应用变得很复杂的时候,Store对象就会变得相当臃肿,为了解决这些问题Vuex允许我们将Store分割成模块,每个模块拥有自己的StateMutationActionsGetter,甚至是嵌套的子模块。

    Vuex基本结构

    使用vue-cli创建项目的时候,如果选择了Vuex,会自动生成Vuex的基本结构。

    // store.js
    import Vue from 'vue'
    // 导入Vuex插件
    import Vuex from 'vuex'
    // 通过use方法注册插件
    // 插件内部把Vuex的Store注入到了Vue的实例上
    Vue.use(Vuex)
    // 创建了Vuex的Store对象并且导出
    export default new Vuex.Store({
        state: {
            ...
        },
        mutations: {
            ...
        },
        actions: {
            ...
        },
        modules: {
            ...
        }
        // 如果有需要还可以有getters
    })
    
    //App.js
    // 导入store对象
    import store from './store'
    new Vue({
        router,
        // 在初始化Vue的时候传入store选项,这个选项会被注入到Vue实例中
        // 我们在组件中使用的this.$store就是在这个地方注入的
        store,
        render: h => h(App)
    }).$mount('#app')
    

    State的使用

    1. 下载项目模板vuex-sample-tempnpm install下载依赖,在store/index.js中定义两个state
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        count: 0,
        msg: 'hello vue'
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    
    1. App.vue中使用state,然后使用npm run serve查看结果
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        count: {{ $store.state.count}} <br/>
        msg: {{ $store.state.ms }}
      </div>
    </template>
    <script>
    
    1. 每次使用变量都要前面写 $store.state 很是麻烦,所以这里使用``Vuex内部提供的myState的函数,会帮我们生成状态对应的计算属性
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <!--4. 在使用的时候直接用计算属性count和msg即可-->
        count: {{ count }} <br/>
        msg: {{ msg }}
      </div>
    </template>
    <script>
      // 1. 引入vuex的mapState模块
      import { mapState } from 'vuex'
      export default {
        // 2. 在计算属性中调用mapState函数
        computed: {
          // 3. mapState需要接收数组作为参数,数组的元素是需要映射的状态属性
          // 会返回一个对象,包含两个对应属性计算的方法
          // { count: state => state.count, msg: state => state.msg }
          // 然后这里使用扩展运算符展开对象,完成之后我们就有了count和msg两个计算属性
          ...mapState(['count', 'msg'])
        }
      }
    </script>
    
    1. 上面的方法比较简洁但是如果这个组件中本身就有count或者msg属性,就会造成名称冲突,这个时候需要设置别名。
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <!-- 使用的时候直接使用别名即可 -->
        count: {{ num }} <br/>
        msg: {{ message }}
      </div>
    </template>
    <script>
    import { mapState } from 'vuex'
    export default {
      computed: {
        // mapState可以传对象,键是别名,值是映射的状态属性
        ...mapState({ num: 'count', message: 'msg' })
      }
    }
    </script>
    

    Getter的使用

    Vuex中的getter就相当于组件中的计算属性,如果想要对state的数据进行简单的处理在展示,可以使用getter

    这里用Vuexgetter处理而不是用组件中的计算属性是因为状态本身属于Vuex,应该在其内部处理

    1. store.js中设置getters
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        count: 0,
        msg: 'hello vue'
      },
      // 与计算属性的写法一致
      getters: {
        reverseMsg (state) {
          return state.msg.split('').reverse().join('')
        }
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    
    
    1. App.vue中使用
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <h2>reverseMsg: {{ $store.getters.reverseMsg }}</h2>
        <br/>
      </div>
    </template>
    
    1. 同样那样引用过于麻烦,那么和mapState一样,使用内部的mapGetters,也是将其映射到组件的计算属性,其用法和mapState一样,也可以为了避免冲突使用对象设置别名。
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <h2>reverseMsg: {{ reverseMsg }}</h2>
        <br/>
      </div>
    </template>
    <script>
    // 1. 引入vuex的mapGetters模块
    import { mapGetters } from 'vuex'
    export default {
      // 2. 在计算属性中调用mapGetters函数
      computed: {
        // 3. 用法与mapState一致,这里也可以使用对象设置别名
        ...mapGetters(['reverseMsg'])
      }
    }
    </script>
    

    Mutation的使用

    状态的修改必须提交MutationMutation必须是同步执行的。

    1. 当用户点击按钮的时候,count值进行增加,先在store.js中写Mutation函数
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        count: 0,
        msg: 'hello vue'
      },
      mutations: {
        // 增加函数,接收两个参数
        // 第一个state状态
        // 第二个是payload载荷,payload是mutations的时候提交的额外参数,可以是对象,这里传递的是数字
        increate (state, payload) {
          state.count += payload
        }
      },
      actions: {
      },
      modules: {
      }
    })
    
    
    1. App.vue中设置按钮,并注册事件
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        count: {{ $store.state.count }} <br/>
        <h2>Mutation</h2>
        <!-- 给按钮注册点击事件,点击的时候调用commit提交Mutation,第一个参数是调用的方法名,第二个参数是payload,传递的数据 -->
        <button @click="$store.commit('increate', 2)">Mutation</button>
      </div>
    </template>
    
    1. 点击按钮的时候,count的值每次+2
    2. 下面进行写法优化,使用map方法将当前的mutation映射到methods中,其依旧会返回一个对象,这个对象中存储的是mutation中映射的方法
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        count: {{ $store.state.count }} <br/>
        <h2>Mutation</h2>
        <!-- 这里直接写方法,,第一个参数state不需要传递,后面传payload参数为3 -->
        <button @click="increate(3)">Mutation</button>
      </div>
    </template>
    <script>
    // 1. 引入vuex的mapMutations模块
    import { mapMutations } from 'vuex'
    export default {
      // 2. methods中调用mapMutations方法
      methods: {
        ...mapMutations(['increate'])
      }
    }
    </script>
    

    Mutation的调试

    运行到4之后,这时看一下devtools看一下时光旅行和历史回滚,下面是初始状态

    image

    点一下按钮之后就增加了一个记录,还显示了改变之后的数据

    image

    如果数据不对,可以进行调试。

    时光旅行

    然后多点几下,进行时光旅行。

    image

    点击按钮之后,状态就变成了之前那个状态,这个功能也是为了方便调试

    image
    状态回滚

    这个图标就是状态回滚

    image

    点击之后,代码就回到了没有执行这一步的状态

    image
    提交改变

    下面那个按钮的意思是将这次提交作为最后一次提交

    image

    点击之后,base State变成了那次的状态,其他的状态以这个作为起始点

    image

    Actions的使用

    如果有异步的修改,需要使用actions,在actions中可以执行异步操作,当异步操作结束后,如果需要更改状态,还需要提交Mutation

    1. actions中添加方法increateAsync
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        count: 0,
        msg: 'hello vue'
      },
      mutations: {
        increate (state, payload) {
          state.count += payload
        }
      },
      actions: {
        // actions中的方法有两个参数:第一个参数是context上下文,这个对象中有state,commit,getters等成员,第二个参数是payLoad
        increateAsync (context, payLoad) {
          setTimeout(() => {
            context.commit('increate', payLoad)
          }, 2000)
        }
      },
      modules: {
      }
    })
    
    1. App.vue中使用dispatchactions的方法都要用这个
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        count: {{ $store.state.count }} <br/>
        <h2>Actions</h2>
        <!--这里使用了dispatch-->
        <button @click="$store.dispatch('increateAsync',5)">Action</button>
      </div>
    </template>
    
    1. 进行优化,这个时候引入mapActions
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        count: {{ $store.state.count }} <br/>
        <h2>Actions</h2>
        <button @click="increateAsync(5)">Action</button>
      </div>
    </template>
    <script>
    // 1. 引入vuex的mapActions模块
    import { mapActions } from 'vuex'
    export default {
      methods: {
        // 这个是对Actions的方法的映射,把this.increateAsync映射到this.$store.dispatch
        ...mapActions(['increateAsync'])
      }
    }
    </script>
    

    Modules的使用

    模块可以让我们把单一状态树拆分成多个模块,每个模块都可以拥有自己的statemutationactiongetter甚至嵌套子模块。

    模块定义

    store文件夹中,创建一个modules文件夹,里面每一个js文件就是一个模块,下面是每一个模块的定义格式

    const state = {
      products: [
        { id: 1, title: 'iPhone 11', price: 8000 },
        { id: 2, title: 'iPhone 12', price: 10000 }
      ]
    }
    const getters = {}
    const mutations = {
      setProducts (state, payload) {
        state.products = payload
      }
    }
    const actions = {}
    
    export default {
      namespaced: false,
      state,
      getters,
      mutations,
      actions
    }
    
    

    模块注册

    1. 先导入这个模块
    import products from './modules/products'
    import cart from './modules/cart'
    
    1. 后来在modules选项中注册,注册之后这里会把模块挂载到storestate中,这里可以通过store.state.products访问到products模块中的成员,还把的模块中的mutation成员记录到了store的内部属性_mutation中,可以通过commit直接提交mutation
    export default new Vuex.Store({
      state: {},
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        products,
        cart
      }
    })
    

    模块使用

    1. App.vue中使用,state就点出来,mutation还是用commit方法
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <h2>Modules</h2>
        <!--第一个products是products模块,第二个products是模块的state的products属性-->
        products: {{ $store.state.products.products }} <br/>
        <button @click="store.commit('setProducts',[])">Mutation</button>
      </div>
    </template>
    

    添加命名空间

    因为每个模块中的mutation是可以重名的,所以推荐使用命名空间的用法,方便管理。

    1. 在开启命名空间的时候,在模块的导出部分添加namespaced
    const state = {}
    const getters = {}
    const mutations = {}
    const actions = {}
    
    export default {
      // true就是开启,false或者不写就是关闭
      namespaced: false,
      state,
      getters,
      mutations,
      actions
    }
    
    
    1. 使用的时候在App.vue中要设置state是模块中出来的,如果没有命名空间,就是全局的state的。
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        products: {{ products }} <br/>
        <button @click="setProducts([])">Mutation</button>
      </div>
    </template>
    <script>
    import { mapState, mapMutations } from 'vuex'
    export default {
      computed: {
        // 模块中的state,第一个参数写模块名称,第二个参数写数组或者对象
        ...mapState('products', ['products'])
      },
      methods: {
        // 模块中的mutations,第一个写模块名称,第二个写数组或者对象
        ...mapMutations('products', ['setProducts'])
      }
    }
    </script>
    

    Vuex严格模式

    所有的状态变更必须提交mutation,但是如果在组件中获取到$store.state.msg进行修改,语法层面没有问题,却破坏了Vuex的约定,且devTools也无法跟踪到状态的修改,开启严格模式之后,如果在组件中直接修改state,会报错。

    1. index.js,初始化Store的时候开启严格模式
    export default new Vuex.Store({
      strict: true,
      state: {
        ...
      },
      ...
    }
    
    1. App.vue中使用直接赋值的语句
    <template>
      <div id="app">
        <h1>Vuex - Demo</h1>
        <h2>strict</h2>
        <button @click="$store.state.msg = 'hello world~'">strict</button>
      </div>
    </template>
    
    1. 点击按钮内容改变,但是控制台会抛出错误
    image

    注意:不要在生产环境开启严格模式,因为严格模式会深度检测状态树,会影响性能。在开发模式中开启严格模式,在生产环境中关闭严格模式

    export default new Vuex.Store({
     strict: process.env.NODE_ENV !== 'production',
     state: {
      ...
    }
    

    Vuex插件

    • Vuex插件就是一个函数,接收一个store的参数
    • 在这个函数中可以注册函数让其在所有的mutations结束之后再执行

    插件的使用

    • 插件应该在创建Store之前去创建
    • subscribe函数
      • 作用是去订阅store中的mutation
      • 他的回调函数会在每个mutation之后调用
      • subscribe会接收两个参数,第一个是mutation,还可以区分模块的命名空间,第二个参数是state,里面是存储的状态
    1. 定义插件
    // 这个函数接收store参数
    const myPlugin = store => {
        // 当store初始化后调用
        store.subscribe((mutation, state) => {
            // 每次 mutation 之后调用
            // mutation 的格式为 { type, payload }
            // type里面的格式是 "模块名/state属性"
            // state 的格式为 { 模块一, 模块二 }
        })
    }
    
    1. Store中注册插件
    const store = new Vuex.Store({
        //...
        plugins: [myPlugin]
    })
    

    相关文章

      网友评论

          本文标题:Vuex(一) —— 集中式的状态管理仓库

          本文链接:https://www.haomeiwen.com/subject/vjoroktx.html