1. 为什么使用Vuex?
在Vue的组件化开发中,父组件和子组件之间可以相互传递数据,但是依然有两个问题:
- 如果想在子组件中获取祖先组件(父组件以上的组件)中的数据,必须一层一层的进行传递
- 如果两个兄弟组件想传递数据,则必须先将数据传递给父组件,借助父组件才能完成兄弟组件传递数据;如果两个组件不在同一个父组件内,则也需要传递到祖先组件。 通过以上两个问题可以看出,不使用Vuex的情况下,兄弟组件之间传递、共享数据有多麻烦。
使用Vuex,可以将组件中公共的部分抽取出来,独立于各个组件,程序中的任何组件都可以获取和修改Vuex保存的公共数据。
2. 如何使用Vuex?
四个步骤:引入Vuex、创建Vuex对象、保存store、使用Vuex
2.1 引入Vuex
在官网下载Vuex:vuex.vuejs.org/zh/installa…
<script src="./lib/vue.js"></script>
<!-- 1. 导入Vuex -->
<!-- 注意点:在导入Vuex之前必须先导入Vue -->
<script src="./lib/vuex-3.6.2.js"></script>
2.2 创建Vuex对象
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 如果已经通过script引入了vuex文件可以忽略以上代码
const store = new Vuex.Store({
state: {
count: 0
},
// 注意点:在执行mutations中定义的方法的时候,系统会自动给这些方法传递一个state参数
// state中就保存了共享的数据
mutations: {
increment (state) {
state.count++
}
}
})
- state:相对于组件中的data,专门用于保存共享数据
// 在组件中可以通过"this.$store.state.变量名"来获取vuex中的数据,this.$store.state是固定字段
console.log(this.$store.state.count); // 0
- mutations: 定义在vuex内部的,用于对vuex保存数据进行操作的函数,在vuex中不推荐由组件直接修改vuex中的数据,通过调用在vuex内部定义的函数更有利于维护代码。
// 在组件中可以通过"this.$store.commit("函数名")"来调用vuex中的函数
add() {
this.$store.commit("increment");
}
2.3 添加store的key
在祖先组件(最外层的组件)中添加stroe,在祖先组件中定义的组件就都可以使用vuex
const store = new Vuex.Store({
// 这里的state就相对于组件中的data,就是专门用于保存共享数据的
state: {
msg: "深海鱼"
},
/* mutations: {
increment(state) {
state.count++
}
} */
});
Vue.component("grandfather", {
template: "#grandfather",
// 3. 在祖先组件中添加store的key保存Vuex对象
// 只要祖先组件中保存了Vuex对象,那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据
store,
// 父组件
components: {
"father": {
template: "#father",
components: {
"son1": {
template: "#son1",
}
}
}
}
})
2.4 使用Vuex
可以直接使用规定的字段来获取Vuex中的数据
<template id="grandfather">
<div>
<h1>{{this.$store.state.msg}}</h1>
<father></father>
</div>
</template>
<!-- 父组件使用vuex -->
<template id="father">
<div>
<!-- 4. 在使用Vuex中保存的共享数据的时候,必须通过如下的格式来使用 -->
<h2>{{this.$store.state.msg}}</h2>
<son1></son1>
</div>
</template>
<!-- 子组件使用vuex -->
<template id="son1">
<div>
<h3>{{this.$store.state.msg}}</h3>
</div>
</template>
3. getters属性
Vuex的getters相对于组件中的计算属性
const store = new Vuex.Store({
// state:用于保存共享数据
state: {
msg: "深海鱼"
},
// mutations: 用于保存修改共享数据的方法
mutations: {
},
getters: {
format(state) {
console.log("getters方法被执行了");
return state.msg + '没了没了';
}
}
});
<template id="father">
<div>
{{this.$store.state.msg}}
{{this.$store.getters.format}}
{{this.$store.getters.format}}
{{this.$store.getters.format}}
</div>
</template>
输出结果:
暂时写到这里,Vuex之后的部分会随着学习不断更新。
4. 应用
在真实的应用开发中,可能会有很多条数据或状态存储在vuex中,如果我们把每条数据都存储在state中,就必须对每条数据建立mutations方法,就必须对每个方法建立actions触发事件,也必须在getters中定义返回state当中值的事件。
我们可以将state、mutations、actions、getters单独建立文件,在文件中利用export default将其暴露,再在index.js中逐一导入,就可以实现很方便的管理,接下来是实例:
音乐在线项目
在这个项目中,vuex被用来管理播放页、迷你播放组件,以及迷你组件当中播放列表页面的显示和隐藏状态,因为这些页面和组件可以在项目的任意位置被使用,所以最适合用vuex来进行全局管理。
vuex中定义
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
// state: 用于保存全局共享的数据
state,
// mutations:用于保存修改全局共享的数据的方法
mutations,
// actions:用于保存触发mutations中保存的方法的方法
actions,
modules: {
},
getters
})
state.js
export default {
isFullScreen: false,
isShowMiniPlayer: false
}
mutations.js
- 这里可以看到多引入了一个mutations-type,原因是如果使用直接使用字符串,填写错误时不会报错,如果定义了一个公共变量,变量名错误时就会报错。
import { SET_FULL_SCREEN, SET_MINI_PLAYER } from './mutations-type'
export default {
/* eslint-disable */
/* changeFullScreen (state, flag) {
state.isFullScreen = flag
} */
[SET_FULL_SCREEN] (state, flag) {
state.isFullScreen = flag
},
[SET_MINI_PLAYER] (state, flag) {
state.isShowMiniPlayer = flag
}
}
mutations-type
export const SET_FULL_SCREEN = 'SET_FULL_SCREEN'
export const SET_MINI_PLAYER = 'SET_MINI_PLAYER'
actions
import { SET_FULL_SCREEN, SET_MINI_PLAYER } from './mutations-type'
export default {
setFullScreen ({ commit }, flag) {
commit(SET_FULL_SCREEN, flag)
},
setMiniPlayer ({ commit }, flag) {
commit(SET_MINI_PLAYER, flag)
}
}
getters
export default {
isFullScreen (state) {
return state.isFullScreen
},
isShowMiniPlayer (state) {
return state.isShowMiniPlayer
}
}
播放页面
*css部分代码不做展示
<template>
<transition v-on:enter="enter" @leave="leave">
<!-- 在这里使用了vuex中定义的数据,在getters中定义了获取方法后在需要使用的组件的computed中定义...mapGetters(['getters中方法名']),就可以通过this.方法名的方式获取到所需数据 -->
<div class="normal-player" v-show="this.isFullScreen">
<div class="player-waepper">
<PlayHeader></PlayHeader>
<PlayerMiddle></PlayerMiddle>
<PlayerBottom></PlayerBottom>
</div>
<div class="player-bg">
<img src="https://dfzximg01.dftoutiao.com/news/20210614/20210614134446_5350398987762e5ff84917e9f52af0c9_4.png" alt="">
</div>
</div>
</transition>
</template>
<script>
import PlayHeader from './PlayHeader'
import PlayerMiddle from './PlayerMiddle'
import PlayerBottom from './PlayerBottom'
import { mapGetters } from 'vuex' // vuex在这里被引入
import Velocity from 'velocity-animate'
import 'velocity-animate/velocity.ui'
export default {
name: 'NormalPlayer',
components: {
PlayHeader,
PlayerMiddle,
PlayerBottom
},
computed: {
...mapGetters([
'isFullScreen' // 因为没有调用vuex当中的方法,仅是获取属性值,只需要在computed中定义...mapGetters({})
])
},
methods: {
enter (el, done) {
Velocity(el, 'transition.shrinkIn', { duration: 500 }, () => {
done()
})
},
leave (el, done) {
Velocity(el, 'transition.shrinkOut', { duration: 500 }, () => {
done()
})
}
}
}
</script>
歌单页面
<template>
<ul class="detail-bottom">
<li class="bottom-top">
<div class="bottom-icon"></div>
<div class="bottom-title">播放全部</div>
</li>
<!-- 歌单中的歌曲被点击时更改vuex中管理的数据状态,使歌曲播放页显示 -->
<li v-for="value in playlist" :key="value.id" class="item" @click="selectMusic">
<h3>{{value.name}}</h3>
<p>{{value.al.name}} - {{value.ar[0].name}}</p>
</li>
</ul>
</template>
<script>
// 因为需要更改数据状态,所以从vuex需要引入mapActions
import { mapActions } from 'vuex'
export default {
name: 'DetailBottom',
props: {
playlist: {
type: Array,
default: () => [],
required: true
}
},
methods: {
// 这里同样只需要在其中写明需要调用的actions方法,用数组+字符串的形式,就可以在外部通过this.方法名调用
...mapActions([
'setFullScreen', // 将`this.setFullScreen(true)`映射为 `this.$store.dispatch('setFullScreen', true)`
'setMiniPlayer'
]),
selectMusic () {
// this.$store.dispatch('setFullScreen', true)
// 可以看到actions方法可以传递参数
this.setFullScreen(true)
this.setMiniPlayer(false)
}
}
}
</script>
迷你播放器组件
迷你播放器组件既需要获取自身是否显示的状态,又需要监听点击事件来控制大播放页面是否显示,这时就需要同时从vuex中引入mapActions和mapGetters。
<template>
<transition @enter="enter" @leave="leave">
<div class="mini-player" v-show="this.isShowMiniPlayer">
<div class="player-warpper">
<div class="player-left" @click="showNormal">
<img src="https://p1.music.126.net/8y8KJC1eCSO_vUKf2MyZwA==/109951165796899183.jpg" alt="" >
<div class="player-title">
<h3>演员</h3>
<p>薛之谦</p>
</div>
</div>
<div class="player-right">
<div class="play"></div>
<div class="list" @click.stop="showList"></div>
</div>
</div>
</div>
</transition>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import Velocity from 'velocity-animate'
import 'velocity-animate/velocity.ui'
export default {
name: 'MiniPlayer',
methods: {
...mapActions([
'setFullScreen',
'setMiniPlayer'
]),
showNormal () {
this.setMiniPlayer(false)
this.setFullScreen(true)
},
showList () {
this.$emit('showList')
},
enter (el, done) {
Velocity(el, 'transition.bounceUpIn', { duration: 500 }, () => {
done()
})
},
leave (el, done) {
Velocity(el, 'transition.bounceDownOut', { duration: 500 }, () => {
done()
})
}
},
computed: {
...mapGetters([
'isShowMiniPlayer'
])
}
}
</script>
记忆点:
- mapActions 引入后在methods中定义
...mapActions([]) - mapGetters 引入后在computed中定义
...mapGetters([])