VueCli 自定义创建项目

1.安装脚手架 (已安装)

1
npm i @vue/cli -g

2.创建项目

1
vue create hm-exp-mobile
  • 选项
1
2
3
4
5
Vue CLI v5.0.8
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features 选自定义

image.png

  • 选择eslint的风格 (eslint 代码规范的检验工具,检验代码是否符合规范)

  • 比如:const age = 18;   =>  报错!多加了分号!后面有工具,一保存,全部格式化成最规范的样子
    image.png

  • 启动项目

1
npm run serve

Vuex

Vuex 是一个 Vue 的 状态管理工具,状态就是数据。
大白话:Vuex 是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如:购物车数据 个人信息数

基本使用

image.png

1.安装 vuex

安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。

1
yarn add vuex@3 或者 npm i vuex@3

2.新建 store/index.js 专门存放 vuex

为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和 `router/index.js` 类似)

image.png
.创建仓库 store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store()

// 导出仓库
export default store

4 在 main.js 中导入挂载到 Vue 实例上

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
store
}).$mount('#app')

此刻起, 就成功创建了一个 空仓库!!

5.测试打印Vuex

App.vue

1
2
3
created(){
console.log(this.$store)
}

state 状态

如何给仓库存储数据, 如果取出使用仓库的数据

提供数据(存入数据)

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。
打开项目中的store.js文件,在state对象中可以添加我们要共享的数据。

1
2
3
4
5
6
7
8
9
10
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别:
// 1.data 是组件自己的数据,
// 2.state 中的数据整个vue项目的组件都能访问到
state: {
count: 101
}
})

访问数据

1
2
3
4
5
6
7
8
获取 store:
1.Vue模板中获取 this.$store
2.js文件中获取 import 导入 store


模板中: {{ $store.state.xxx }}
组件逻辑中: this.$store.state.xxx
JS模块中: store.state.xxx

如果数据量变大, 那么使用这种方法明显就比较累坠了。所以我们可以通过使用辅助函数来帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法
image.png
通过数组的方式得到对象

第一步:导入mapState (mapState是vuex中的一个函数)

1
import { mapState } from 'vuex'

第二步:采用数组形式引入state属性

1
mapState(['count']) // count 就是我们仓库中的属性名

上面代码的最终得到的是 类似于

1
2
3
count () {
return this.$store.state.count
}

第三步:利用展开运算符将导出的状态映射给计算属性

1
2
3
computed: {
...mapState(['count'])
}
1
<div> state的数据:{{ count }}</div>

注意:

通过这样方式如果修改属性会报错, 因为vuex默认开启了严选模式
也就是说通过**vuex** 得到的数据是单项流模式, 组件是不能直接修改仓库中的数据。

state数据的修改只能通过mutations,并且mutations必须是同步的

image.png

核心概念mutations

定义mutations

1
2
3
4
5
6
7
8
9
const store  = new Vuex.Store({
state: {
count: 0
},
// 定义mutations
mutations: {

}
})

mutations是一个对象, 对象中存放的是修改state的方法

1
2
3
4
5
6
7
mutations: {
// 方法里参数 第一个参数是当前store的state属性
// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
addCount (state) {
state.count += 1
}
},

组件中提交mutations

通过点击事件实现修改方法的触发, 然后在通过下面语句实现调用mustations中的addCount方法

1
<button @click="add()">值 + 5</button>
1
2
3
4
5
methods: { 
add(){
this.$store.commit('addCount')
}
}

带参数的mutations函数

**提交 mutation 是可以传递参数的 **this.$store.commit('xxx', 参数)
在定义mutations中的方法的时候可以直接通过下面的类似语句进行修改。

1
2
3
4
5
6
mutations: {

addCount (state, count) {
state.count = count
}
},

注意: 提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象

通过input标签修改state数据

image.png

1
2
3
4
5
6
7
8
9
10
11
12
<input :value="count" @input="handleInput" type="text">

export default {
methods: {
handleInput (e) {
// 1. 实时获取输入框的值
const num = +e.target.value
// 2. 提交mutation,调用mutation函数
this.$store.commit('changeCount', num)
}
}
}

然后在store/index.js文件中

1
2
3
4
5
mutations: { 
changeCount (state, newCount) {
state.count = newCount
}
},

辅助函数 - mapMutations

mapMutations和mapState很像,它把位于mutations中的方法提取了出来,我们可以将它导入

在所要使用的组件中

1
2
3
4
import  { mapMutations } from 'vuex'
methods: {
...mapMutations(['addCount'])
}

上面代码的含义是将mutations的方法导入了methods中,等价于

1
2
3
4
5
6
methods: {
// commit(方法名, 载荷参数)
addCount () {
this.$store.commit('addCount')
}
}

此时,就可以直接通过this.addCount调用了

1
<button @click="addCount">值+1</button>

注意: Vuex中mutations中要求不能写异步代码,如果有异步的ajax请求,应该放置在actions中

核心概念 - actions

state是存放数据的,mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),actions则负责进行异步操作

说明:mutations必须是同步的

需求: 一秒钟之后, 要给一个数 去修改state

image.png

  1. 在组件中通过点击事件修改
1
2
3
4
5
6
7
8
9
10
11
<button @click="setAfter()" >1s 后修改为 666</button>


// script格式中

methods: {
setAfter(){
const val = 666
this.$store.dispatch('change', val)
}
}
  1. 通过this.$store.dispatch('方法名', 参数)调用store/index.js中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建仓库 store
const store = new Vuex.Store({
state: {
ount: 100
},
mutations: {
changeCount(state, count){
state.ount = count
}
},
actions: {
// 不能在actions中直接修改, 需要调用mutations中的方法
change(context, count) {
setTimeout(() => {
//调用mutations 的changeCount, 从而修改
context.commit('changeCount', count)
},2000)
}
}
})

image.png

辅助函数 - mapActions

mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中, 不需要在写方法来调用了

1
2
3
4
5
6
7
8
9
10
11
12
import { mapActions } from 'vuex'

methods: {
...mapActions(['changeCountAction'])
}

//mapActions映射的代码 本质上是以下代码的写法
//methods: {
// changeCountAction (n) {
// this.$store.dispatch('changeCountAction', n)
// },
//}
1
2
<!-- 参数可以直接进行传递, 不需要考虑methods中, 但是还是最多只能传一个, 多了就封装成为对象, 然后通过对象的形式传过去. -->
<button @click="changeCountAction(200)">+异步</button>

然后在store/index.js中进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建仓库 store
const store = new Vuex.Store({
state: {
ount: 100
},
mutations: {
addCount(state, count) {
state.ount += count
},
changeCount(state, count){
state.ount = count
}
},
actions: {
// 最好不要自己直接修改,
change(context, count) {
setTimeout(() => {
//调用山寺规模的changeCount, 从而修改
context.commit('changeCount', count)
},2000)
},

addFive(context, count) {
setTimeout(() => {
//在这里通过上下文来调用mutations中的方法
context.commit('addCount', count)
},2000)
}
}
})

核心概念 - getters

除了state之外,有时我们还需要从state中筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters

例如, 组件中定义了list数组, 我们需要筛选出list中 大于 X的数据. 就可以通过getters实现

1
2
3
4
5
6
7
8
9
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},

getters: {
// getters函数的第一个参数是 state
// 必须要有返回值
filterList: state => state.list.filter(item => item > 5)
}

使用getters

2.1原始方式-$store

在组件中, 通过$store对象来获取他的getters属性, 然后再获取其中的方法

1
<div>{{ $store.getters.filterList }}</div>

2.2辅助函数 - mapGetters

1
2
3
computed: {
...mapGetters(['filterList'])
}
1
<div>{{ filterList }}</div>

四种核心方法使用总结

image.png

模块module

拆封模块的原因:

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
这句话的意思是,如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护

image.png

在store中配置module模块。 然后在每个模块中设置state、mutations、actions、getters
image.png

挂载模块

  1. model/模块名.js定义模块的内容, 设置4个属性, 然后导出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//settings模块
const state= {
theme: 'light' ,
desc: '测试demo'
}
const mutations = { }
const actions = { }
const getters = { }

// 导出
export default {
state,
mutations,
actions,
getters
}
  1. index.js中进行导入模块和 注册这两个模块
1
2
3
4
5
6
7
8
9
10
11
12
//导入模块
import user from './modules/user'
import settings from './modules/settings'

// 创建仓库 store
const store = new Vuex.Store({
//注册模块
modules: {
user,
settings
},
})

image.png

访问模块中的数据

具体细节可以参考之前的核心概念的使用方法
xxx 表示我们需要得到的属性

获取state内容

  1. 直接通过模块名访问$store.state.模块名.xxx
  2. 通过 mapState 映射:
    1. 默认根级别的映射 mapState([ 'xxx' ])
    2. 子模块的映射 :mapState('模块名', ['xxx']) - 需要开启命名空间 namespaced:true
      image.png

获取getters中的内容

image.png

获取mutations中的内容

image.png

获取actions中的内容

image.png

实现案例

获取请求, 然后存入vuex ,最后渲染

image.png

  1. 首先创建模块modules/cart.js, 然后构建框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { mapActions ,mapGetters, mapState, mapMutations } from "vuex"
// 导出
export default {
namespaced: true,
state() {
return {
//关于购物车的一个数据[{ } ,{ }]
list: []
}
},
mutations: {
},
actions: {
},
getters: {

}
}

通常都是通过一个对象的形式来进行构建数据的

  1. 注册模块到index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue

import Vuex from 'vuex'

//1. 导入模块
import cart from './modules/cart'
Vue.use(Vuex)

export default new Vuex.Store({
modules: {
cart
}
})

  1. 准备需要的actionsmutations 代码( 因为我们获取数据是通过异步的方式, 所以在actions里 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default {
namespaced: true,
state() {
return {
//关于购物车的一个数据[{ } ,{ }]
list: []
}
},
mutations: {
//更新List中的数据
updateList(state, newList){
state.list = newList
}
},
actions: {
// 异步更新数据
async getList(context){
const res = await axios.get('http://localhost:3000/cart')
// 调用updateList 存入数据
context.commit('updateList', res.data)
}
},
getters: {}
}

仅仅这样在模块中写还无法将数据加载到组件中, 需要在App.vue 组件中调用才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
//1. 导入组件和模块
import { mapActions , mapGetters, mapState, mapMutations} from 'vuex'
import cart from './store/modules/cart'
export default {
name: 'App',
components: {
//组件注册
CartHeader,
CartFooter,
CartItem
},
//得到使用vuex中存入的数据
computed: {
...mapState('cart', ['list'])
},
// 通过使用created将数据加载进去

created() {
// this.$store.dispatch('cart/getList')
//通过this调用
this.getList()
},
methods: {
...mapActions('cart',['getList'])
}
}
</script>
  1. 动态渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="app-container">
<!-- Header 区域 -->
<cart-header></cart-header>

<!-- 商品 Item 项组件
通过mapState得到数据, 然后进行v-for 渲染, 最后通过:item="item" 将对象传入
上述就是父传子
-->
<cart-item v-for="(item, index) in list" :key="item.id" :item="item"></cart-item>

<!-- Foote 区域 -->
<cart-footer></cart-footer>
</div>
</template>

子组件通过props数据, 然后进行渲染即可

数据更新

  1. 基于state 来使用getters从而实现 数据的更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import axios from "axios"
import { mapActions ,mapGetters, mapState, mapMutations } from "vuex"
// 导出
export default {
namespaced: true,
state() {
return {
//关于购物车的一个数据[{ } ,{ }]
list: []
}
},
mutations: {
//更新List中的数据
updateList(state, newList){
state.list = newList
},
// 对页面作数据更新
add(state, id) {
const goods = state.list.find((item) => item.id == id)
goods.count += 1
},
// 对页面作数据更新
del(state, id) {
const goods = state.list.find((item) => item.id == id)
goods.count -= 1
}

},
actions: {
// 异步更新数据
async getList(context){
const res = await axios.get('http://localhost:3000/cart')
// console.log(res.data)
context.commit('updateList', res.data)
},
// 新增商品数量
async addItem(context, item) {
const newCount = item.count + 1
const res = await axios.patch(`http://localhost:3000/cart/${item.id}`, {
count: newCount
})
console.log(res.data)
context.commit('add', item.id)

},
// 减少商品数量
async delItem(context, item) {
const newCount = item.count - 1
if(newCount < 1) return
const res = await axios.patch(`http://localhost:3000/cart/${item.id}`, {
count: newCount
})
context.commit('del', item.id)
}
},
getters: {
totalCount(state) {
return state.list.reduce((sum,item ) => sum + item.count, 0);

},
totalMoney(state) {
return state.list.reduce((sum,item ) => sum += item.price*item.count, 0);
}
}
}
  1. 然后通过使用getter实现总数的计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<!-- item页面的update数据 -->
<button class="btn btn-light" @click="delItem(item)">-</button>
<span class="count">{{ item.count }}</span>
<button class="btn btn-light" @click="addItem(item)">+</button>

<!-- 下面是footer页面的和上面不同 -->
<div class="footer-container">
<!-- 中间的合计 -->
<div>
<span>共 {{totalCount}} 件商品,合计:</span>
<span class="price">¥{{ totalMoney }}</span>
</div>
<!-- 右侧结算按钮 -->
<button class="btn btn-success btn-settle">结算</button>
</div>
</template>

<script>
import { mapActions , mapGetters, mapState, mapMutations} from 'vuex';
import cart from '../store/modules/cart'
export default {
name: 'CartFooter',
props: {
list: {
type: Array,
required: true //必须传
}
},
computed: {
...mapGetters('cart', ['totalCount','totalMoney'])
}


}
</script>