store模式&Vuex使用-简单案例入门

655 阅读5分钟

前言: 项目中其实用到Vuex的地方并不多,甚至根本用不上,最近遇到了一个用vuex超多的项目,啥都挂在vuex上,看着晕呀 所以这次特地学习记录一下,好记性不如烂笔头,何况我的记性都不咋好呀~

你可以学到:

1.store模式的应用
2.vuex中State/ Getter /Mutation /Action/ Module
3.mapState/mapGetters/mapActions/mapMutations

从两个简单的小案例出发,一起来吧,写了我几个小时,喜欢的收藏呀~

一.vuex是什么?与store模式有啥关系

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

在什么时候用vuex呢?

1.跨组件共享数据、跨页面共享数据
2.当一个组件需要多次派发事件时

在什么时候用store模式呢?

在需要管理共享状态,但是业务比较简单的时候,但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态

store模式用的是比较少的,如果真的比较简单,父子相传差不多了,并且为了扩展性,还是会选择Vuex

二.案例1 (常见vue页面)

需求:实现某组件点击按钮实现数据的增加

在app.vue里面引入Test1.vue组件

<template>
  <div id="app">
    <Test1/>
  </div>
</template>

<script>
import Test1 from './components/Test1.vue'
export default {
  name: 'App',
  components: {
    Test1,
  }
}
</script>

Test1.vue组件代码:

<template>
  <div class="hello">
    <div>{{count}}</div>
    <button @click="add()">增加</button>
  </div>
</template>
<script>

export default {
  name: 'Test1',
  props: {
    msg: String
  },
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    add() {
      this.count++
    }
  }
}
</script>

测试1.gif

三.两个组件需要修改同一个数据状态---store模式

需求:实现两个不同组件点击按钮当前同个数据的增加

新建components/Test2.vue以及store/store.js,并在app.vue中引入Test2.vue

Test1代码:

<template>
  <div class="hello">
    <div>{{state.count}}</div>
    <button @click="add()">Test1增加</button>
  </div>
</template>
<script>
import store from '../store/store'
export default {
  name: 'Test1',
  data() {
    return {
      state: store.state,
    }
  },
  methods: {
    add() {
      store.add()
    }
  }
}
</script>

Test2代码:

<template>
  <div class="hello">
    <button @click="add()">Test2增加</button>
  </div>
</template>
<script>
import store from '../store/store'
export default {
  name: 'Test2',
  methods: {
    add() {
      store.add()
    }
  }
}
</script>

store.js

const myStore = {
  debug: true,
  state: {
    count: 0,
  },
  add() {
    this.state.count++
  },
};

export default myStore

在这个时候,你想想,如果有很多个页面都要管理这一个count,是不是要每个页面都要引入,每个页面都要定义?那么全局注入变成了必要的。

个人觉得store模式的本质还是import进来了一个store实例, 在页面const store from xxxx 实际上等于 const store =,我们可以直接修改这个实例底下的值(或者说调用),并不是说因为vue新产生的一种模式.

下面来自于js高级程序第4版26.4.5,总的来说store 模式

1635935079(1).png

如果在Test1的data中,写入count: store.state.count,是不能实现做到计数增加的.解释如下:

1.获取到的是一个对象,能够通过引用改变下面的属性

   const obj = {
        state: {
          count: '0'
        },
     }
    const other = obj.state
    obj.state.count = '2'
    console.log(other.count); // 2

2.获取到的是一个基础类型的值,不能通过引用改变

   const obj = {
        state: {
          count: '0'
        },
     }

    const other = obj.state.count
    obj.state.count = '2'
    console.log(other); // 0

测试2.gif

四.两个组件需要修改同一个数据状态---Vuex实现(state/mutations)

1.安装vuex

 npm install vuex

2.建立store/vuexStore.js 文件,必须use

import Vue from 'vue' //必须
import Vuex from 'vuex' //必须
Vue.use(Vuex) //必须
const store = new Vuex.Store({ //必须
  state: {
    count: 0,
  },
  mutations: {
    add (state) {
      state.count++
    },
  },
})

export default store

3.在main.js中引入,并且挂在到vue上

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
import store from './store/vuexStore' // 引入

new Vue({
  render: h => h(App),
  store, // 挂载
}).$mount('#app')

4.组件里面的使用

Test1.vue

<template>
  <div class="hello">
    <div>{{count}}</div>
    <button @click="add()">Test1增加</button>
  </div>
</template>
<script>
export default {
  name: 'Test1',
  computed: {
    count () {
      return this.$store.state.count
    }
  },
  methods: {
    add() {
      this.$store.commit('add')
    }
  }
}
</script>

Test2.vue

<template>
  <div class="hello">
    <button @click="add()">Test2增加</button>
  </div>
</template>
<script>
export default {
  name: 'Test2',
  methods: {
    add() {
      this.$store.commit('add')
    }
  }
}
</script>

五.Vuex(mapState/mapMutations)

接标题四,假设不仅仅有共同的状态count,还有一个共同的开关

store/vuexStore.js代码

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0,
    open: false,
  },
  mutations: {
    add (state) {
      state.count++
    },
    toggle(state) {
      state.open = !state.open
    },
  },
})

export default store

Test1.vue代码

<template>
  <div class="hello">
    <div>{{count}}</div>
    <div>{{open ? '开' : '关'}}</div>
    <button @click="add()">Test1增加</button>
    <button @click="toggle()">Test1开关</button>
  </div>
</template>
<script>
export default {
  name: 'Test1',
  computed: {
    count () {
      return this.$store.state.count
    },
    open () {
      return this.$store.state.open
    },
  },
  methods: {
    add() {
      this.$store.commit('add')
    },
    toggle() {
      this.$store.commit('toggle')
    },
  }
}
</script>

Test2.vue代码

<template>
  <div class="hello">
    <button @click="add()">Test2增加</button>
    <button @click="toggle()">Test2开关</button>
  </div>
</template>
<script>
export default {
  name: 'Test2',
  methods: {
    add() {
      this.$store.commit('add')
    },
    toggle() {
      this.$store.commit('toggle')
    },
  }
}
</script>

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键,根据mapState定义,我们改写一下Test1.vue的代码:

<template>
  <div class="hello">
    <div>{{count}}</div>
    <div>{{open ? '开' : '关'}}</div>
    <button @click="add()">Test1增加</button>
    <button @click="toggle()">Test1开关</button>
  </div>
</template>
<script>
import { mapMutations, mapState } from 'vuex'
export default {
  name: 'Test1',
  computed: {
    ...mapState(['count', 'open'])
  },
  methods: {
    ...mapMutations(['add', 'toggle'])
  }
}
</script>

当当前页面的点击事件,与mutation里面的方法名不同名时,可以采用key:value的形式,例如:

 ...mapMutations(
      {
        add: 'add' // 左边的add就是当前页面的方法,右边的'add'表示vuexStore里面的mutations的add方法
      }
    )

测试4.gif

六.Vuex(Getter/mapGetters)

需求:当数据增加到3个以上,颜色变红, 5个以上颜色变蓝

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

Test1.vue代码

<template>
  <div class="hello">
    <div :class="[{'get-red' : $store.getters.getRed },
    {'get-blue' : $store.getters.getBlue }]">{{count}}</div>
    <button @click="add()">Test1增加</button>
  </div>
</template>
<script>
export default {
  name: 'Test1',
  computed: {
    count () {
      return this.$store.state.count
    }
  },
  methods: {
    add() {
      this.$store.commit('add')
    }
  }
}
</script>
<style scoped>
  .get-red {
    color: red;
  }
  .get-blue {
    color: blue;
  }
</style>

store/vuexStore.js

import Vue from 'vue' //必须
import Vuex from 'vuex' //必须
Vue.use(Vuex) //必须
const store = new Vuex.Store({ //必须
  state: {
    count: 0,
  },
  mutations: {
    add (state) {
      state.count++
    },
  },
  getters: {
    getRed: state => {
      return state.count > 3
    },
    getBlue: state => {
      return state.count > 5
    },
  }
})

export default store

(这里只是为了突出有2个getters才这样写的哦,主要还是体现getters的作用)

mapGetters改写Test1.vue

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

<div class="hello">
    <div :class="[{'get-red' : getRed },{'get-blue' : getBlue }]">{{count}}</div>
    <button @click="add()">Test1增加</button>
</div>

import { mapGetters } from 'vuex'
computed: {
    count () {
      return this.$store.state.count  // 这里也可以使用...mapState([ 'count']), mapState需要引入
    },
    ...mapGetters([
      'getRed',
      'getBlue',
    ])
},

测试3.gif

七.Vuex(Action/mapActions)

需求:实现两个不同组件点击按钮当前同个数据的异步后的增加

Action 类似于 mutation,不同在于:

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

以上的意思就是:如果有异步,就要用到action,不是直接去提交commit,而是dispatch action,异步有结果完成之后让action自己去提交mutation.这样就可以在vue插件里面去追踪state的状态

1.使用mutation做异步

import Vue from 'vue' //必须
import Vuex from 'vuex' //必须
Vue.use(Vuex) //必须
const store = new Vuex.Store({ //必须
  state: {
    count: 0,
  },
  mutations: {
    add (state) {
      setTimeout(()=> {
        state.count++
      }, 1000)
    },
  },
})

export default store

延迟mutation.gif

那么你可以看到调试的时候state里面count不同步,官方解释:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的

2.使用actions

Test1.vue

<script>
export default {
  name: 'Test1',
  computed: {
    count () {
      return this.$store.state.count
    }
  },
  methods: {
    add() {
      this.$store.dispatch('add')
    }
  }
}
</script>

vuexStore.js

import Vue from 'vue' //必须
import Vuex from 'vuex' //必须
Vue.use(Vuex) //必须
const store = new Vuex.Store({ //必须
  state: {
    count: 0,
  },
  mutations: {
    add (state) {
      state.count++
    },
  },
  actions: {
    add (content) {
      setTimeout(()=> {
        content.commit('add') // 拿到结果后提交mutation
      }, 1000)
    }
  }
})

export default store

actions里面的状态是可以追踪的. 延迟action.gif Test1.vue里面的dispatch也可以用mapActions表示:

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store

 methods: {
    add() {
      this.$store.dispatch('add')
    }
  }

=>

 import { mapActions } from 'vuex' // 必须引入
 methods: {
    ...mapActions({
      add: 'add'   //add这个key表示当前组件页面的方法,'add'这个value表示是actions里面的add方法
    })
  }

八.Module

需求: 有两个不同的功能count和open,count有对应的组件共同维护的状态,open也是

所以我们应该根据模块来划分不同的状态存储.

1636013238(1).png

1.公共 App.vue

<template>
  <div id="app">
    <Test1/>
    <Test2/>
    -------------上面是count模块--------------------
    -------------下面是open模块--------------------
    <Test3/>
    <Test4/>
  </div>
</template>

<script>
import Test1 from './components/Test1.vue'
import Test2 from './components/Test2.vue'
import Test3 from './components/Test3.vue'
import Test4 from './components/Test4.vue'

export default {
  name: 'App',
  components: {
    Test1,
    Test2,
    Test3,
    Test4,
  }
}
</script>

vuexStore.js模块

import Vue from 'vue' //必须
import Vuex from 'vuex' //必须
import count from './vuexCount'
import open from './vuexOpen'
Vue.use(Vuex) //必须
const store = new Vuex.Store({
  modules: {
    a:count,
    b:open,
  }
})

export default store
  1. count模块相关代码

Test1.vue

<template>
  <div class="hello">
    <div>{{count}}</div>
    <button @click="add()">Test1增加</button>
  </div>
</template>
<script>
export default {
  name: 'Test1',
  computed: {
    count () {
      return this.$store.state.a.count
    }
  },
  methods: {
    add() {
      this.$store.commit('a/add')
    }
  }
}
</script>

test2.vue

<template>
  <div class="hello">
    <button @click="add()">Test2增加</button>
  </div>
</template>
<script>
export default {
  name: 'Test2',
  methods: {
    add() {
      this.$store.commit('a/add')
    }
  }
}
</script>

vuexCount.js

const store = { //必须
  namespaced: true,
  state: {
    count: 0,
  },
  mutations: {
    add (state) {
      state.count++
    },
  },
}

export default store

3.open模块相关代码

Test3.vue

<template>
  <div class="hello">
    <div>{{open ? '开' : '关'}}</div>
    <button @click="toggle()">Test3开关</button>
  </div>
</template>
<script>
export default {
  name: 'Test3',
  computed: {
    open () {
      return this.$store.state.b.open
    },
  },
  methods: {
    toggle() {
      this.$store.commit('toggle')
    },
  }
}
</script>

Test4.vue

<template>
  <div class="hello">
    <button @click="toggle()">Test4开关</button>
  </div>
</template>
<script>
export default {
  name: 'Test4',
  methods: {
    toggle() {
      this.$store.commit('toggle')
    },
  }
}
</script>

vuexOpen.js

 const store = { //必须
  state: {
    open: false,
  },
  mutations: {
    toggle (state) {
      state.open = !state.open
    },
  }
}

export default store

vuex官网上有很重要的一句话: 默认情况下,模块内部的 action、mutation 和 getter 是注册在**全局命名空间**的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

什么意思呢? 看open模块相关代码,这里在组件中读取是通过this.store.state.b.open,带上了b的模块名,this.store.state.b.open,带上了b的模块名,而this.store.commit('toggle')没有带上模块名,所以就是说 action、mutation 和 getter 是注册在全局命名空间.

但是在count模块代码,我使用的是this.$store.commit('a/add'),是因为我在vuexCount.js中加上了一个 namespaced: true, 一般来说,如果一个模块单独,但是其他模块都需要调用到,就全局吧-_-

通过mapState命名用法,当然不止mapState有,其他的也可以哦~

   count () {
      return this.$store.state.a.count
    }

=>

...mapState('a', ['count'])

测试5.gif

九.参考与推荐

Vuex
手把手教你使用Vuex - Actions

十.个人公众号

觉得自己并没有代码天赋,只能说希望自己更加勤奋吧,最近在写一些关于在前端行业摸爬滚打还有生活中的趣事来让自己变得更加快乐,追求生活和工作中的进步.

公众号:程序媛爱唠嗑
欢迎关注以及留言,一起唠嗑!

c79fa8967887df6527a162bd85cae3a.jpg

如有错误,欢迎指正!!!生活愉快-_-