vuex的基本使用方法

146 阅读4分钟

Vuex的基础学习

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

1.1 Vuex是什么?

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

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

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

什么样的数据才适合存储到vuex中?一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;对于组件的私有数据,依旧存储在组件自身的data中即可

2.1 Vuex的基本使用

  1. 安装 vuex 依赖包 npm install vuex --save

  2. 导入 vuex 包

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
  1. 创建 Store 对象并导出出去
export default new Vuex.Store({
    state: {
    //...
  },
    mutations: {
    //...
  },
    actions: {
    //...
    },
    getters: {
    //...
    },
    modules: {
    //...
  }
})

4.在 main.js 中导入 store 并挂载到 app 上

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

Vue.config.productionTip = false

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

3.1 Vuex的核心概念

  • state:state 提供唯一的公共数据源,所有用于共享的数据均放在store的 state 中进行存储
  • mutation: mutation 用于变更 state 中的数据(mutation能且只能用于处理同步操作)
  • action:action 用于处理异步任务
  • getters:getters 用于对 state 中的数据进行加工处理,形成新的数据。
  • modules:项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

3.2 State

export default new Vuex.Store({
    state: {
        count: 10
    }
})

组件访问 state 的方式

  • 第一种方式 通过 this.$store.state.<全局状态名>
<template>
    <div>
        <h3>当前最新的count值为:{{this.$store.state.count}}</h3>
    </div>
</template>
  • 第二种方式 在vuex中按需导入 mapState 函数,通过 mapState 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性
<template>
  <div>
    <h3>当前最新的count值为:{{ count }}</h3>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  data() {
    return {
        //...
    }
  },
  computed: {
       ...mapState(['count'])          //  采用vuex中的原名 count 当作计算属性使用
       ...mapState({
                add:'count'            // 将vuex中的 count 映射成当前组件的的 add的计算属性,其实就是起一个别名 
        })
       ...mapState({
            count:state=>state.count   // 箭头函数的方法 映射
       })
  }
}
</script>

以上三种方法达到的效果是一样的,选择其一即可,后续提到的 mapMutations , mapActionsmapGetters 用法类似,就不再一一赘述了

3.3 Mutation

不直接对 state 中的数据进行操作,而采用 mutations 变更 state 中的数据的原因,因为state是实时更新的,mutations无法进行异步操作,而如果直接修改 state 的话是能够异步操作的,当你异步对 state 进行操作时,还没执行完,这时候如果 state 已经在其他地方被修改了,这样就会导致程序存在问题了。所以 state 要同步操作,通过 mutations 的方式限制了不允许异步

export default new Vuex.Store({
    state: {
      count: 100
  },
    mutations: {
        addN(state, step) {
            return state.count += step
        },
        delN(state, step) {
            return state.count -= step
        }
  }
})

组件访问 mutation 的方式

  • 第一种方式 通过 this.$store.commit('<方法名>',【参数】) 的方式导入
<template>
    <div>
        <h3>当前最新的count值为:{{this.$store.state.count}}</h3>
        <input type="text" v-model="value">
        <button @click = "handlerToAdd" >+N</button>
    </div>
</template>
//...
export default {
    data() {
        return{
            value: 0
        }
    },
    methods:{
        handlerToAdd(step){
            // commit 的作用就是调用某个mutation函数
            step = Number(this.value)
            this.$store.commit('addN', step)
        }
    }
}
  • 第二种方式 在vuex中按需导入 mapMutations 函数,通过 mapMutations 函数,将当前组件需要的全局数据,映射为当前组件的 methods 方法
<template>
  <div>
    <h3>当前最新的count值为:{{ count }}</h3>
    <input type="text" name="" id="" v-model="value" />
    <button @click="del">-N</button>
  </div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
  data() {
    return {
        value: 0
    }
  },
  methods: {
    ...mapMutations(['delN']),
    handlerToDel(step) {
        step = this.value
    //  this.$store.commit('delN', step)
        this.delN(step)
    }
  },
  computed: {
    ...mapState(['count']),
  }
}
</script>

3.4 Action

通过异步操作变更数据需要调用 action ,但在 action 中还需要通过调用 mutation 才能真正变更 state 中的数据

export default new Vuex.Store({
    state: {
      count: 100
  },
    mutations: {
        addN(state, step) {
            return state.count += step
        },
        delN(state, step) {
            return state.count -= step
        }
  },
    actions: {
        addNAsync({ commit }, step) {
            setTimeout(() => {
                commit('addN', step)
            }, 1000)
        },
        delNAsync({ commit }, step) {
            setTimeout(() => {
                commit('delN', step)
            }, 1000)
        }
  },
  modules: {
  }
})

组件访问 mutation 的方式

  • 第一种方式 通过this.$store.dispatch('<函数名>',【参数】)
<template>
    <div>
        <h3>当前最新的count值为:{{this.$store.state.count}}</h3>
        <input type="text" v-model="value">
        <button @click= "handlerToAddAsync">+N_Async</button>
    </div>
</template>
<script>
export default {
  data() {
    return {
      value: 0,
    };
  },
  methods: {
    handlerToAddAsync(step) {
      step = Number(this.value);
      this.$store.dispatch("addNAsync", step);
    }
  }
}
</script>
  • 第二种方式 在vuex中按需导入 mapActions 函数,通过 mapActions 函数,将当前组件需要的全局数据,映射为当前组件的 methods 方法
<template>
  <div>
    <h3>当前最新的count值为:{{ count }}</h3>
    <input type="text" v-model="value" />
    <button @click= "handlerToDelAsync">-N_Async</button>
  </div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
  data() {
    return {
      value: 0
    }
  },
  methods: {
    ...mapActions([ 'delNAsync']),
    handlerToDelAsync(step) {
      step = Number(this.value)
      this.delNAsync(step)
    }
  },
  computed: {
    ...mapState(["count"]),
  }
}
</script>

3.5 Getter

  1. getter 会对 state 中的数据进行加工处理,形成新的数据;
  2. state 中的数据发生变化, getter 的数据也会跟着变化

getter 不会修改 state 中的数据,只会对自己包装的数据进行变更

export default new Vuex.Store({
    state: {
        count: 0
    },
    // 只有mutations中定义的函数,才有权利修改 state 中的数据
    mutations: {
        add(state) {
            // 不要在mutations函数中执行异步操作
           /*  setTimeout(() => {
                state.count++
            }, 1000) */
            state.count++
        },
        addN(state, step) {
            state.count += step
        },
        delete(state, step) {
            if (step) {
                state.count-=step
            }
            state.count-=1
        }
    },
    actions: {
        addAsync(context) {
            // 在 actions 中,不能直接修改 state 中的数据;
            // 必须通过 context.commit() 触发某个mutation 才行
            setTimeout(() => {
                context.commit('add')
            }, 1000)
        },
        addNAsync(context, step) {
            setTimeout(() => {
                context.commit('addN', step)
            }, 1000)
        },
        subAsync(context, step) {
            setTimeout(() => {
                context.commit('delete', step)
            }, 1000)
        }
    },
    getters: {
        showNum(state) {
            return `当前最新的数量是:【${state.count}】`
        }
    },
    modules: {
    }
})
  • 第一种方式 通过this.$store.getters.<名称>
<template>
  <div>
    <h3>当前最新的count值为:{{ this.$store.state.count }}</h3>
    <input type="text" v-model="value" />
    <button @click="handlerToAdd">+N</button>
    <button @click="handlerToAddAsync">+N_Async</button>
    <h4>{{$store.getters.showNum}}</h4>
  </div>
</template>
  • 第二种方式 在vuex中按需导入 mapGetters 函数,通过 mapGetters 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性
<template>
  <div>
    <h3>当前最新的count值为:{{ count }}</h3>
    <input type="text" v-model="value" />
    <button @click="handlerToDel">-N</button>
    <button @click="handlerToDelAsync">-N_Async</button>
    <h4>{{ showNum }}</h4>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from "vuex";
export default {
  data() {
    return {
      value: 0,
    };
  },
  methods: {
    ...mapMutations(["delN"]),
    ...mapActions(["delNAsync"]),
    handlerToDel(step) {
      step = Number(this.value);
      this.delN(step);
    },
    handlerToDelAsync(step) {
      step = Number(this.value);
      this.delNAsync(step);
    },
  },
  computed: {
    ...mapState(["count"]),
    ...mapGetters(["showNum"])
  },
};
</script>

3.6 Module

module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

下面关于 Module 的内容是我复制的,我觉得写的还是容易理解的,基本上都看的懂,所以我自己就不重新造轮子了 (~~懒果然是程序员的共性啊)

首先是使用Modules时的一般结构:

const moduleA = {
 state: { ... },
 mutations: { ... },
 actions: { ... },
 getters: { ... }
 }
const moduleB = {
 state: { ... },
 mutations: { ... },
 actions: { ... }
 }
 
const store = new Vuex.Store({
 modules: {
  a: moduleA,
  b: moduleB
})

新建一个项目体验一下,通过vue –cli新建一个项目vuemodule, 不要忘记安装vuex.

  1. 在src 目录下新一个login文件夹,在里面新建index.js 用于存放login 模块的状态。 为了简单起见,我把模块下的state, actions,mutations, getters 全放在index.js文件中。先简单给它增加一个状态,userName: “sam”
const state = {
  useName: "sam"
};
const mutations = {
 
};
const actions = {
 
};
const getters = {
 
};
 
// 不要忘记把state, mutations等暴露出去。
export default {
  state,
  mutations,
  actions,
  getters
}
  1. 在src 目录下,再新建一个 store.js 这是根store, 它通过modules 属性引入 login模块。
import Vue from "vue";
import Vuex from "Vuex";
 
Vue.use(Vuex);
 
// 引入login 模块
import login from "./login"
 
export default new Vuex.Store({
  // 通过modules属性引入login 模块。
  modules: {
    login: login
  }
})
  1. 在main.js中引入store, 并注入到vue 根实例中。
import Vue from 'vue'
import App from './App.vue'
 
// 引入store
import store from "./store"
 
new Vue({
 el: '#app',
 store, // 注入到根实例中。
 render: h => h(App)
})
  1. 在 app.vue 中通过computed属性获取到login下的state. 这里要注意,在没有modules 的情况下,组件中通过 this.store.state.属性名可以获取到,但是有modules之后,state被限制到login的命名空间(模块)下,所以属性名前面必须加模块名(命名空间),组件中通过this.store.state.属性名 可以获取到,但是有modules 之后,state 被限制到login 的命名空间(模块)下,所以属性名前面必须加模块名(命名空间),组件中通过 this.store.state.模块名.属性名,在这里是 this.$store.state.login.userName
<template>
 <div id="app">
  <img src="./assets/logo.png">
  <h1>{{useName}}</h1>
 </div>
</template>
 
<script>
export default {
 // computed属性,从store 中获取状态state,不要忘记login命名空间。
 computed: {
  useName: function() {
   return this.$store.state.login.useName
  }
 }
}
</script>

当名字改变时和上述 Mutation 以及 Action 操作一致,就不赘述了

在模块中,state 是被限制到模块的命名空间下,需要命名空间才能访问。 但actions 和mutations, 其实还有 getters 却没有被限制,在默认情况下,它们是注册到全局命名空间下的,所谓的注册到全局命名空间下,其实就是我们访问它们的方式和原来没有module 的时候是一样的。

  • 其实actions, mutations, getters, 也可以限定在当前模块的命名空间中。只要给我们的模块加一个namespaced 属性:
const state = {
  useName: "sam"
};
const mutations = {
  CHANGE_NAME (state, anotherName) {
    state.useName = anotherName;
  }
};
const actions = {
  changeName ({commit, rootState},anotherName) {
    if(rootState.job =="web") {
      commit("CHANGE_NAME", anotherName)
    }
  },
  alertName({state}) {
    alert(state.useName)
  }
};
const getters = {
  localJobTitle (state,getters,rootState,rootGetters) {
    return rootGetters.jobTitle + " aka " + rootState.job
  }
};
// namespaced 属性,限定命名空间
export default {
  namespaced:true,
  state,
  mutations,
  actions,
  getters
}

当所有的 actions, mutations, getters 都被限定到模块的命名空间下,我们dispatch actions, commit mutations 都需要用到命名空间。如 dispacth("changeName"), 就要变成 dispatch("login/changeName"); getters.localJobTitle 就要变成 getters["login/localJobTitle"]

app.vue 如下:

<template>
 <div id="app">
  <img src="./assets/logo.png">
  <h1 @click ="alertName">{{useName}}</h1>
 
  <!-- 增加h2 展示 localJobTitle -->
  <h2>{{localJobTitle}}</h2>
  <!-- 添加按钮 -->
  <div>
   <button @click="changeName"> change to json</button>
  </div>
 </div>
</template>
 
<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
 // computed属性,从store 中获取状态state,不要忘记login命名空间。
 computed: {
  ...mapState("login",{
   useName: state => state.useName
  }),
 
   localJobTitle() {
    return this.$store.getters["login/localJobTitle"]
   }
 },
 methods: {
  changeName() {
   this.$store.dispatch("login/changeName", "Jason")
  },
  alertName() {
   this.$store.dispatch("login/alertName")
  }
 }
}
</script>

有了命名空间之后,mapState, mapGetters, mapActions 函数也都有了一个参数,用于限定命名空间,每二个参数对象或数组中的属性,都映射到了当前命名空间中。

<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
 computed: {
  // 对象中的state 和数组中的localJobTitle 都是和login中的参数一一对应。
  ...mapState("login",{
   useName: state => state.useName
  }),
  ...mapGetters("login", ["localJobTitle"])
 },
 methods: {
  changeName() {
   this.$store.dispatch("login/changeName", "Jason")
  },
  ...mapActions('login', ['alertName'])
 }
}
</script>