Javascript

与vuex来一次邂逅

本文主要是介绍与vuex来一次邂逅,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

先埋一个伏笔

在这里插入图片描述

看不懂不要紧。这张图片只是一个伏笔

前奏

学习一个新的知识,应该带有一些目的性,或者了解一下相关背景,带着疑问去学一个东西,至少不会那么痛苦。(不要跟我说学习是快乐的,学习哪来的快乐( ̄ー ̄) ( ̄ー ̄))

我们先看一个场景,一个很常见且简单的需求。

有2个兄弟组件A,B,现在A,B组件有一个count变量需要共享,也就是count改变了,两边组件的count都要改变,这时候除去我们知道的vuex,我们可以怎么做呢

1.使用中央总线$bus

2.把count放到A,B组件的父级组件上,然后父子组件传参

3.借用本地存储

思路肯定是没有问题的,问题也能够解决,但是,如果现在的场景是A,B,C三个组件(甚至是三个以上组件)都需要共享同一个变量呢?还用以上2种方法就显得非常麻烦和累赘了。带着这个问题,我们看看应该怎么处理。这时候就有同志问了,那为什么不借用本地存储呢?别急,继续往后看

主题

接下来介绍我们的主角:vuex

vuex官方称为vue的状态管理工具,通俗一点解释,就是一个大仓库,项目中的所有组件都可以向vuex这个大仓库拿东西,删东西,改东西。

但是需要遵循一定的游戏规则。并且仓库的东西被动了,会通知到所有的组件:’啊,我的东西被别的组件给修改了‘。所以vuex的数据是响应式的。这就是本地存储跟vuex很大的一个区别

先上用vue-cli4搭出来的store目录下index.js文件的脚手架代码,先说一下Vue的原型上是有$store属性的,这个可以认为是vuex仓库

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

我们看到vuex一共有5个模块,我们一一来进行分析

State

state就是我上面所说的大仓库,所有的组件都可以拿到state里面的数据,再次强调一下,state里面的数据是响应式的。

我们在state里面搞一个变量count

export default new Vuex.Store({
  state: {
      count: 1
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

讲了这么多,我们开始上A组件的代码

<template>
  <div class="a-container">  
      <h3>我是A组件</h3>
    {{ $store.state.count }}
  </div>
</template>

<script>
</script>

<style scoped lang="less"></style>

在上B组件的代码

<template>
  <div class="b-container">
    <h3>我是B组件</h3>
    {{ $store.state.count }}
  </div>
</template>

<script>
export default {
  name: 'B',
  data () {
    return {}
  },
}
</script>

<style scoped lang="less"></style>

在上视图

在这里插入图片描述

很明显,state的值我们都拿到了。但是这时候问题又来了,如果一个组件或多个组件都需要使用这个count,那么我们在模板页面就要反复使用$store.state.count,觉不觉得点的有点麻烦?这时候我们又来了一个新东西,mapState辅助函数,这个辅助函数就是用来帮我们简化的我们操作的。

1.mapState辅助函数

mapStatevuex的辅助函数,主要用来帮助我们在开发中简化我们的操作,更方便的取到state中的值

1.1mapState辅助函数的数组映射

话不多说,直接上代码

talk is cheap,show you my code

<template>
  <div class="b-container">
    <h3>我是B组件</h3>
    {{ count }}
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'B',
  computed: {
    ...mapState(['count'])
  },

}
</script>

<style scoped lang="less"></style>

这个地方我暂时还没找到比较好的解释方法,因为我觉得看代码足以能理解mapState函数怎么使用了。等我想到通俗易懂的语言来解释此处时,我会在更新文档。

1.2 mapState辅助函数的对象式映射

这一部分你需要先了解modules对象的相关知识,如果你还不了解modules模块,你等会在来看就比较好了,比如说modules模块有A,B个模块,也就是下面这样

  state: {
    count: 1
  },
  mutations: {},
  actions: {},
  modules: {
    A,
    B
  }

那这时候你再用数组的方式把vuex的数据映射到当前组件,就肯定不行了,因为当前组件搞不清楚你这个count是哪个模块里面的了。

这时候就要用到对象式映射了

上代码

<template>
  <div class="a-container">
    <h3>我是A组件</h3>
    {{ count }}
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'A',
  data () {
    return {}
  },
  props: {},
  components: {},
  computed: {
    ...mapState({
        // 拿到state模块中的A分支,在取A分支中的count
      count: (state) => state.A.count
    })
  },
}
</script>

<style scoped lang="less"></style>

这一部分我觉得看我代码中的注释完全是能够理解的。

getters

getters属性可以理解为组件里面的计算属性,举个例子,你去水果店买苹果,最后你的总价,肯定是单个苹果的价格乘以苹果的总数量。

这时候就可以给state对象里面来2个属性了

export default new Vuex.Store({
  state: {
      // 苹果的数量
    count: 1,
      // 苹果的单价
    applePrice: 5
  },
  mutations: {},
  getters: {

  },
  actions: {},
  modules: {
    A,
    B
  }
})

那么苹果的总价appleSum就是count * applePrice了,这时候苹果的总价格是随着countapplePrice这2个变量而改变的,所以这个变量我们就可以放在getters里面了

  state: {
    count: 1,
    applePrice: 5
  },
  mutations: {},
  getters: {
      // 苹果的总价格,这里箭头函数能接受一个参数,就是state,第二个参数是getters,也就是说你可以用getters里面的其他变量
    appleSum: (state) => {
      return state.count * applePrice
    }
  },
  actions: {},
  modules: {
    A,
    B
  }

这里引用Vuex官方的一句话getters 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

在后面的使用中,在组件中使用vuex里面的值或者函数,统一使用函数映射的方式,这是目前项目中的主流方式。

组件使用getters中的变量,注意看注释

<template>
  <div class="a-container">
    <h3>我是A组件</h3>
    <h4>我是state里面的值:</h4>
    {{ count }}
    <h4>我是getters里面的值</h4>
    {{ appleSum }}
  </div>
</template>

<script>
import { mapGetters, mapState } from 'vuex'
export default {
  name: 'A',
  data () {
    return {}
  },
  props: {},
  components: {},
  computed: {
    ...mapState({
      count: (state) => state.A.count
    }),
      // 这种写法你可理解为,把getters中的appleSum映射到当前组件中,并且在当前组件中命名为appleSum
    ...mapGetters(['appleSum'])
      // 如果你想给予其它命名,你可以使用如下写法
      ...mapGetters({
      // key是你自己另外取的名字,value是getters里面的名字
      apple_sum: 'appleSum'
  })
  },
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less"></style>

上视图

在这里插入图片描述

getters传参

先看场景: 现在你在网上买了苹果付款总价应该是苹果的个数乘以苹果价格。但是现在水果店还有橘子卖。这时候就得根据水果的id查出水果的价格,在算。这里我们假设用户只能购买一种水果(别杠,理解为主o(╯□╰)o)

state里面的变量

 state: {
    appleCount: 1,
    orangeCount: 2,
        // 水果信息
    fruitInfo: [
      { id: 1, price: 10 },
      { id: 2, price: 20 }
    ]
  }

getters里面的变量

  getters: {
    fruitPrice: (state) => {
      return (id) => {
        const fruit = state.fruitInfo.find((val) => val.id === id).price
        return fruit.price * fruit.count
      }
    }
  },

A组件调用:

<template>
  <div class="a-container">
    <h3>我是A组件</h3>
    <h4>我是state里面的值:</h4>
    {{ count }}
    <h4>我是getters里面的值</h4>
    {{ fruitPrice(2) }}
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'A',
  data () {
    return {}
  },
  props: {},
  components: {},
  computed: {
    ...mapState({
      count: (state) => state.A.count
    }),
    fruitPrice () {
        // 计算属性传参
      return (id) => {
        return this.$store.getters.fruitPrice(id)
      }
    }
  },
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less"></style>

上视图

在这里插入图片描述

Mutations

还是先说场景,现在用户在某app购买界面买苹果,点击增加的图标,所购苹果的数量肯定要加1,假设这个苹果的数量我们是存在vuex里面的,那么现在就涉及到vuex里面数据的改变了。

你所有对vuex里面的变量进行的修改操作,都必须通过mutations这一个对象,Mutations你可以理解为仓库的管理员,对仓库里面的材料进行改动,你就要告知他。

我们现在mutations对象里面搞一个对苹果数量增加的操作:

  mutations: {
      // 操作函数接收2个参数,一个是state容器,一个是你调这个函数所传的参数,传参有2中方式一种是对象式的传参(官方推荐,变量名称清晰明朗)
    increnment (state, payload) {
      state.appleCount = payload.num + state.appleCount
    }
      // 另一种是单个变量传参
          increnment (state, num) {
      state.appleCount = state.appleCount + num
    }
  }

然后在A组件我们调用一下(还是直接用辅助函数的形式去映射,注意看注释)这里我们跳过this.$store.commit的方式讲解,因为项目开发这种方式基本不用

<template>
  <div class="a-container">
    <h3>我是A组件</h3>
    <h4>我是state里面的值:</h4>
    {{ count }}
    <br />
    <button @click="onIncreatment">增加</button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'A',
  data () {
    return {}
  },
  props: {},
  components: {},
  computed: {
    ...mapState({
      count: (state) => state.appleCount
    })
  },
  watch: {},
  created () {},
  mounted () {},
  methods: {
    // 注意这个辅助函数要放在methods里面
    ...mapMutations(['increment']),
      // 如果你想修改映射过来的函数名称,你可以用对象式写法
      ...mapMutations({
          incre: 'increment'
      }),
    onIncreatment () {
      this.increment({
        num: 2
      })
    }
  }
}
</script>

<style scoped lang="less"></style>

视图更新前

在这里插入图片描述

点击增加按钮后

在这里插入图片描述

视图更新,并且调试工具记录下来了这么一个改变的过程。

关于使用mutations的小细节

  • 所有的异步操作,不要放在mutations里面,也就是说mutations里面只能放同步操作。

    这里要解释一下,你在mutations里面放异步操作不是说代码会无效,而是调试工具它会监测不到数据的改变,假设别人接收你的项目,然后用调试工具调试,那么麻烦就大了。

  • 可以使用常量来代理事件类型,也就是说,你的所有mutations的命名可以封装起来,这里我项目中暂时还没用到,等实际用到我会在此补充先贴一个官方解释

Actions

如果你有异步操作,比如请求后端接口,那么你可以选择放在actions模块里面。

场景: 用户登录,登录成功,保存用户数据,登录失败,给予用户提示

actions模块里面的函数也能接受2个参数,第一个是上下文context,官方说跟store是有区别的,关于context和store的区别,我理解之后再更新文档,这里附上context对象

在这里插入图片描述

actions代码:

 actions: {
     /**
     login为已经封装好的登录方法,这里假设用户登录失败后端直接返回错误的状态码
      **/
    onLogin (context, data) {
      return new Promise((resolve, reject) => {
        login(data)
          .then(res => {
            context.commit('setUser', res)
            resolve()
          })
          .catch(err => {
            reject(err)
          })
      })
    },
        // 如果你经常需要用到commit,你可以直接在接受的时候解构
            onLogin ({ commit }, data) {
      return new Promise((resolve, reject) => {
        login(data)
          .then(res => {
            // 登录成功把数据存到vuex
            context.commit('setUser', res)
            resolve()
          })
          // 登录失败把错误丢出去
          .catch(err => {
            reject(err)
          })
      })
    }
  },

A组件代码,我们还是用辅助函数把actions里面的函数映射到A组件

<template>
  <div class="login-container">
	<div>
       <button>
           登录
    </button>
    </div>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'Login',
  data () {
    return {

      //  后端返回的用户信息
      userInfo: null,

    }
  },
  methods: {
    ...mapMutations(['setUser']),
    ...mapActions(['onLogin']),
    },
    // 表单校验成功登录操作
    async login () {
      try {
        await this.onLogin(this.loginForm)
        this.$message.success('登录成功')
          // 登录成功跳转首页
        this.$router.push('/layout/home')
        this.isLogin = false
      } catch (error) {
        const { response } = error
        if (response && response.status === 400) {
          this.$message.error('手机号或验证码不对')
        } else if (response && response.status === 403) {
          this.$message.error('权限不足无法登录')
        } else if (response && response.status === 507) {
          this.$message.error('数据异常')
        } else {
          // 处理其他异常
        }
      }
    },
}
</script>

<style lang="less" scoped>

</style>

组合Action

场景: 有时候会有这种需求,先根据用户名获取用户id(假设这个用户id是好几个组件共享的),在根据用户id去查找用户相关信息,这时候异步操作就是有顺序的了

上代码:

    async onGetUserId ({ commit }, userName) {
      const id = await getUserId(userName)
      commit('setUserId', {
        id
      })
    },
    async onGetUserInfo ({ commit, dispatch, state }, userName) {
        // 先等onGetUserId方法完成
      await dispatch('onGetUserId', userName)
      const userInfo = await getUserInfo(state.userId)
      commit('setUserInfo', userInfo)
    }

关于伏笔的解释

写到这里,文章最开始的伏笔就起到作用了

这里再次把文章最开始的图片贴出来

在这里插入图片描述

我们从Vue Components开始,假设我们要修改vuex里面的值,如果没有异步操作,那么就通过commit的方式提交给mutations,devtoolsvuex调试工具这时候会监测到该次修改,然后state里面的值发现改变,相关的组件变量重新渲染,视图得以更新。

如果有异步操作,我们先进行异步操作,根据后端接口返回的不同状态,决定commit到哪个方法

Modules

还是先说场景,比如我们现在多人协作开发一个商城项目,商城有首页,登录页,商品详情页,搜索页,如果把所有页面需要共享的数据全放在vuexstate里面,可以当然是可以,但是容器就变得比较臃肿了,这时候我们就可以进行模块化的区分了,也就是可以把首页的相关组件主要共享的变量拆出来,搜索页需要共享的组件拆出来。这样整个容易的结构就很清晰了,也利于维护。

上目录结构:

store

​ modules

​ --home.js

​ --search.js

homesearch页的代码

home.js

export default {
  state: {
    count: 1
  },
  mutations: {},
  actions: {}
}

search.js

export default {
  state: {
    count: 2
  },
  mutations: {},
  actions: {}
}

模块要在index.js文件也就是要在vuex modules中注册

import Vue from 'vue'
import Vuex from 'vuex'
import A from './a'
import B from './b'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
      count: 3
  },
  mutations: {
  },
  getters: {},
  actions: {
  },
  modules: {
    A,
    B
  }
})

命名空间

在怎么取相关模块的值之前,先说一个命名空间

先引用官方原话

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

也就是说,你在home.jssearch.js里面写的actionmutationgetter,它自动被放到了index.js文件下的actionmutationgetter里面,解决这一问题,我们可以按照如下方式做,话不多说上代码

home.js

export default {
    // 加一个 namespaced: true属性
  namespaced: true,
  state: {
    count: 1
  },
  getters: {
    gettersCount (state) {
      return ++state.count
    }
  },
  mutations: {},
  actions: {}
}

A组件代码(注意看注释)

先上A组件代码

<template>
  <div class="a-container">
    <h3>我是A组件</h3>
    <h4>我是A模块里面的gettersCount值:</h4>
    {{ gettersCount }}
    <br />
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  name: 'A',
  data () {
    return {}
  },
  props: {},
  components: {},
  computed: {
    // ...mapState({
    //   count: (state) => state.count
    // })
      // 第一个参数是模块名,第二个是你要映射的模块getters变量,你也可以用对象时映射,如果你觉得变量名称需要改变
    ...mapGetters('A', ['gettersCount'])
  },
  watch: {},
  created () {
    console.log(this.$store)
  },
  mounted () {}
}
</script>

<style scoped lang="less"></style>

上视图

在这里插入图片描述

这样就拿到A模块里面的getters了。action和mutation同理,在methods里面映射一下就行了

  methods: {
    ...mapMutations('A', ['mutationsTest']),
    ...mapActions('A', ['actionTest'])
  }

提示

别忘记加上namespaced: true这一条属性哦~

待补充。。。。。。。

这篇关于与vuex来一次邂逅的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!