# 第八模块:前端VUE2-状态管理
# 6.vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
说人话:将组件中需要共享的数据交给vuex来帮我们进行管理,例如:用户登录状态、加入购物车。
# 6.1 案例:登录
vue create vxdemo
npm install vue-router@3
npm install vuex@3
main.js
import Vue from 'vue' import App from './App.vue' import router from "./router" import store from "./store" Vue.config.productionTip = false new Vue({ router: router, store: store, render: h => h(App), }).$mount('#app')
App.vue
<template> <div id="app"> <div class="menu"> <div class="container"> <router-link to="/home">首页</router-link> <router-link to="/course">课程</router-link> <div style="float: right"> <a v-if="this.$store.state.isLogin"> {{this.$store.state.userInfo.username}} </a> <router-link v-else to="/login">登录</router-link> </div> </div> </div> <div class="container"> <router-view></router-view> </div> </div> </template> <script> export default { name: 'App', data() { return {} }, components: {}, } </script> <style> body { margin: 0; } .container { width: 1100px; margin: 0 auto; } .menu { height: 48px; background-color: #499ef3; line-height: 48px; } .menu a { color: white; text-decoration: none; padding: 0 10px; } </style>
store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { isLogin: false, //是否登录 userInfo: null //用户信息 }, mutations: { login: function (state, info) { state.userInfo = info; state.isLogin = true; }, }, actions: {} })
router/index.js
// router/index.js import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../components/Home' import Course from '../components/Course' import Login from '../components/Login' Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: '/home', name: "Home", component: Home }, { path: '/course', name: "Course", component: Course }, { path: '/login', name: "Login", component: Login }, ] }) export default router
components/Login.vue
<template> <div> <input type="text" v-model="info.username" placeholder="用户名"> <input type="password" v-model="info.password" placeholder="密码"> <input type="button" value="登录" @click="doLogin"/> </div> </template> <script> export default { name: "Login", data() { return { info: { username: "", password: "" } } }, methods: { doLogin: function () { // 1.用户登录 this.$store.commit('login', this.info); // 2.登录成功修改状态 this.$router.push({name: 'Home'}); } } } </script> <style scoped> </style>
# 6.2 关于computed属性
在vue的组件中有一个computed属性(计算属性),监听关联的数据,如果发生变化则重新计算并显示。
<template>
<div>
<h1>主页 {{v1}} {{ v2}}</h1>
<div>总数:{{totalData}}</div>
<input type="button" value="点我" @click="addData"/>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
v1: 123,
v2: 456
}
},
computed: {
totalData: {
get() {
let data = this.v1 + this.v2;
return data + 1000;
},
set(value) {
this.v1 = value;
}
}
},
methods: {
addData() {
this.totalData = 999;
// this.v2 = 1000;
}
}
}
</script>
<style scoped>
</style>
所以,上述案例也可以用computed属性来实现,例如:App.vue改成:
<template>
<div id="app">
<div class="menu">
<div class="container">
<router-link to="/home">首页</router-link>
<router-link to="/course">课程</router-link>
<div style="float: right">
<a v-if="userState">
{{userName}}
</a>
<router-link v-else to="/login">登录</router-link>
</div>
</div>
</div>
<div class="container">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {}
},
computed: {
userState: {
get() {
return this.$store.state.isLogin;
}
},
userName() {
return this.$store.state.userInfo.username;
},
},
components: {},
}
</script>
<style>
body {
margin: 0;
}
.container {
width: 1100px;
margin: 0 auto;
}
.menu {
height: 48px;
background-color: #499ef3;
line-height: 48px;
}
.menu a {
color: white;
text-decoration: none;
padding: 0 10px;
}
</style>
# 6.3 案例:购物车
# 6.4 关于Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
代码示例:
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count+=1;
}
},
actions: {
increment (context) {
// 触发mutations
context.commit('increment')
}
}
})
在组件中如果要触发,则应该执行:
this.$store.dispatch('increment')
这就有点像脱裤子放屁,意义何在呢? 当有异步操作时,应该使用action、而不是mutation,例如:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
isLogin: false, //是否登录
userInfo: null, //用户信息
carNumber: 0,
xxxxx: 10
},
mutations: {
login: function (state, info) {
state.userInfo = info;
state.isLogin = true;
},
addCar: function (state) {
state.carNumber += 1;
},
fetchAsync: function (state) {
// ajax
setTimeout(function () {
state.xxxxx += 1;
}, 1000);
}
},
actions: {}
})
this.$store.commit("fetchAsync");
从效果上看是没有问题的,但是通过开发者工具就会发现,监测到的state的数据不准确。
所以,这种情况可以选择用Action。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
isLogin: false, //是否登录
userInfo: null, //用户信息
carNumber: 0,
xxxxx: 10
},
mutations: {
login: function (state, info) {
state.userInfo = info;
state.isLogin = true;
},
addCar: function (state) {
state.carNumber += 1;
},
fetchAsync: function (state,data) {
state.xxxxx += 1;
console.log(data);
}
},
actions: {
fetchAsync: function (context,data) {
setTimeout(function () {
context.commit("fetchAsync",data);
}, 1000);
}
}
})
再触发时,调用:
this.$store.dispatch('fetchAsync',{ amount: 10})
# 7.flex布局
在CSS3中flex可以非常便捷的可以帮助我们实现对页面的布局。
- 传统的页面布局,基于div+float来实现。
- flex可以快速实现页面的布局(很方便)。
关于flex布局你必须要了解的有一下几点:
<div class="menu" 样式>
<div class="item" 样式>112</div>
<div class="item">113</div>
</div>
# 7.1 容器
# 7.1.1 flex布局
在容器元素上应用
<div class="menu">
<div class="item">112</div>
<div class="item">113</div>
</div>
<style>
.menu{
border: 1px solid red;
width: 500px;
display: flex; // 表示flex布局
}
</style>
# 7.1.2 元素的方向(主轴和副轴)
<div class="menu">
<div class="item">112</div>
<div class="item">113</div>
</div>
<style>
.menu{
border: 1px solid red;
width: 500px;
display: flex; // 表示flex布局
flex-direction: row; // 主轴是横向,副轴是纵向。
}
</style>
# 7.1.3 元素排列方式
justify-content,主轴上的元素的排列方式
align-items,副轴上的元素的排列方式。
<div class="menu">
<div class="item">11</div>
<div class="item">112</div>
<div class="item">112</div>
</div>
<style>
.menu {
width: 500px;
border: 1px solid red;
display: flex;
flex-direction: row;
justify-content: flex-start; /* 主轴=横向,横向从左开始 */
justify-content: flex-end; /* 主轴=横向,横向从右开始 */
justify-content: space-between; /* 主轴=横向,左右定格,中间元素等分空白 */
justify-content: space-around; /* 主轴=横向,所有元素等分空白 */
justify-content: space-evenly; /* 主轴=横向,元素间距一样 */
}
.item {
border: 1px solid green;
padding: 5px 50px;
height: 50px;
width: 40px;
}
</style>
<div class="menu">
<div class="item">11</div>
<div class="item">112</div>
<div class="item">112</div>
</div>
<style>
.menu {
width: 500px;
height: 300px;
border: 1px solid red;
display: flex;
flex-direction: row;
justify-content: flex-start; /* 主轴=横向,横向从左开始 */
justify-content: flex-end; /* 主轴=横向,横向从右开始 */
justify-content: space-between; /* 主轴=横向,左右定格,中间元素等分空白 */
justify-content: space-around; /* 主轴=横向,所有元素等分空白 */
justify-content: space-evenly; /* 主轴=横向,元素间距一样 */
align-items: center; /* 副轴=纵向,元素居中*/
align-items: flex-start; /* 副轴=纵向,元素顶部*/
align-items: flex-end; /* 副轴=纵向,元素底部*/
}
.item {
border: 1px solid green;
padding: 5px 50px;
height: 50px;
width: 40px;
}
</style>
# 7.1.4 换行
flex-wrap: nowrap;
元素超过容器,不换行flex-wrap: wrap;
元素超过容器,换行
示例1:不换行
<div class="menu">
<div class="item">11</div>
<div class="item">112</div>
<div class="item">112</div>
<div class="item">112</div>
<div class="item">112</div>
</div>
<style>
.menu {
width: 500px;
height: 200px;
border: 1px solid red;
display: flex;
flex-direction: row;
justify-content: space-evenly; /* 主轴=横向,元素间距一样 */
align-items: flex-start; /* 副轴=纵向,元素顶部*/
flex-wrap: nowrap;
}
.item {
border: 1px solid green;
padding: 5px 50px;
height: 50px;
width: 40px;
}
</style>
示例2:换行
<div class="menu">
<div class="item">111</div>
<div class="item">112</div>
<div class="item">112</div>
<div class="item">112</div>
<div class="item">112</div>
</div>
<style>
body{
margin: 0;
}
.menu {
width: 500px;
height: 200px;
border: 1px solid red;
display: flex;
flex-direction: row;
justify-content: space-evenly; /* 主轴=横向,元素间距一样 */
align-items: flex-start; /* 副轴=纵向,元素顶部*/
flex-wrap: wrap;
}
.item {
border: 1px solid green;
padding: 5px 50px;
height: 50px;
width: 40px;
}
</style>
# 7.1.5 多行对齐方式
align-content用于控制多行元素的对齐方式,如果元素只有一行则不会起作用;默认stretch,即在元素没设置高度,或高度为auto情况下让元素填满整个容器。
flex-start | flex-end | center | space-between | space-around | space-evenly | stretch(默认);
# 7.2 元素
# 7.2.1 顺序
order,默认0,用于决定项目排列顺序,数值越小,项目排列越靠前。
<div class="father">
<div class="son" style="order: 2">11</div>
<div class="son" style="order: 0">22</div>
<div class="son" style="order: 1">33</div>
</div>
<style scoped>
.father {
border: 1px solid red;
width: 500px;
height: 300px;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.father .son {
border: 1px solid saddlebrown;
width: 20px;
height: 18px;
}
</style>
# 7.2.2 剩余空间
flex-grow,默认0,用于决定项目在有剩余空间的情况下是否放大,默认不放大;
<div class="father">
<div class="son">11</div>
<div class="son" style="flex-grow: 1">22</div>
<div class="son" style="flex-grow: 1">33</div>
</div>
# 案例:flex页面布局
<template>
<div>
<div class="row1">
<div class="company"></div>
<div class="pic"></div>
<div class="pic"></div>
<div class="pic"></div>
</div>
<div class="row2">
<div class="title">
<div>手机</div>
<div>查看更多</div>
</div>
<div class="pic-list">
<div class="big"></div>
<div class="right-list">
<div class="group">
<div class="phone"></div>
<div class="phone"></div>
<div class="phone"></div>
<div class="phone"></div>
</div>
<div class="group">
<div class="phone"></div>
<div class="phone"></div>
<div class="phone"></div>
<div class="phone"></div>
</div>
</div>
</div>
</div>
<div class="course-list">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
</div>
</template>
<script>
export default {
name: "Mi"
}
</script>
<style scoped>
.row1 {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.row1 .company {
width: 210px;
height: 180px;
background-color: saddlebrown;
}
.row1 .pic {
width: 266px;
height: 180px;
background-color: cadetblue;
}
.row2 .title {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.row2 .pic-list {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.row2 .pic-list .big {
background-color: aquamarine;
height: 610px;
width: 210px;
margin-right: 20px;
}
.row2 .pic-list .right-list {
/*background-color: antiquewhite;*/
flex-grow: 1;
}
.row2 .pic-list .right-list .group {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
}
.row2 .pic-list .right-list .phone {
margin-bottom: 10px;
border: 1px solid red;
width: 200px;
height: 300px;
}
.course-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.course-list .item {
width: 24%;
height: 100px;
background-color: skyblue;
margin-top: 15px;
}
// 如果最后一个元素,是第3个,右边距=一个位置 + 所有空白位置/3(有三个空白位置)
.course-list .item:last-child:nth-child(4n - 1) {
margin-right: calc(24% + 4% / 3);
}
// 如果最后一个元素,是第2个,右边距=两个位置 + 所有空白位置/3*2(有三个空白位置)
.course-list .item:last-child:nth-child(4n - 2) {
margin-right: calc(48% + 8% / 3);
}
</style>
至此,结合以上的知识点,我们就可以来开发一个项目,但很多的页面按钮、标签、提示框等都需要自己手动,太费劲。
所以,接下来就要给大家的来聊一个UI框架 - ElementUI,他的内部提供了很多方便的样式和组件,可以加速我们开发。
# 8.ElementUI
Element,是有 饿了吗 团队开发一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库(也支持vue3.x)。
在他的基础上来开发,可以大大提升开发进度。
想要在项目中进行使用element-ui需要提前安装并引入。
# 8.1 引入
**方式1:**完整引入
安装
cd 项目目录 npm install element-ui
引入
import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; Vue.use(ElementUI); new Vue({ render: h => h(App), }).$mount('#app')
在组件中使用
<el-button type="success">哈哈哈</el-button>
方式2:局部引入
安装
cd 项目目录 npm install element-ui
引入
import Vue from 'vue'; import { Button, Select } from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; Vue.use(Button) Vue.use(Select) new Vue({ render: h => h(App), }).$mount('#app')
在组件中使用
<el-button type="success">哈哈哈</el-button>
完整组件名称:https://element.eleme.cn/#/zh-CN/component/quickstart
# 8.2 组件的使用
参见官方文档,在后续的项目开发中来应用并使用。