Vue学习笔记 (第七天2023黑马版)
第七天
1.基于VueCli自定义创建项目
创建vue项目,配置选项如下
2.ESlint代码规范
代码规范:一套写代码约定规则
解决代码规范错误:
- 手动修正
根据错误提示来修正代码,不认识语法报错可以去ESLint规则表中查找具体含义 - 自动修正
基于vscode插件ESLint高亮错误,并通过配置自动帮助我们修复错误
//当保存时,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
//保存代码,不自动格式化
"editor.formatOnSave": false
3.vuex
vuex概述
1.定义:vuex是一个vue的状态管理工具,状态就是数据
2.场景:
- 某个状态在很多个组件来使用(个人信息)
- 多个组件共同维护一份数据(购物车)
3.优势:
- 共同维护一份数据,数据集中化管理
- 响应式变化
- 操作简洁
构建vuex[多组件数据共享]环境
创建三个组件分别为App.vue,Son1.vue,Son2.vue
代码如下:
<!--App.vue-->
<template>
<div id="app">
<h1>根组件</h1>
<input type="text">
<Son1></Son1>
<hr>
<Son2></Son2>
</div>
</template>
<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
export default {
name: 'app',
data: function () {
return {
}
},
components: {
Son1,
Son2
}
}
</script>
<style>
#app {
width: 600px;
margin: 20px auto;
border: 3px solid #ccc;
border-radius: 3px;
padding: 10px;
}
</style>
<!--Son1.vue-->
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label></label>
<br />
<button>值 + 1</button>
</div>
</template>
<script>
export default {
name: 'Son1Com'
}
</script>
<style lang="css" scoped>
.box {
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>
<!--Son2.vue-->
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label></label>
<br />
<button>值 - 1</button>
</div>
</template>
<script>
export default {
name: 'Son2Com'
}
</script>
<style lang="css" scoped>
.box {
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>
创建一个空仓库
目标: 安装vuex插件,初始化一个仓库
- 安装vuex
npm add vuex@3 --legacy-peer-deps
- 新建vuex模块文件
- 创建仓库
//index.js
// vuex相关核心代码
import Vue from 'vue'
import Vuex from 'vuex'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store()
// 导出给main.js使用
export default store
- main.js挂载
new Vue({
render: h => h(App),
store
}).$mount('#app')
如何提供&访问vuex的数据
核心概念 - state状态
1.提供数据:
State提供唯一的公共数据源,所有共享的数据都统一放到Store中的State中存储。
在State对象中可以添加我们要共享的数据
const store = new Vuex.Store({
state: {
数据名: 数据值,
}
})
2.使用数据:
- 通过store直接访问
<!--模板中-->
{{ $store.state.xxx }}
//组件逻辑中
this.$store.state.xxx
//js模块中
store.state.xxx
- 通过辅助函数
①导入mapState
import { mapState } from 'vuex'
②数组方式引入state
③展开运算符映射
computed: {
...mapState(['count'])
}
④可以直接使用
<label>{{ count }}</label>
核心概念 - mutations
vuex同样遵循单向数据流,组件中不能直接修改仓库的数据
通过 strict:true 可以开启严格模式
const store = new Vuex.Store({
strict: true,
state: {
title: '大标题',
count: 100
}
})
- 定义mutations对象,对象中存放修改state的方法
const store = new Vuex.Store({
strict: true,
state: {
title: '大标题',
count: 100
},
mutations: {
addCount (state) {
state.count += 1
},
}
})
- 组件中提交调用mutations
this.$store.commit('addCount')
mutations的传参语法
- 提供mutations函数(带参数 - 提交载荷 payload)
mutations: {
addCount (state, n) {
state.count += n
},
}
- 页面中提交mutation(提交参数只能一个,如果有多个参数,包装成一个对象传递)
this.$store.commit('addCount', n)
辅助函数:mapMutations
mapMutations和mapState很像,他是把位于mutations中的方法提取出来,映射到组件methods中
methods: {
...mapMutations(['subCount', 'changeTitle']),//映射
handleSub (n) {
this.subCount(n)//直接调用
}
}
核心概念 - actions
mutations必须是同步的
actions处理异步操作
- 提供action方法
actions: {
changeCountAction (context, num) {
//一秒后,给个数,修改num值
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
}
- 页面dispatch调用
this.$store.dispatch('changeCountAction', 666)
辅助函数 - mapActions
mapActions是把位于actions中的方法提取出来,映射到组件methods中
import { mapActions } from 'vuex'
//-----------------------------------------
methods: {
...mapActions(['changeCountAction']),
}
//调用
this.changeCountAction(666)
核心概念 - getters
说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态时依赖state的,此时会用到getters
例如:state中定义了list,为1-10的数组,组件中,需要显示所有大于5的数据
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
- 定义getters
注意点:
getters函数的第一个参数是state
getters函数必须要有返回值
getters: {
filterList (state) {
return state.list.filter(item => item > 5)
}
}
- 访问getters
①通过store访问getters
{{ $store.getters.filterList }}
②通过辅助函数mapGetters映射
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
//------------------------------------------------
computed: {
...mapGetters(['filterList'])
},
{{ filterList }}
核心概念 - 模块module
user模块:store/modules/user.js
const state = {
userInfo: {
name: 'zs',
age: 18
},
score: 80
}
const mutations = {}
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
//在index中导入
import user from './modules/user'
const store = new Vuex.Store({
modules: {
user,
}
})
使用模块中的数据
- 直接通过模块名访问$store.state.模块名.xxx
<div>{{ $store.state.setting.theme }}</div>
- 通过mapState映射
默认根级别的映射 mapState([‘xxx’])
...mapState(['user', 'setting']),
子模块的映射 mapState(‘模块名’,[‘xxx’]) - 需要开启命名空间
//user.js
export default {
namespaced: true,//开启命名空间
state,
mutations,
actions,
getters
}
...mapState('user', ['userInfo']),
使用getters中的数据
- 直接通过模块名访问$store.getter[‘模块名/xxx’]
<div>{{ $store.getters['user/UpperCaseName'] }}</div>
- 通过mapGetters映射
默认根级别的映射 mapGetters([‘xxx’])
子模块的映射 mapState(‘模块名’,[‘xxx’]) - 需要开启命名空间
...mapGetters('user', ['UpperCaseName'])
调用子模块的mutation
- 直接通过模块名访问$store.commit(‘模块名/xxx’, 额外参数)
this.$store.commit('setting/setTheme', 'pink')
- 通过mapMutations映射
默认根级别的映射 mapMutations([‘xxx’])
子模块的映射 mapMutations(‘模块名’,[‘xxx’]) - 需要开启命名空间
...mapMutations('setting', ['setTheme']),
调用子模块中action
- 直接通过store访问$store.dispatch(‘模块名/xxx’, 额外参数)
this.$store.dispatch('user/setUserSecond', {
name: 'xiaohong',
age: 28
})
- 通过mapActions映射
默认根级别的映射 mapActions([‘xxx’])
子模块的映射 mapActions(‘模块名’,[‘xxx’]) - 需要开启命名空间
...mapActions('user', ['setUserSecond']),
4.综合案例 - 购物车
分析
- 构建cart购物车模块
①新建store/moudles/cart.js
②挂载到vuex仓库上store/index.js
- 基于json-server工具,准备后端接口服务环境
①安装全局工具json-server(以管理员身份运行)
yarn global add json-server
或
npm install -g json-server
②代码根目录新建一个db目录
③将资料index.json移入db目录
④进入db目录,执行命令,启动后端接口服务
json-server index.json
⑤访问接口测试http://localhost:3000/cart
- 请求获取数据存入vuex,映射渲染
①安装axios
npm install axios --save --legacy-peer-deps
②准备actions和mutations
③调用action获取数据
mutations: {
updateList (state, newList) {
state.list = newList
}
},
actions: {
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
context.commit('updateList', res.data)
}
},
④动态渲染
- 修改数量功能
①点击事件
②页面中dispatch
this.$store.dispatch('cart/updateCountAsync', {
id,
newCount
})
③提供action函数(更改后端数据)
async updateCountAsync (context, obj) {
await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
count: obj.newCount
})
context.commit('updateCount', {
id: obj.id,
newCount: obj.newCount
})
}
④提供mutation函数(更改前端数据)
updateCount (state, obj) {
const goods = state.list.find(item => item.id === obj.id)
goods.count = obj.newCount
}
- 底部getters统计
①提供getters
getters: {
total (state) {
return state.list.reduce((sum, item) => sum + item.count, 0)
},
totalPrice (state) {
return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
}
}
②使用getters
computed: {
...mapGetters('cart', ['total', 'totalPrice'])
}
核心代码
//cart.js
import axios from 'axios'
export default {
namespaced: true,
state () {
return {
list: []
}
},
mutations: {
updateList (state, newList) {
state.list = newList
},
updateCount (state, obj) {
const goods = state.list.find(item => item.id === obj.id)
goods.count = obj.newCount
}
},
actions: {
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
context.commit('updateList', res.data)
},
async updateCountAsync (context, obj) {
await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
count: obj.newCount
})
context.commit('updateCount', {
id: obj.id,
newCount: obj.newCount
})
}
},
getters: {
total (state) {
return state.list.reduce((sum, item) => sum + item.count, 0)
},
totalPrice (state) {
return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
}
}
}
<!--cart-footer.vue-->
<template>
<div class="footer-container">
<!-- 中间的合计 -->
<div>
<span>共 {{ total }} 件商品,合计:</span>
<span class="price">¥{{ totalPrice }}</span>
</div>
<!-- 右侧结算按钮 -->
<button class="btn btn-success btn-settle">结算</button>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'CartFooter',
computed: {
...mapGetters('cart', ['total', 'totalPrice'])
}
}
</script>
<style lang="less" scoped>
.footer-container {
background-color: white;
height: 50px;
border-top: 1px solid #f8f8f8;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 10px;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 999;
}
.price {
color: red;
font-size: 13px;
font-weight: bold;
margin-right: 10px;
}
.btn-settle {
height: 30px;
min-width: 80px;
margin-right: 20px;
border-radius: 20px;
background: #42b983;
border: none;
color: white;
}
</style>
<!--App.vue-->
<template>
<div class="app-container">
<!-- Header 区域 -->
<cart-header></cart-header>
<!-- 商品 Item 项组件 -->
<cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>
=
<!-- Foote 区域 -->
<cart-footer></cart-footer>
</div>
</template>
<script>
import CartHeader from '@/components/cart-header.vue'
import CartFooter from '@/components/cart-footer.vue'
import CartItem from '@/components/cart-item.vue'
import { mapState } from 'vuex'
export default {
name: 'App',
created () {
this.$store.dispatch('cart/getList')
},
computed: {
...mapState('cart', ['list'])
},
components: {
CartHeader,
CartFooter,
CartItem
}
}
</script>
<style lang="less" scoped>
.app-container {
padding: 50px 0;
font-size: 14px;
}
</style>
```html
<!--cart-item.vue-->
<template>
<div class="goods-container">
<!-- 左侧图片区域 -->
<div class="left">
<img :src="item.thumb" class="avatar" alt="">
</div>
<!-- 右侧商品区域 -->
<div class="right">
<!-- 标题 -->
<div class="title">{{ item.name }}</div>
<div class="info">
<!-- 单价 -->
<span class="price">¥{{ item.price }}</span>
<div class="btns">
<!-- 按钮区域 -->
<button class="btn btn-light" @click="btnClick(-1)">-</button>
<span class="count">{{ item.count }}</span>
<button class="btn btn-light" @click="btnClick(1)">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CartItem',
methods: {
btnClick (step) {
const newCount = this.item.count + step
const id = this.item.id
if (newCount < 1) return
this.$store.dispatch('cart/updateCountAsync', {
id,
newCount
})
}
},
props: {
item: {
type: Object,
required: true
}
}
}
</script>
<style lang="less" scoped>
.goods-container {
display: flex;
padding: 10px;
+ .goods-container {
border-top: 1px solid #f8f8f8;
}
.left {
.avatar {
width: 100px;
height: 100px;
}
margin-right: 10px;
}
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.title {
font-weight: bold;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
.price {
color: red;
font-weight: bold;
}
.btns {
.count {
display: inline-block;
width: 30px;
text-align: center;
}
}
}
}
}
.custom-control-label::before,
.custom-control-label::after {
top: 3.6rem;
}
</style>
//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
cart
}
})