傻瓜式学习Vuex,学不会打我(附源码)

官方文档路径:https://vuex.vuejs.org/zh/

一、Vuex简单介绍

大家可直接打开官网,原文解释的很清晰。

1. 单向数据流

在这里插入图片描述

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

以上模型在多组件共享状态时,单向数据流的简洁性很容易被破坏

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。

对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

2. Vuex的实现原理

总结一句话:将组件的共享状态抽象出来,并使用单例模式管理
在这里插入图片描述

也许,你们看到这儿,可能还一头雾水。没关系,下面我会展开讲解:

  • 不使用Vuex的代码是怎么写的?可能会有什么问题?
  • 使用Vuex的代码是怎么写的?
  • Vuex的各组成部分是怎么用的?

二、不使用Vuex的代码怎么写?

准备工作:全局安装脚手架npm install @vue/cli -g

可直接访问github(注意是not-use-vuex分支)去starorfork代码查看,个人比较建议查看,因为从项目中的不足才能知道Vuex的伟大之处。

> 本分支主要介绍在不适用`Vuex`插件时,项目中会遇到哪些问题。

# 项目概览
![若图片打不开,图片在@/img/summary.png](https://github.com/Airpy/use-vuex/tree/not-use-vuex/src/img/summary.jpg)

# 实现了什么功能
1. 页面展示两个tab标签
2. 支持切换标签,并根据标签展示对应标签的内容(**这是本项目的重点**)

# 关键实现点
1. `@/Components/Tab/index.vue`:接收父组件`@/App.vue`传过来的`CurIdx`参数;点击时向父组件传递`changeParentTab`点击事件
2. `@/App.vue`:接收`changeParentTab`点击事件做`CurIdx`参数值的变更
3. `@/Components/Page/index.vue`:接收父组件`@/App.vue`变更过来的`CurIdx`参数,显示对应标签的内容

# 本项目中的特点:
1. `App`组件中的`curIdx`属性,被`Page`和`Tab`两个组件共享
2. `Tab`组件无法直接更改`curIdx`属性值,是通过向父组件传递时间通知父组件更改`curIdx`属性值,最后将`curIdx`属性值传递给`Page`组件
3. `Page`和`Tab`两个组件之间无法直接对`curIdx`属性值传递,是通过父`App`组件桥梁传递

# 本项目中的缺点:
1. 共性的数据(state)无法统一管理或管理麻烦
2. 兄弟组件无法直接更改数据

下文将开始逐步展开,介绍Vuex中的几大组成部分及怎么使用。

三、Vuex的使用

如需源码,请点击我。

  • main分支主要是将多个子分支进行整合
  • not-use-vuex分支是介绍不使用vuex时的代码怎么写,会遇到什么问题
  • use-state-mutations分支是介绍使用statemutations功能
  • use-getters分支是介绍使用getters功能
  • use-actions分支是介绍使用actions功能
  • 精简版项目见main分支
  • modules使用见main分支

1. 安装Vuex

如果使用@vue/cli脚手架创建的项目,且初始化时已选择Vuex组件,则无需再次安装,若没有,则:

# 在not-use-vuex根目录下
npm i vuex -S

此时在package.json中新增:

{
  "dependencies": {
    ......
    "vue-router": "^3.2.0",
    "vuex": "^3.5.1"
  },
}

2. 新建store

use-vuex根目录新建目录store,表示该目录下存储一些状态(state)的容器。

// @/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

// Vuex对于Vue来说相当于是一个插件
Vue.use(Vuex)

export default new Vuex.Store({
  // 存储一些需要集中管理的状态(state)
  state: {},
  // 存储一些提交(commit)状态的方法
  mutations: {}
})

3. main.js中引入store

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/store';

Vue.config.productionTip = false

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

4. statemutations的使用

1. 创建statemutations

我们知道,curIdx参数在APP.vueTab/index.vuePage/index.vue中都有使用,故curIdx需要被统一管理。

// @/store/index.js
......
export default new Vuex.Store({
  // 存储一些需要集中管理的状态(state)
  state: {
    curIdx: 1,
    content: ''
  },
	......
})

现在,我们需要一个可以修改状态的方法。整合成的代码如下:

// @/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

// Vuex对于Vue来说相当于是一个插件
Vue.use(Vuex)

export default new Vuex.Store({
  // 存储一些需要集中管理的状态(state)
  state: {
    curIdx: 1,
    content: 'vuex学习'
  },
  // 存储一些提交(commit)状态的方法
  mutations: {
    // state是固定入参,其携带state中的所有属性
    // index为形参,告诉mutations将state修改成什么值
    setCurIdx(state, index) {
      state.curIdx = index
    },
    // 当提交过来的数据是个对象时,则需要使用payload来装载
    setMoreStates(state, payload) {
      state.curIdx = payload.index
      state.content = payload.content
    }
  }
})

那么,我们怎么在视图中使用state?我们如何调用mutations来更改state

2. 使用statemutations

  1. 在需要使用statemutations的视图中(vue文件)引入
import { mapState, mapMutations } from 'vuex';
  1. 若想取出state中的某个状态值
// @/components/Tab/index.vue
export default {
  ......
  // 引入计算属性
  computed: {
    // 通过mapState取出指定state的值(curIdx为state的属性名称)
    ...mapState(['curIdx'])
  },
  ......
}

因为在main.js中注册了store选项,故该store实例会注入到所有的子组件中,子组件可通过this.$store.state获取到state值,如下:

// @/components/Tab/index.vue
export default {
  ......
  // 引入计算属性
  computed: {
    ......
    // 也可以通过`this.$store.state`取出state属性值,如下
    curIdx () {
      return this.$store.state.curIdx
    }
  },
  ......
}
  1. 若想取出mutations中的某个方法(进而修改state状态)
// @/components/Tab/index.vue
export default {
  ......
  methods: {
    // 通过mapMutations取出指定mutations的方法(setCurIdx为mutations的方法名称)
    ...mapMutations(['setCurIdx'])
  }
  ......
}

同样的,在子组件中,mutations也可以通过this.$store.commit('mutations的方法名', '可选的参数值'),如下:

// @/components/Tab/index.vue
export default {
  ......
  methods: {
    ......
    // 也可以通过`this.$store.commit('mutations的方法名', '可选的参数值')`提交state的更改,如下
    changeTab (index) {
      this.$store.commit('setCurIdx', index)
    },
    // 提交的数据为对象类型时的方法
    changeContent (index) {
      this.$store.commit('setMoreStates', {
        index: index,
        content: '傻瓜式学习vuex'
      })
    }
  }
  ......
}
  1. 使用statemutations
// @/components/Tab/index.vue
<template>
  <div>
    <a
      href="javascript:;"
      :class="[{current: curIdx === 1}]"  // 因为属性名仍为curIdx,故不需要修改
      @click="setCurIdx(1)"  // 将changeTab改为引入的setCurIdx,提交更改state
    >标签1</a>
    <a
      href="javascript:;"
      :class="[{current: curIdx === 2}]"
      @click="changeTab(2)"  // 使用changeTab
    >标签2</a>
    <a
      href="javascript:;"
      :class="[{current: curIdx === 3}]"
      @click="changeContent(3)"  // 修改page的内容
    >切换后修改page内容</a>
  </div>
</template>
  1. @/components/Page/index.vue@/App.vue以同样的方法修改,此处省略

3. 上述使用statemutations的完整代码

点击(use-state-mutations分支):github

5. getters的使用

1. 创建getters

  1. 使用getters能解决什么问题?

我们可以在视图中对store中的state,在computed计算属性中处理,如:

export default {
  name: 'Tab',
  // 引入计算属性
  computed: {
    ......
    tabContent () {
      return `标签[${this.$store.state.curIdx}]的内容: [${this.$store.state.content}]`
    }
  },
}

然后在template中以{{ tabContent }}方式使用。但如果多个子组件均需用到tabContent,若以以上写法则需要在不同的子组件中都定义该计算属性,造成代码冗余。

  1. 创建getters
// @/store/index.js
......
export default new Vuex.Store({
  // 可以理解为`store`的计算属性
  getters: {
    tabContent: (state) => {
      return `标签[${state.curIdx}]的内容: [${state.content}]`
    }
  }
	......
})

2. 使用getters

  1. 在需要使用getters的视图中(vue文件)引入
import { mapGetters } from 'vuex';
  1. 取出store中的getters
// @/components/Page/index.vue
export default {
  name: 'Page',
  computed: {
    ......
    // 通过mapGetters取出指定getters
    ...mapGetters(['tabContent'])
  }
  ......
}

因为在main.js中注册了store选项,故该store实例会注入到所有的子组件中,子组件可通过this.$store.getters获取到getters值,如下:

// @/components/Page/index.vue
export default {
  name: 'Page',
  computed: {
		......
    tabContents () {
      return this.$store.getters.tabContent
    }
  }
  ......
}
  1. 新的getters中使用已存在的getters
// @/store/index.js
export default new Vuex.Store({
  ......
  // 可以理解为`store`的计算属性
  getters: {
    ......
    // 在新的getters中可以使用已存在的getters
    useExistGetters: (state, getters) => {
      return getters.tabContent.length
    }
  }
})
  1. 如何给getters传参
// @/store/index.js
export default new Vuex.Store({
  ......
  // 可以理解为`store`的计算属性
  getters: {
    ......
    // getters返回函数类型,实现可传参的getters
    gettersByParam: (state) => (anotherContent) => {
      return `标签[${state.curIdx}]的内容: [${state.content}],额外的内容为: ${anotherContent}`
    }
  }
})
// 使用
computed: {
  ......
  anotherContent () {
    return this.$store.getters.gettersByParam('vuejs超简单')
  }
}

3. 上述使用getters的完整代码

点击点击(use-getters分支):github

6. actions的使用

actions的出现在于异步请求时使用,而mutations是在同步请求时使用。

在这里插入图片描述
从上图中可看到,actions的使用步骤为:

  1. 从视图中先派发dispatch指定actions
  2. actions调用后台APIBackend API
  3. actions拿到接口返回的数据,提交commit变更给mutations
  4. mutations进行state状态的修改**(这边的流程与上方的muatations用法相同)**

1. 创建actions

// @/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

// Vuex对于Vue来说相当于是一个插件
Vue.use(Vuex)

export default new Vuex.Store({
  // 存储一些需要集中管理的状态(state)
  state: {
    ......
    feedbackData: []
  },
  // 存储一些提交(commit)状态的方法
  mutations: {
    ......
    // 测试actions功能,将后台接口返回的数据填充到state中
    setFeedbackData(state, data) {
      state.feedbackData = data
    }
  },
  // 可以理解为`store`的计算属性
  getters: {
    ......
  },
  actions: {
    // 定义actions,在actions中提交mutations
    // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
    // 可调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
    getFeedbackData(context, payload) {
      const { page, limit } = payload
      axios(
        `http://xxx/api/quality/feedback/?page=${page}&limit=${limit}`
      ).then((res) => {
        // 提交mutations
        context.commit('setFeedbackData', res.data)
      })
    }
  }
})

2. 使用actions

注意,这边用到了axios,需要npm i axios -S安装

// @/components/Feedback/index.vue
<template>
  <div>
    {{ feedbackData }}
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  name: 'Feedback',
  // 当子组件被挂载时,去派发actions去后台请求数据
  mounted () {
    // 派发使用dispatch方法,第一个参数为actions的名称,第二个为可选参数
    this.$store.dispatch('getFeedbackData', {
      page: 1,
      limit: 10
    }),
    // 调用method完成绑定
    // 个人建议:在有参数的派发actions时,使用以上方法更为简便一点。
    this.getFeedbackData({
      page: 1,
      limit: 10
    })
  },
  // 获取state中的值
  computed: {
    ...mapState(['feedbackData'])
  },
  methods: {
    // 也可以使用mapActions派发
    ...mapActions(['getFeedbackData'])
  }
}
</script>

7. 精简项目

目前,我们将statemutationsgettersactions都放在一个文件中,这样不利于维护,且显得结构较乱。我们可以将属于statemutationsgettersactions拆分成多个文件,然后在引入就行了。

下面以state为例,我们在store文件夹下新增state.js

export default {
  curIdx: 1,
  content: 'vuex学习',
  feedbackData: []
}

然后在@/store/index.js中引入

// @/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

import state from './state';

// Vuex对于Vue来说相当于是一个插件
Vue.use(Vuex)

export default new Vuex.Store({
  // 存储一些需要集中管理的状态(state)
  state,
	......
})

8. Modules的使用

当遇到比较大型的项目,需要按模块module来区分store时较为清晰,每个模块有自己的state/mutations/actions/getters。

以下为官网例子:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

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

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

当多个模块中使用到相同名称的state、mutations等,则需要使用命名空间

// @store/module1/state.js
export default {
  curIdx: 1
}

// @store/module1/mutations.js
export default {
  setCurIdx(state, index) {
    state.curIdx = index
  },
}

// @store/module1/index.js
import Vue from 'vue';
import Vuex from 'vuex';

import state from './state';
import mutations from './mutations';

Vue.use(Vuex)

export default {
  namespaced: true,  // 这边指定为true,表示使用命名空间
  state,
  mutations,
})

在视图中使用

import { mapState, mapMutations } from 'vuex'

......
computed: {
  ...mapState('module1', {  // 指定命名空间的名称,一般为文件夹的名称
    curIdx: (state) => state.curIdx
  })
},
methods: {
  ...mapMutations('module1', ['setCurIdx'])
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值