1.Vuex简单了解
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
如果有一些公共的数据需要在多个组件中共享或者某一个状态的改变会影响多个组件,那么这时候用vuex是非常合适的,比如我们经常会看到的中后台都有多风格的切换,这种全局的改变就可以用到vuex去完成!
使用Vuex管理数据的好处:
- 能够在 vuex 中集中管理共享的数据,便于开发和后期进行维护
- 能够高效的实现组件之间的数据共享,提高开发效率
- 存储在 vuex 中的数据是响应式的,当数据发生改变时,页面中的数据也会同步更新
2.简单组件传值
2.1传统组件传值的缺点
vuex 的主要作用就是解决数据共享的问题,也就是组件之间传值的问题。
前面讲过的主要是通过输入和输出的方式传值:
props:输入
$emit:输出
输入输出机制有个缺点:页面复杂时,经常需要层层传递数据,因为非父子组件间的交互,只能寻找最近的祖先组件来做中转。
同时,输入输出机制还有一个局限性:当不同页面的组件需要进行数据交互时,它就无能为力了。
平常开发,这种输入输出的方案也基本满足了。
但如果有需要跨页面的数据交互或者说,有需要将数据做持久化处理的场景时;以及如果页面组件层次复杂,存在props 和 $emit 层层传递时,这种方案其实是很难忍受的。
下面是一个计数器案例
2.2计数器案例
下面代码演示一个计数器案例,此案例演示的是兄弟组件传值的问题
2.2.1 初始化项目
直接使用脚手架创建项目即可,安装的时候可以选择使用 vuex ,也可以后面再安装。
先创建一个不带有 vuex 的项目 vuex-demo1:
打开刚刚创建的 vue项目,找到src目录中的App.vue组件,将代码重新编写如下:
<template>
<div>
<my-addition></my-addition>
<my-subtraction></my-subtraction>
</div>
</template>
<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'
export default {
data() {
return {}
},
components: {
'my-subtraction': Subtraction,
'my-addition': Addition
}
}
</script>
<style>
</style>
在components文件夹中创建Addition.vue组件,代码如下:
<template>
<div>
<h3>当前最新的count值为:</h3>
<button>+1</button>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
<style>
</style>
在components文件夹中创建Subtraction.vue组件,代码如下:
<template>
<div>
<h3>当前最新的count值为:</h3>
<button>-1</button>
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
<style>
</style>
2.2.2实现
这是两个单独的组件,也就是两个单独的 vue 实例
vue 实例中的数据是私有的,但是我们的应用确实要操作同一个变量 count,所以变量 count 不能声明
在单独组件中
如何实现呢?
提供一种解决方案
- count 变量定义在父组件 App.vue 中
- 两个子组件中都有一个 prop,名为 count
- 父组件通过 :count 为子组件中的 prop count 传值
- 子组件单击按钮时,通过 $emit,向外广播,告诉父组件应该为 count 变量+1或者 -1
app.vue中代码
<template>
<div id="app">
<my-add :count="count" @handle-add="handleAdd"></my-add>
<my-sub :count="count" @handle-sub="handleSub"></my-sub>
</div>
</template>
<script>
import Addtion from './components/Addition'
import Subtraction from './components/Subtraction'
export default {
data () {
return {
count: 0
}
},
components: {
'my-add': Addtion,
'my-sub': Subtraction
},
methods: {
handleAdd () {
this.count++
},
handleSub () {
this.count--
}
}
}
</script>
Addition.vue
<template>
<div class="main">
<h3>变量 count 的值为:{{ count }}</h3>
<button @click="add">+1</button>
</div>
</template>
<script>
export default {
props: ['count'],
methods: {
add () {
this.$emit('handle-add')
}
}
}
</script>
Subtraction.vue
<template>
<div class="main">
<h3>变量 count 的值为:{{ count }}</h3>
<button @click="sub">-1</button>
</div>
</template>
<script>
export default {
props: ['count'],
methods: {
sub () {
this.$emit('handle-sub')
}
}
}
</script>
3.使用vuex
3.1安装和配置
安装
当前的项目在创建时,并没有安装 vuex ,所以需要单独安装
npm install vuex
新建store文件
src 目录下创建 store 目录,其中新建 index.js 文件
index.js 文件内容很简单,仅需要提供一个初始 state 对象和一些 mutation
代码如下
import Vue from 'vue'
import Vuex from 'vuex'
// 这里不要忘了引用插件
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
},
mutations: {
}
})
export default store
引入
为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:
修改 main.js
3.2改写计算器案例
改写步骤
- store 中的 state 中定义共享数据 count
- store 中的 mutaions 中定义相关 mutation
- 删除 Addtion.vue 和 Subtraction.vue 中的 props 选项(当然后面可以加上)
- 组件模板中使用
$store.state.count
获取 store 中 state 中的变量 - 组件中通过
this.$store.commit(mutation_name)
触发 store 中的
mutation(其实就是一个方法)
修改store
修改 store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 这里不要忘了引用插件
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
reduction (state) {
state.count--
}
}
})
export default store
修改组件
Addition.vue
<template>
<div class="main">
<h3>变量 count 的值为:{{ $store.state.count }}</h3>
<button @click="add">+1</button>
</div>
</template>
<script>
export default {
methods: {
add () {
this.$store.commit('increment')
}
}
}
</script>
Subtraction.vue
<template>
<div class="main">
<h3>变量 count 的值为:{{ $store.state.count }}</h3>
<button @click="sub">-1</button>
</div>
</template>
<script>
export default {
methods: {
sub () {
this.$store.commit('reduction')
}
}
}
</script>
总结:似乎代码量并没有减少多少,但是数据的共享逻辑清楚很多:
- 凡是多个组件都需要的数据,就定义在 store 中
- 凡是多个组件都需要的数据,就从 store 中获取
4.vue详解
4.1State
4.1.1 定义satet
State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储
vuex 中的 state 相当于组件中的 data
State中的数据与组件 data 中的数据一样,也是响应式的
import Vue from 'vue'
import Vuex from 'vuex'
// 这里不要忘了引用插件
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
name: '美的冰箱',
price: 120
}
})
export default store
4.1.2 组件中使用state
在组件中访问 state 中变量,模板中直接使用 $store.state.属性名称获取
<template>
<div class="container">
{{ $store.state.name }}
</div>
</template>
4.1.3 计算属性
上面写起来有点长
使用计算属性可以解决这个问题
<template>
<div class="container">{{ name1 }}--{{ price1 }}</div>
</template>
<script>
export default {
computed: {
name1 () {
return this.$store.state.name
},
price1 () {
return this.$store.state.price
}
}
}
</script>
辅助函数
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个
问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
<template>
<div class="container">{{ name1 }}--{{ price1 }}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: mapState({
name1: state => state.name,
price1: state => state.price
})
}
</script>
- 我们都知道 computed 的值应该是一个对象,所以 mapState 函数的返回值一定是个对象
- name1 和 price1都是计算属性的名称
- 使用箭头函数时,可以省略 this.$store,而是直接使用 state
更简单一些
上面计算属性后面跟的时一个箭头函数,虽然已经很简单了,但是在辅助函数的帮助下,还可以简化成一个属性名称
<script>
import { mapState } from 'vuex'
export default {
computed: mapState({
name1: 'name',
price1: 'price'
})
}
</script>
再简单一点
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组
<template>
<div class="container">{{ name }}--{{ price }}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: mapState(['name', 'price'])
}
</script>
- 这时候我们的计算属性就不能叫 name1 和 price1 了,而是与 state 中的属性同名
- mapState 中不是一个对象了,而是一个数组,数组的元素名称就是你想要使用的 state 中的属性名称
4.1.4 对象展开运算符
计算属性的格式如下:
- 值是一个对象
- 对象的键是属性名称,对象的值是一个函数
所以计算属性也可以写成这样
computed: { ...mapState(['name', 'price']) },
对比原来的写法
computed: mapState(['name', 'price']),
有点多此一举,但是假设,我们组件中本来有一些计算属性, mapState 函数返回的对象中也是计算属性,我们想将两者混合使用呢?
通过对象展开运算符,一切都变得简单了
<template>
<div class="container">{{ name }}--{{ price }}--{{ count }}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
count () {
return 5
},
sail () {
return 100
},
...mapState(['name', 'price'])
}
}
</script>
4.1.5 总结
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态
4.2Getter
Getter用于对Store中的数据进行加工处理形成新的数据
它只会包装Store中保存的数据,并不会修改Store中保存的数据,当Store中的数据发生变化时,Getter生成的内容也会随之变化
打开store.js文件,添加getters,如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '吃饭', done: true },
{ id: 2, text: '睡觉', done: false }
]
},
getters: {
// 定义一个 gettter,用于统计所有完成的事项
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
export default store
组件中可以这样直接使用
<template>
<div class="container">{{ $store.getters.doneTodos }}</div>
</template>
也可以使用 mapGetters 辅助函数
<template>
<div class="container">
<p>{{ doneTodos }}}}</p>
<p>{{ todos }}</p>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['doneTodos']), // 导入 getters
...mapState(['todos']) // 导入 state
}
}
</script>
总结:getter 是对 state 中的数据做出筛选过滤等,不能改变 state 中的数据,记住一句话:无论何时,都不允许直接操作 state 中的数据
4.3Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
比如想给 todos 中增加一个新的事项,不能直接修改,只能通过 mutation 才可以
mutations: {
add (state) {
state.todos.push({ id: 3, text: '学习', done: false })
}
}
mutation 非常像是一个事件处理函数,不能直接通过 mutation 名字+() 的方式调用,必须是通过store.commit 触发
组件中编写方法,触发 mutation
4.3.2 提交载荷
所谓载荷(payload)就是参数
在通过 commit 调用 mutaion 的时候可以传递额外的参数
修改 mutation
mutations: {
add (state, item) {
state.todos.push(item)
}
}
组件中调用
methods: {
addItem () {
this.$store.commit('add', { id: 3, text: '载荷', done: false })
}
}
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读Mutation 必须是同步函数:也就是说,不要在其中执行ajax 等异步操作
也可以使用 mapMutations辅助函数减少代码
4.4Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作,当然也可以是同步代码。
在mutations中不能编写异步的代码,会导致vue调试器的显示出错。
在vuex中我们可以使用Action来执行异步操作。