【study】Vue全家桶之Vuex

322 阅读2分钟

一.组件之间共享数据的方式

  • 1.父传子:v-bind属性绑定,props接收
  • 2.子传父:v-on事件绑定,on接收数据的那个组件,emit发送数据的那个组件
  • 3.兄弟组件之间共享数据:EventBus,中央事件总线

二.Vuex是什么

  • Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据的共享

三.使用Vuex统一管理状态的好处

  • 1.能够在vuex中集中管理共享的数据,易于开发和维护
  • 2.能够高效地实现组件之间的数据共享,提高开发效率
  • 3.存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步

四.什么样的数据适合存储在Vuex中

  • 组件共享的数据

五.Vuex的基本使用

  • 1.安装vuex依赖包
npm install vuex --save
  • 2.导入vuex包(在store文件夹下的index.js页面)
import Vuex from 'vuex'
Vue.use(Vuex)
  • 3.创建store对象(在store文件夹下的index.js页面)
const store = new Vuex.Store({
  state:{num:''} // state中存放的就是全局的共享数据
  mutations:{ }  // mutations里面通常存放的是直接改变state里数据的事件
  actions:{ }    // actions里面通常存放的是异步请求的api等等,记住actions不可以直接更改state里面的数据,想修改需要通过context.commit触发mutations来修改
  getters:{ }    // 可以对Store中已有的数据加工处理之后形成新的数据,类似Vue的计算属性
})
  • 将store对象挂载到Vue实例中(main.js中)
new Vue({
  router,
  store, // 将创建的共享数据对象,挂载到Vue实例上,所有的组件就可以从store中获取全局的数据
  render: h => h(App)
}).$mount('#app')

六.格式化配置文件(可以将分号去掉,双引号变为单引号)

// 在根目录中添加.prettierrc
{
 "semi": false,      // 去掉分号
 "singleQuote": true // 双引号变为单引号
}

七.Vuex的核心概念

  1. 提供唯一公共数据源,所有共享的数据都要统一放到Store的State中存储

(一)State:

// 在store/index.js中定义全局的state下的数据,count
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count:0
  }
})
export default store
  1. 组件访问State中数据的第一种\color{red}{第一种}方式
// this.$store.state.全局数据的名称
// 在组件中使用(不加this是因为这是在html上)
 <h3>加法:当前最新的值是:{{ $store.state.count }}</h3>
// 效果如下图

image.png
3. 组件访问State中数据的第二种\color{red}{第二种}方式:

// 从vuex中按需引入mapState函数
import { mapState } from 'vuex'
// 通过导入的mapState函数,将当前的组件需要的全局数据,映射为当前组件的computed计算属性:
computed: {
   ...mapState(['count'])
}
// 在组件中使用
<h3>减法:当前最新的值是:{{count}}</h3>
// 效果如下图

image.png

(二)Mutations:(commit专门用来调用某个Mutations函数)

  1. 思考之路:
// 加法的情况下,当点击加号的时候,我们想让count+1,于是我们有了以下的思路
<template>
  <div class="add">
    <h3>加法:当前最新的值是:{{$store.state.count}}</h3>
    <button @click="handleAdd">+</button>
  </div>
</template>
<script>
export default {
  data() {
    return {};
  },
  methods: {
    handleAdd () {
      this.$store.state.count++
    }
  }
};
</script>
<style lang="scss" scoped>
.add {
  display: flex;
  margin: 20px;
  button {
    margin-left: 20px;
    width: 25px;
  }
}
</style>
// 效果如下:
//(注意注意注意:不可以直接操作Store中的数据!!!所以这种写法是错误的!!!)

image.png
2. Mutations用于变更Store中的数据

  • (1)只能通过mutation变更Store数据state,不可以直接操作Store中的数据
  • (2)通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监测所有数据的变化
  1. 触发mutations的第一种\color{red}{第一种}方式(无参数传递)
// this.$store.commit('add') commit的作用,就是调用某个mutations函数

// 定义mutations
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
    add(state) {
      state.count++
    }
  }
})

<button @click="handleAdd"> +1 </button>
// 触发mutations
 methods: {
    handleAdd () {
      this.$store.commit('add')
    }
  }
  1. 触发mutations的第一种\color{red}{第一种}方式(有参数传递)
// this.$store.commit('addN')

// 定义mutations
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
    addN (state,step) {
      state.count += step // 变更状态,step可以由外界传递是几,就逐个加几
    }
  }
})

<button @click="handleAddN"> +N </button>
// 触发mutations
 methods: {
    handleAddN () {
      this.$store.commit('addN', 3) // 每次点击加3
    }
  }
  1. 触发mutations的第二种\color{red}{第二种}方式(无参数传递)
// 定义mutations
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
    sub (state) {
      state.count--
    }
  }
})

// 第一种触发mutations的方式
<button @click="handleSub">-1</button>
// 从Vuex中按需引入mapMutations函数
import { mapMutations } form 'vuex'
// 将mutations函数,映射为当前组件的methods方法
methods: {
   ...mapMutations(['sub']),
    handleSub() {
      this.sub() // 直接调用即可
    }
}

// 第二种触发mutations的方式
<button @click="sub">-1</button>  // 直接把sub函数写在这里
// 从Vuex中按需引入mapMutations函数
import { mapMutations } form 'vuex'
// 将mutations函数,映射为当前组件的methods方法
methods: {
   ...mapMutations(['sub'])
}
  1. 触发mutations的第二种\color{red}{第二种}方式(有参数传递)
// 定义mutations
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
   subN (state,step) {
      state.count -= step
    }
  }
})

// 第一种触发mutations的方式
<button @click="handleSubN">-N</button>
// 从Vuex中按需引入mapMutations函数
import { mapMutations } form 'vuex'
// 将mutations函数,映射为当前组件的methods方法
methods: {
    ...mapMutations(['subN']),
    handleSubN() {
      this.subN(3) // 调用时直接传入参数即可
    },
}

// 第二种触发mutations的方式
<button @click="subN(3)">-N</button>  // 直接把subN写在这里,并传参
// 从Vuex中按需引入mapMutations函数
import { mapMutations } form 'vuex'
// 将mutations函数,映射为当前组件的methods方法
methods: {
    ...mapMutations(['subN']),
}

(三)Actions(dispatch专门用来调用某个Actions函数)

  1. 思考之路:在mutations中模拟异步操作
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
    add (state) {
      setTimeout(()=>{
        state.count++
      })
    }
  }
})

// 页面上显示的值会直接发生改变,但在vue的调试器vuetools,值是不变的
// 不要在mutations函数中,执行异步操作
  1. Action用来处理异步任务
  • 如果通过异步操作变更数据,必须通过Actions,而不能使用Mutations,但是在Actions中还是要通过触发Mutations的方式间接变更数据
  1. 触发Actions的第一种\color{red}{第一种}方式(无参数传递)
// 定义actions
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    add(state) {
      state.count++
    }
  },
  actions: {
    addAsync(context) { // context相当于new出来的store的实例
      setTimeout(() => { // 在actions中不能直接修改state中的数据,
        context.commit('add') // 想修改必须通过context.commit()触发某个mutations
      }, 1000);
    }
})
  
 <button @click="handleAddasync"> +1 Async </button>
 // 触发actions
 handleAddasync () {
    this.$store.dispatch('addAsync')
 }  
  1. 触发Actions的第一种\color{red}{第一种}方式(有参数传递)
// 定义actions
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    addN(state, step) {
      state.count += step
    }
  },
  actions: {
    addAsyncN(context, step) {
      setTimeout(() => {
        context.commit('addN', step) // 将step传递给commit
      }, 1000);
    }
  }
})

 <button @click="handleAddasyncN"> +N Async </button>
 // 触发actions
 handleAddasyncN () {
    this.$store.dispatch('addAsyncN',5)
 }
  1. 触发Actions的第二种\color{red}{第二种}方式(无参数传递)
// 定义actions
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    sub(state) {
      state.count--
    }
  },
  actions: {
    subAsync(context) { 
      setTimeout(() => { 
        context.commit('sub') 
      }, 1000);
    },
  }
})

 // 第一种触发actions的方式
 <button @click="handleSubasync"> -1 Async </button>
 // 从Vuex中按需引入mapActions函数
 import { mapActions } from 'vuex'
 methods: {
    ...mapActions(['subAsync']),
    handleSubasync () {
      this.subAsync()
    }
 }
 
 // 第二种触发actions的方式
 <button @click="subAsync"> -1 Async </button>
 // 从Vuex中按需引入mapActions函数
 import { mapActions } from 'vuex'
 methods: {
    ...mapActions(['subAsync']),
 }
  1. 触发Actions的第二种\color{red}{第二种}方式(有参数传递)
// 定义actions
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    subN(state, step) {
      state.count -= step
    }
  },
  actions: {
    subAsyncN(context, step) {
      setTimeout(() => {
        context.commit('subN', step)
      }, 1000);
    }
  }
})

 // 第一种触发actions的方式
 <button @click="handleAddasyncN"> -N Async </button>
 // 从Vuex中按需引入mapActions函数
 import { mapActions } from 'vuex'
 methods: {
    ...mapActions(['subAsyncN']),
    handleAddasyncN () {
      this.subAsyncN(6) // 传入参数,每次减几
    }
  },
 
 // 第二种触发actions的方式
 <button @click="subAsyncN(6)"> -N Async </button> // 直接把subAsyncN写在这里,并传参
 // 从Vuex中按需引入mapActions函数
 import { mapActions } from 'vuex'
 methods: {
    ...mapActions(['subAsyncN'])
  },

(四)Getters:

  1. Getters的作用
  • Getters可以对Store中已有的数据加工处理之后形成新的数据,类似Vue的计算属性
  • Store中的数据的变化, Getters的数据也会跟着变化(类似于响应式)
// 定义getters
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    showNumber (state) {
      return '当前最新的数量 ['+ state.count + ']'
    }
  }
})

// 第一种触发getters的方式
<h3>{{$store.getters.showNumber}}</h3>

// 第二种触发getters的方式
<h3>{{ showNumber }}</h3>
// 引入mapGetters
import { mapGetters } from 'vuex'
computed: {
    ...mapGetters(['showNumber'])
},

(五)Mutations和Actions传入参数有两个或以上的时候

  1. mutations有两个或以上的参数的时候
// 定义mutations需要第二个参数传递一个payload,并解构得到里面的值
const store = new Vuex.Store({
  state: {
    count:0
  },
  mutations:{
    addN (state,payload) {
      const { index,text } = payload
      state.count += step // 变更状态,step可以由外界传递是几,就逐个加几
    }
  }
})

// 触发mutations需要第二个参数传入一个payload对象,例子如下
 methods: {
    handleAddN (index,text) {
      this.$store.commit('addN', {
        index,
        text
      }) 
    }
  }
  1. actions有两个或以上的参数的时候
// actions一般用于获取接口api,在actions中获取到数据接口,然后通过context.commit赋值给Mutations的方法中,并在State的数据中定义好空数组用来接收api数据即可
// 定义actions需要第二个参数传递一个payload,并解构得到里面的值
const store = new Vuex.Store({
  state: {
    datalist:[]
  },
  mutations: {
    setDate (state, datalist) {
      state.datalist = datalist
    }
  },
  actions: {
    getDate(context, payload) {
      const { key, testType} = payload
      axios(
        `/api&key=${key}&testType=${testType}`  //假数据
      ).then((res)=> {
        context.commit('setDate',res.data.result)
      })
    }
  }
})

// 触发actions需要第二个参数传入一个payload对象
this.$store.dispatch('getDate',{ //传值对象
    key:'123',
    testType:'456'
})

(六)Modules

  1. 可以让每一个模块拥有自己的state、mutations、actions、getters,使得结构非常清晰,方便管理。
  2. 目录结构

image.png

// countOne/state.js
export default {
  count: 0,
  name: '尤大大'
}

// countOne/mutations.js
export default {
  add(state) {
    state.count++
  }
}

// countOne/actions.js
export default {
  addAsync(context) {
    setTimeout(() => {
      context.commit('add')
    }, 1000);
  }
}

// countOne/getters.js
export default {
  showNumber(state) {
    return '当前最帅的男人' + state.name  + ''
  }
}

// countOne/index.js
import state  from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
export default {
  namespaced: true, // 开启命名空间
  state,
  mutations,
  actions,
  getters
}

// countTwo类似于countOne,按照自己需求写入即可

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import counterOne from './counterOne'
import counterTwo from './counterTwo'
const store = new Vuex.Store({
  modules:{
    counterOne,
    counterTwo
  }
})
export default store
// counterOne的页面

<template>
  <div class="add">
    <h3>当前最新的值是:{{ count }}</h3>
    <button @click="handleAdd">+</button>
    <button @click="handleAddAsync">+Async</button>
    <h3>Getters: {{showNumber}}</h3>
  </div>
</template>
<script>
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState('counterOne',{
      count: state => state.count
    }),
    ...mapGetters('counterOne',['showNumber'])
  },
  methods: {
    ...mapMutations('counterOne',['add']),
    handleAdd () {
      this.add()
    },
    ...mapActions('counterOne',['addAsync']),
    handleAddAsync () {
      this.addAsync()
    }
  }
};
</script>
<style lang="scss" scoped>
.add {
  display: flex;
  align-items: center;
  margin: 20px;
  button {
    height: 20px;
    margin: 20px;
  }
}
</style>
// counterTwo的页面

<template>
  <div class="add">
    <h3>当前最新的值是:{{ count }}</h3>
    <button @click="handleAdd">+</button>
    <button @click="handleAddAsync">+Async</button>
    <h3>Getters: {{showNumber}}</h3>
  </div>
</template>
<script>
import { mapGetters,mapActions,mapMutations, mapState } from 'vuex'
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState('counterTwo',{
      count: state => state.count
    }),
    ...mapGetters('counterTwo',['showNumber'])
  },
  methods: {
    ...mapMutations('counterTwo',['add']),
    handleAdd () {
      this.add()
    },
    ...mapActions('counterTwo',['addAsync']),
    handleAddAsync () {
      this.addAsync()
    }
  }
};
</script>
<style lang="scss" scoped>
.add {
  display: flex;
  align-items: center;
  margin: 20px;
  button {
    height: 20px;
    margin: 20px;
  }
}
</style>

image.png

八.Vuex的选项卡demo

  1. 效果:点击选项一,选项一变红加粗,并出现相对应的内容一

image.png
2. 目录结构:

image.png
image.png

image.png

// views/tab.vue

<template>
   <div>
    <Tab></Tab>
    <Content></Content>
   </div>
</template>
<script>
import Tab from '@/components/tab/tab.vue'
import Content from '@/components/tab/content.vue'
export default {
  components: { Tab,Content }
}
</script>
// components/tab/tab.vue

<template>
  <div class="tab">
    <p @click="selectIndex(0)" :class="[{change: selectIdx === 0}]" class="box">选项一</p>
    <p @click="selectIndex(1)" :class="[{change: selectIdx === 1}]" class="box">选项二</p>
    <p @click="selectIndex(2)" :class="[{change: selectIdx === 2}]" class="box">选项三</p>
    <p @click="selectIndex(3)" :class="[{change: selectIdx === 3}]" class="box">选项四</p>
  </div>
</template>
<script>
import {mapState,mapMutations} from 'vuex'
export default {
  computed: {
    ...mapState('changeTab',{
      selectIdx: state => state.selectIdx
    })
  },
  methods: {
   ...mapMutations('changeTab',['selectIndex'])
  }
}
</script>
<style lang="scss" scoped>
.tab {
  display: flex;
  .box {
    margin: 20px;
  }
  .change{
    margin: 20px;
    color: red;
    font-weight: 900;
  }
}
</style>
// components/tab/content.vue

<template>
  <div class="content">
    <p>{{content[selectIdx]}}</p>
  </div>
</template>
<script>
import { mapState }  from 'vuex'
export default {
  data() {
    return {
      content:['页面一','页面二','页面三','页面四']
    }
  },
  computed: {
    ...mapState('changeTab',{
      selectIdx: state => state.selectIdx
    })
  }
}
</script>
<style lang="scss" scoped>
.content {
  display: flex;
  p {
    margin: 20px;
  }
}
</style>
// store/changeTab/state.js

export default {
  selectIdx : 0
}

// store/changeTab/mutations.js

export default {
  selectIndex (state,selectIdx) {
    state.selectIdx = selectIdx
 }
}

// store/changeTab/index.js

import state  from './state'
import mutations from './mutations'
export default {
  namespaced: true,
  state,
  mutations
}