状态管理
在很多应用场景下,我们需要在组件之间共享状态,比如我们的左侧导航栏需要收缩和展开的功能,收缩状态时宽度很小,只显示菜单图标,因为导航菜单栏收缩之后宽度变了,所以右侧的主内容区域要占用导航栏收缩的空间,主内容区域宽度也要根据导航栏的收缩状态做变更,而导航栏和主内容区域是两个不同的组件,而非父子组件之间不支持状态传递,所以组件之间的状态共享问题发生了。
之前我写flutter的时候,响应式设计的状态管理给我留下了深刻的印象,当时我用了provider这个库来管理全局状态。然后vue生态里有个vuex,据说借鉴了 Flux、Redux 和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
vuex是一个专为vue.js应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex官网地址:https://vuex.vuejs.org/zh/
安装vuex
不管那么多,先安装
yarn add vuex@3.6.2
注意要使用3.x版本的vuex,4.x是vue3用的,目前我们的项目还是基于vue2。
添加store
在src目录下新建一个store目录,专门管理应用状态。
目录结构如下
store
├── modules
│ └── app.js
└── index.js
index.js
在index.js中引入vuex并统一组织导入和管理子模块。
代码如下
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
// 引入子模块
import app from './modules/app'
const store = new vuex.Store({
modules: {
app: app
}
})
export default store
modules/app.js
app.js是属于应用内的全局性的配置,比如主题色、导航栏收缩状态等,详见注释。
代码如下
export default {
state: {
appName: "StarBlog", // 应用名称
themeColor: "#14889A", // 主题颜色
oldThemeColor: "#14889A", // 上一次主题颜色
collapse: false, // 导航栏收缩状态
menuRouteLoaded: false // 菜单和路由是否已经加载
},
getters: {
collapse(state) {// 对应着上面state
return state.collapse
}
},
mutations: {
onCollapse(state) { // 改变收缩状态
state.collapse = !state.collapse
},
setThemeColor(state, themeColor) { // 改变主题颜色
state.oldThemeColor = state.themeColor
state.themeColor = themeColor
},
menuRouteLoaded(state, menuRouteLoaded) { // 改变菜单和路由的加载状态
state.menuRouteLoaded = menuRouteLoaded;
}
},
actions: {}
}
引入与使用store
在src/main.js
中引入store。
代码如下
import store from './store'
new Vue({
el: '#app',
router,
store, // 传入store
components: {App},
template: '<App/>'
})
这里以收缩侧边栏功能为例。
首先给顶栏添加一个按钮,用来控制侧边栏收缩。
添加收缩组件
在src下新建components目录,并在其下创建导航栏收缩展开组件Hamburger。
目录结构如下
src
└── components
└── Hamburger
└── index.vue
组件是使用SVG绘制,绘制根据isActive状态决定是否旋转、显示收缩和展开状态不同的图形。
代码如下
<template>
<svg t="1492500959545" @click="onClick == null ? emptyClick : onClick" class="hamburger" fill="#fff"
fill-opacity="0.8" :class="{'is-active':isActive}" viewBox="0 0 1024 1024"
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink"
width="64" height="64">
<path
d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
p-id="1692"></path>
<path
d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
p-id="1693"></path>
<path
d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
p-id="1694"></path>
</svg>
</template>
<script>
export default {
name: 'hamburger',
props: {
isActive: {
type: Boolean,
default: false
},
onClick: {
type: Function,
default: null
}
},
methods: {
emptyClick() {
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
cursor: pointer;
width: 20px;
height: 20px;
transform: rotate(90deg);
transition: .38s;
transform-origin: 50% 50%;
}
.hamburger.is-active {
transform: rotate(0deg);
}
</style>
HeaderBar页面引用收缩组件
编辑HeaderBar.vue
,在头部区域HeadBar中引入hamburger,并将自身isActive状态跟收缩状态collapse绑定
布局代码如下
<template>
<div class="header" style="background:#14889A"
:class="collapse?'position-collapse-left':'position-left'">
<!-- 导航收缩 -->
<span class="hamburg">
<el-menu class="el-menu-demo" background-color="#14889A" text-color="#fff" active-text-color="#14889A"
mode="horizontal">
<el-menu-item index="1" @click="onCollapse"><hamburger :isActive="collapse"></hamburger></el-menu-item>
</el-menu>
</span>
<!-- 工具栏 -->
<!-- 旧代码省略 -->
</template>
通过computed计算属性引入store属性,这样就可以直接在页面中通过collapse引用状态值了。(当然如果不嫌长,也可以不使用计算属性,直接在页面中通过$store.state.app.collapse
引用)
添加以下js代码
// 首先引入
import {mapState} from 'vuex'
import Hamburger from "@/components/Hamburger"
export default {
name: "HeaderBar",
components: {
Hamburger
},
methods: {
// 折叠导航栏
onCollapse: function () {
this.$store.commit('onCollapse')
},
},
computed: {
...mapState({
collapse: state => state.app.collapse
})
}
}
添加以下scss代码
.hamburg, .navbar {
float: left;
}
.position-collapse-left {
left: 65px;
}
Navbar页面修改
修改Navbar.vue
布局代码修改
<template>
<div class="menu-bar-container">
<!-- logo -->
<div class="logo" style="background:#14889A" :class="collapse?'menu-bar-collapse-width':'menu-bar-width'"
@click="$router.push('/')">
<img v-if="collapse" src="@/assets/codelab.jpg"/>
<div>{{ collapse ? '' : appName }}</div>
</div>
</div>
</template>
js代码修改
import {mapState} from 'vuex'
export default {
computed: {
...mapState({
appName: state => state.app.appName,
collapse: state => state.app.collapse,
})
},
}
scss代码添加
.menu-bar-collapse-width {
width: 65px;
}
MainContent页面修改
修改布局代码
<template>
<div id="main-container" class="main-container"
:class="$store.state.app.collapse?'position-collapse-left':'position-left'">
<!-- 标签页 -->
<div class="tab-container"></div>
<!-- 主内容区域 -->
<div class="main-content">
<keep-alive>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</keep-alive>
</div>
</div>
</template>
添加scss代码
.position-collapse-left {
left: 65px;
}
测试效果
未收缩侧边栏的情况
点击按钮收缩侧边栏
完成,收工~