Vuex
1.Vuex 介绍
Vuex
是一个专为Vue.js 应用程序开发的
状态管理模式。它采用集中式
存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测
的方式发生变化。Vuex 也集成到 Vue 的官方调试工具,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
###1. 为什么要使用 Vuex?
为了实现复杂层级的组件间数据传递、共享
- Vuex 是一个专为 Vue.js 应用程序开发的集中式的状态(数据)管理的工具。
- Vuex 最主要去做的事情就是 实现 组件与组件之间的数据传递。
###2. Vue**基础学习的组件通信有哪些?
- 父传子 : props
- 子传父 : $emit
- 兄弟组件之间的数据传递 : eventBus 事件中心
3.vuex 核心内容
state
: state 是一个对象,所有 Vuex 的数据都会保存在 state 中。mutations
: 所有要放到 state 的数据,不能直接放进去,必须通过 mutations 来放进去actions
: 专门用来处理异步操作的东西。
2.vuex 基础使用
- 下包
yarn add vuex
- 导入到 main.js
improt Vuex from 'vuex';
- 全局注册
Vue.use(Vuex); //调用了 vuex 中的 一个 install() 方法
- 创建 Vuex 实例对象
const Vuex = new Vuex.store({});
- 将 store 对象挂载到 vue 对象上
new Vue({
render: (h) => h("#app"),
store,
});
3. state
state 是放置所有公共状态的属性,如果你有一个公共状态数据 , 你只需要定义在 state 对象中
// 初始化vuex对象
const store = new Vuex.Store({
state: {
// 管理数据
count: 0,
name: "张三",
age: 18,
},
});
调用 state 里的数据的三种方法:
- 方式一: 通过 state 属性属性获取 count
<template>
<p>state的数据:{{ $store.state.count }}</p>
</template>
- 方式二 :state 属性定义在计算属性中,自动将指定数据挂载到 computed 上
<template>
<p>state的数据:{{ count }}</p>
</template>
<script>
export default{
computed: {
count() {
return this.$store.state.count,
},
},
}
</script>
- 方法三:使用辅助函数 - mapState
<template>
<div>state的数据:{{ count }}</div>
<div>state的数据:{{ name }}</div>
<div>state的数据:{{ age }}</div>
</template>
<script>
//1.引入 mapState 函数
import { mapState } from "vuex";
export default{
computed: {
// 2利用延展运算符将导出的状态映射给计算属性
...mapState(["count", "name", "age"]),
},
}
</script>
4. mutations
mutations 是一个对象,对象中存放修改 state 的方法
定义mutations
// 初始化vuex对象
const store = new Vuex.Store({
state: {
// 管理数据
count: 0,
name: "张三",
age: 18,
},
mutations: {
//参数一:必须是 state ,不用手动传
//参数二:调用时传入的参数payload(载荷)
addCount(state, count) {
state.count += count;
},
},
});
调用 mutations 里的 方法 的两种方法:
- 方式一:原始形式-$store
<template>
<div>
<button @click="addCount">count加+</button>
</div>
</template>
<script>
export default {
// 方法一原始形式-$store
methods: {
addCount() {
// 参数一:方法名 参数二 参数
this.$store.commit("addCount", 10);
},
},
};
</script>
- 方法二:辅助函数 - mapMutations
<template>
<div>
<button @click="addCount(100)">count加100+</button>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
// 方法一原始形式-$store
methods: {
addCount() {
// 参数一:方法名 参数二 参数
...mapMutations(["addCount"]),
},
},
};
</script>
5. actions
什么时候使用
**actions
**?
- 当我们需要进行异步操作的时候(比如发送请求),我们就应该创建一个 action 来进行异步操作
- 因为 mutations 不支持异步操作
actions 的使用注意:不能直接修改 state 的数据,必须通过 mutations 方法间接的修改 state 的数据
访问形式有以下两种:
定义actions
// 初始化vuex对象
const store = new Vuex.Store({
state: {
...
},
mutations: {
...
},
actions: {
getAsyncCount(context) {
// cintext 对象-> $store
// action 不能做直接操作
//可以通过 context.state 获取状态 也可以通过context.commit 来提交mutations, 也可以 context.diapatch调用其他的action
setTimeout(() => {
context.commit("addCount", 123);
}, 1000);
},
},
});
- 原始调用 - $store
<template>
<div>
<button @click="addAsyncCount">异步原始调用+100</button>
</div>
</template>
<script>
export default {
methods: {
addAsyncCount() {
//actions 方法一 原始调用 - $store
this.$store.dispatch("getAsyncCount");
},
},
};
</script>
- 辅助函数 -mapActions
<template>
<div>
<button @click="getAsyncCount">异步辅助函数 -mapActions+111</button>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
methods: {
addAsyncCount() {
// actions 方法二
...mapActions(["getAsyncCount"]),
},
},
};
</script>
6. getters
在什么时候使用 getters ?
- 当我们有一个 state 中的数据经常需要使用,我们就可以为这个数据建立一个 getters
- 或者我们可以把多个数据整合起来制作成一个 getters ,提供访问
- 再者也可能我们需要得到数据需要进行计算,我们可以创建一个 getters 进行计算,并得到最后的结果
- getters 可以作为提供快速访问数据的一种形式,也可以作为类似计算属性的方式来使用
使用方式有以下两种:
定义 getters
// 初始化vuex对象
const store = new Vuex.Store({
state: {
...
list: [1,2,3,4,5,6,7,8,9,10]
},
mutations: {
...
},
actions: {
...
},
getters:{
// getters函数的第一个参数是 state
// 必须要有返回值
filterList: state => state.list.filter(item => item > 5)
}
});
- 原始形式-$store
<template>
<div>{{ $store.getters.filterList }}</div>
</template>
- 辅助函数 - mapGetters
<template>
<div>
{{ filterList }}
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(['filterList'])
}
};
</script>
7.模块化-Module
Vuex为什么要有模块化?
- 所有的数据都放在 state 中,所有修改数据的方法都放在 mutations ,所有的异步操作都放在 actions 里面,会导致 Vuex 的 store 对象非常臃肿,后续做维护,添加新功能都会比较费劲
- 而通过模块化,我们可以将主模块 Vuex 的 store 拆分成多个子模块,功能划分更加清晰,后续维护更新更易查找
8.模块化的简单应用
应用
定义两个模块 user 和 setting
user中管理用户的状态 token
setting中管理 应用的名称 name
// 初始化vuex对象
const store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {...},
getters:{
// ...
token: state => state.user.token,
name: state => state.setting.name,
},
modules: {
user: {
state: {
token: "12345",
},
},
setting: {
state: {
name: "Vuex实例",
},
},
},
});
调用:
- 原始方法
<template>
<div>
<div>用户token {{ $store.state.user.token }}</div>
<div>网站名称 {{ $store.state.setting.name }}</div>
</div>
</template>
- 通过mapGetters引用 访问模块数据
<template>
<div>
<div>用户token {{ token }}</div>
<div>网站名称 {{ name }}</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
// 通过mapGetters引用 访问模块数据
...mapGetters(["token", "name"]),
}
};
</script>
9. 模块化中的命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
// 初始化vuex对象
const store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {...},
getters:{
// ...
token: state => state.user.token,
name: state => state.setting.name,
},
modules: {
user: {
// 想保证内部模块高封闭性,我们可以采用namespaced来进行设置
namespaced: true,
state: {
token: "12345",
},
mutations: {
// 这里的state表示的是user的state
updateToken(state) {
state.token = 678910;
},
},
},
setting: {
state: {
name: "Vuex实例",
},
},
},
});
方案1:直接调用-带上模块的属性名路径
<template>
<div>
<button @click="test">修改token</button>
</div>
</template>
<script>
export default {
methods: {
test () {
this.$store.dispatch('user/updateToken') // 直接调用方法
}
}
};
</script>
方案2:辅助函数-带上模块的属性名路径
<template>
<div>
<button @click="test">修改token</button>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
methods: {
...mapMutations(['user/updateToken']),
test () {
this['user/updateToken']()
}
}
};
</script>
方案3: createNamespacedHelpers 创建基于某个命名空间辅助函数
<template>
<div>
<button @click="updateToken">修改token2</button>
</div>
</template>
<script>
import { mapGetters, createNamespacedHelpers } from 'vuex'
const { mapMutations } = createNamespacedHelpers('user')
export default {
methods: {
// 通过mapMutations调用
...mapMutations(["updateToken"]),
}
};
</script>
10 头条案例
难点总结
先后对比:
Actions => 封装的请求函数
Mutations => 获取到数据后,赋值给 data
State => data
Getters => computed
子模块
调用 dispatch / commit 时 一定要加模块名:
this.$store.dispatch('catagtory/getCatagtry')
对象解构
目标:更方便的读取数据,如果对象解构为您带来了烦恼,就忘掉它。
接口
获取频道列表
http://ttapi.research.itcast.cn/app/v1_0/channels
获取频道头条
http://ttapi.research.itcast.cn/app/v1_1/articles?channel_id=频道id×tamp=时间戳&with_top=1
第一步:项目初始化
通过vue-cli脚手架搭建项目
vue create toutiao #创建项目
选择 vuex / eslint(stanadard) / pre-cssprocesser (less) / router随意 => 确定
脚手架配置:
选择模式->选择 手动选择(支持更多自定义选项)
Vue CLI v4.2.3
? Please pick a preset:
default (babel, eslint)
> Manually select features
Tips:
default:默认勾选 babel、eslint,回车之后直接进入装包
manually:自定义勾选特性配置,选择完毕之后,才会进入装包
选择第 2 种:手动选择特性,支持更多自定义选项
配置对应插件
? Please pick a preset: Manually select features
? Check the features needed for your project:
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
(*) Vuex
(*) CSS Pre-processors
>(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
Tips:
分别选择:
Babel:es6 转 es5
Vuex:数据容器,存储共享数据
CSS Pre-processors:CSS 预处理器,后面会提示你选择 less、sass、stylus 等
Linter / Formatter:代码格式校验
选择 CSS 预处理器
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
> Less
Stylus
选择代码格式校验的配置方式,采用标准配置即可
? Pick a linter / formatter config:
ESLint with error prevention only
ESLint + Airbnb config
> ESLint + Standard config
ESLint + Prettier
Tips:这里选择 ESLint + Standard config
选择校验工具,并且选择什么时机下触发代码校验工具
? Pick additional lint features:
>(*) Lint on save
( ) Lint and fix on commit
- Lint on save:每当保存文件的时候
- Lint and fix on commit:每当执行 git commit 提交的时候
选择工具配置文件保存位置
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
In package.json
Tips:Babel、ESLint 等工具的一些额外配置信息写入位置:
- In dedicated config files:分别保存到单独的配置文件
- In package.json:保存到 package.json 文件中
这里建议选择第 1 个,保存到单独的配置文件,这样方便我们做自定义配置
选择是否保存模板
? Save this as a preset for future projects? (y/N) N
Tips:这里里是问你是否需要将刚才选择的一系列配置保存起来,然后它可以帮你记住上面的一系列选择,以便下次直接重用
这里根据自己需要输入 y 或者 n,我这里输入 n 不需要
安装 axios
yarn add axios
在main.js中引入样式(该样式在资源/vuex样式中,拷贝到styles目录下)
import './styles/index.css'
拷贝图片资源到assets目录下(在资源/vuex样式目录下的图片)
封装组件并引入
components/category.vue
<template>
<ul class="catagtory">
<li class='select'>开发者资讯</li>
<li>ios</li>
<li>c++</li>
<li>android</li>
<li>css</li>
<li>数据库</li>
<li>区块链</li>
<li>go</li>
<li>产品</li>
<li>后端</li>
<li>linux</li>
<li>人工智能</li>
<li>php</li>
<li>javascript</li>
<li>架构</li>
<li>前端</li>
<li>python</li>
<li>java</li>
<li>算法</li>
<li>面试</li>
<li>科技动态</li>
<li>js</li>
<li>设计</li>
<li>数码产品</li>
<li>html</li>
<li>软件测试</li>
<li>测试开发</li>
</ul>
</template>
components/news.vue
<template>
<div class="list">
<div class="article_item">
<h3 class="van-ellipsis">python数据预处理 :数据标准化</h3>
<div class="img_box">
<img src="@/assets/back.jpg"
class="w100" />
</div>
<!---->
<div class="info_box">
<span>13552285417</span>
<span>0评论</span>
<span>2018-11-29T17:02:09</span>
</div>
</div>
</div>
</template>
App.vue
<template>
<!-- app.vue是根组件 -->
<div id="app">
<category />
<news />
</div>
</template>
<script>
import category from "./components/category";
import news from "./components/news";
export default {
components: {
category,
news,
},
};
</script>
第二步:设计 Vuex 子模块
在store目录下新建目录modules, 新建 category.js 和 news.js
模块结构
export default {
namespaced: true,
state: {},
mutations: {},
actions: {}
}
在 store/index.js 中引入定义的两个模块
import category from './modules/category'
import news from './modules/news'
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
category,
news
}
})
第三步:分类组件 - 渲染
- 分类模块的 store 中定义数据
- 分类组件初始化时调用 action 获取数据
- 分类模块的 store 中定义 actions、mutations
- 全局模块定义 getters 以方便获取分类数据
- 模板中使用 v-for 进行渲染
store/modules/category.js
import axios from 'axios'
// 第三步:分类模块 - 渲染
// 3.1 store 中定义数据
export default {
namespaced: true,
state: {
categoryDatas: []
},
mutations: {
updateCategoryDatas(state, categoryDatas) {
// 3.5 将 action 传过来的数据设置到 state 中
state.categoryDatas = categoryDatas
}
},
actions: {
async getCategoryDatas(context) {
// console.log('getCategoryDatas 触发了')
// 3.3 使用 axios 发送请求 获取数据
let result = await axios.get('http://ttapi.research.itcast.cn/app/v1_0/channels')
// 3.4 调用 mutations 中的方法设置获取的数据
// console.log(result)
context.commit('updateCategoryDatas', result.data.data.channels)
}
},
}
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import category from './modules/category'
import news from './modules/news'
// 第二步: 设计 Vuex 子模块
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
category,
news
},
getters: {
// 3.6 全局模块定义 categoryDatas 以便组件中获取数据
categoryDatas: state => state.category.categoryDatas
}
})
components/category.vue
<template>
<ul class="catagtory">
<li
:class="{ select: item.id == currentId }"
v-for="item in categoryDatas"
:key="item.id"
>
{{ item.name }}
</li>
</ul>
</template>
<script>
// import { mapGetters, mapActions } from 'vuex'
import { mapGetters, createNamespacedHelpers } from "vuex";
let { mapActions } = createNamespacedHelpers("category");
export default {
created() {
// 3.2 在分类组件初始化时发送请求(调用 action 的方法)
// 如果只有一个 action 时不需要使用辅助函数导入
// this.$store.dispatch('category/getCategoryDatas')
// this['category/getCategoryDatas']()
this.getCategoryDatas();
},
computed: {
...mapGetters(["categoryDatas"]),
},
methods: {
// ...mapActions(['category/getCategoryDatas'])
...mapActions(["getCategoryDatas"])
},
};
</script>
第四步:分类组件 - 点击切换类样式
- 分类模块的 store 中定义当前分类 id 数据
- 全局模块定义 getters 以方便获取当前分类 id 数据
- 模板中使用 :class 动态绑定类名,实现高亮
- 分类模块的 store 中定义 mutations
- 获取分类数据时,调用 mutation 设置默认高亮分类 id
- 绑定点击事件,点击时触发 mutation 修改当前分类 id
store/modules/category.js
import axios from 'axios'
// 第三步:分类模块 - 渲染
// 3.1 store 中定义数据
// 4.1 store 中定义当前分类 id
export default {
namespaced: true,
state: {
categoryDatas: [],
currentId: ''
},
mutations: {
updateCategoryDatas(state, categoryDatas) {
// 3.5 将 action 传过来的数据设置到 state 中
state.categoryDatas = categoryDatas
},
// 4.4 分类模块的 store 中定义 mutations
updateCurrentId(state, id) {
state.currentId = id
}
},
actions: {
async getCategoryDatas(context) {
// console.log('getCategoryDatas 触发了')
// 3.3 使用 axios 发送请求 获取数据
let result = await axios.get('http://ttapi.research.itcast.cn/app/v1_0/channels')
// 3.4 调用 mutations 中的方法设置获取的数据
// console.log(result)
context.commit('updateCategoryDatas', result.data.data.channels)
// 4.5 获取分类数据时,调用 mutation 设置默认高亮分类 id
context.commit('updateCurrentId', result.data.data.channels[0].id)
}
},
}
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import category from './modules/category'
import news from './modules/news'
// 第二步: 设计 Vuex 子模块
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
category,
news
},
getters: {
// 3.6 全局模块定义 categoryDatas 以便组件中获取数据
categoryDatas: state => state.category.categoryDatas,
// 4.2 全局模块定义 currentId
currentId: state => state.category.currentId
}
})
components/category.vue
<template>
<ul class="catagtory">
<!-- $store.commit('category/updateCurrentId', item.id) -->
<!--
4.3 模板中使用 :class 动态绑定类名,实现高亮
4.6 绑定点击事件,点击时触发 mutation 修改当前分类 id
-->
<li
@click="updateCurrentId(item.id)"
:class="{ select: item.id == currentId }"
v-for="item in categoryDatas"
:key="item.id"
>
{{ item.name }}
</li>
</ul>
</template>
<script>
// import { mapGetters, mapActions } from 'vuex'
import { mapGetters, createNamespacedHelpers } from "vuex";
let { mapActions, mapMutations } = createNamespacedHelpers("category");
export default {
created() {
// 3.2 在分类组件初始化时发送请求(调用 action 的方法)
// 如果只有一个 action 时不需要使用辅助函数导入
// this.$store.dispatch('category/getCategoryDatas')
// this['category/getCategoryDatas']()
this.getCategoryDatas();
},
computed: {
...mapGetters(["categoryDatas", "currentId"]),
},
methods: {
// ...mapActions(['category/getCategoryDatas'])
...mapActions(["getCategoryDatas"]),
...mapMutations(["updateCurrentId"]),
},
};
</script>
第五步:新闻组件 - 渲染
- 新闻模块的 store 中定义数据
- 在新闻组件中监听当前分类 id 的变化,改变时触发 action 获取数据
- 新闻模块的 store 中定义 actions、mutations
- 全局模块定义 getters 以方便获取新闻数据
- 模板中使用 v-for 进行渲染