vue全家桶 = axios(网络请求) + vueRouter(路由单页面应用) + vueCli(脚手架) + vuex(全局数据管理) + vant移动端布局(PC端elementUI)
所有组件
间的数据共享
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwXB64bq-1664019534533)(day07.assets/image-20210323175925844.png)]
序号 | 组件关系 | 数据通信 |
---|---|---|
1 | 父子关系 | 子传父:$emit ; 父传子:props |
2 | 非父子关系 | eventBus: $on + $emit |
3 | 非父子关系 | vuex |
和我们已经学过的父子通信和兄弟通信类似,vuex也是一种组件通信解决方案
1.在现代化开发中,组件化已经成为主流开发了。
2.每一个组件的数据是独立
的, 每个组件有着自己的状态(数据)
3.在实际开发中,一个组件需要访问另一个组件中的数据是非常常见的。这时候需要进行组件通讯。
常见的组件通讯
props
单向数据流
$emit
事件通知
独立于组件
而单独存在的,所有的组件都可以把它当作一座桥梁来进行通讯。响应式
: 只要vuex中的数据变化,对应的组件会自动更新(类似于vue数据驱动)实际开发中,组件传值大多数情况下还是使用 父子组件传值
少部分情况下会用vuex. (数据需要在非常多的页面使用,比如用户头像,好几个页面都要显示那种)
不必要,尽量别用
很多个地方使用
,如果采用组件传值方式,写起来很麻烦,而且多个地方都要写
不需要多个地方使用
,如果某个数据仅仅只是在两个组件之间通讯
,优先使用props或$emit共享
(2)组件传值比较麻烦(不是父子关系)不共享
(2)组件传值比较容易(父子传值)使用步骤:
vue-cli
中整合==(如果使用vue ui创建创建,直接勾选vuex,会自动帮我们完成配置)==
vue add vuex
y
/src/store/index.js
main.js
导入并挂载到Vue
实例上state
中定义数据this.$store.state.xxx
即可取值
和改值
template
中可以不用写this
.js
文件中
store
对象即可获取属性注意:
vue-cli
创建的项目中如何整合vuex
vue add vuex
vuex
的数据定义在哪里?
state
vuex
中的数据?
this.$store.state.xxx
this
可以省略state作用: 存储公共数据
./vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//1.state作用:存储数据
state: {
user:{
name:'ikun',
age:30
}
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
<template>
<div>
<h1>首页h1>
<button>点我修改vuex数据button><br>
<button>点我发送ajax请求button>
<div>
<h2>个人信息h2>
<p>我的名字是:{{ $store.state.user.name }}p>
<p>我的年龄是:{{ $store.state.user.age }}p>
div>
<div>
<h2>图书管理信息h2>
<ul>
<li>li>
ul>
div>
div>
template>
<script>
export default {
name:'home',
}
script>
<style scoped>
p{
color:red;
}
style>
语法如下
new Vuex.store({
// 省略其他...
getters: {
// state 就是上边定义的公共数据state
计算属性名: function(state) {
return 要返回的值
}
}
})
2.使用getter中的计算属性
$store.getters.getter的名字
./vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//1.state作用:存储数据
state: {
user:{
name:'ikun',
age:30
}
},
/* 2.2.getter作用:
官方术语:从state中派生状态(使用state中的数据,计算得出一些新数据)
说人话: 相当于state的计算属性
*/
getters: {
getInfo(state){
//state:就是vuex上面的state
return `我是${state.user.name},我今年${state.user.age}岁了`
}
},
//2.
mutations: {
},
actions: {
},
modules: {
}
})
home.vue
<div>
<h2>个人信息h2>
<p>我的名字是:{{ $store.state.user.name }}p>
<p>我的年龄是:{{ $store.state.user.age }}p>
<p>自我介绍:{{ $store.getters.getInfo }}p>
div>
1.Mutation作用:更新state中的数据
this.$store.state
来修改,为什么不能这么写呢?vue tools
不会追踪数据的修改,这样不便于维护(不知道这个全局数据什么时候被修改了,再加上vuex是全局响应式的,一旦修改所有使用的地方全部修改。非常不便于维护)
2.Mutation语法如下:
分两个格式: 注册的格式,调用的格式
定义格式: 如下
new Vue.store({
// 省略其他...
mutations:{
// 每一项都是一个函数,可以声明两个形参
mutation名1:function(state [, 载荷]) {
},
mutation名2:function(state [, 载荷]) {
},
}
})
使用格式
this.$store.commit('mutation名', 载荷实参 )
./vuex/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//1.state作用:存储数据
state: {
user:{
name:'ikun',
age:30
}
},
/* 2.2.getter作用:
官方术语:从state中派生状态(使用state中的数据,计算得出一些新数据)
说人话: 相当于vuex的计算属性
*/
getters: {
getInfo(state){
//state:就是vuex上面的state
return `我是${state.user.name},我今年${state.user.age}岁了`
}
},
//3.mutations作用:修改state中的数据
mutations: {
setUser(state,newData){
state.user = newData
}
},
actions: {
},
modules: {
}
})
home.vue
export default {
name:'home',
methods: {
doClick(){
this.$store.commit('setUser', {
name:'黑马李宗盛',
age:38
})
}
},
}
问:为啥是$store.commit('mutations的名字')
而不是$store.mutations的名字()?
答:Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
问:数据不可以该在组件内部直接修改吗?
答:不能。虽然语法上不报错,也有响应式的特点。但是不推荐。特别是在严格模式下会报错。若将vue创建 store 的时候传入 strict: true, 开启严格模式,那么任何修改state的操作,只要不经过 mutation的函数,vue就会报错
问:可以传递多个数据吗?
答:参数只能有一个:下面的写法是不对的:
this.$store.commit('setUser', name, age) // age这个参数将无法被接收到
如果希望传递复杂的数据,第二个参数可以是对象,例如下面的写法
this.$store.commit('setUser', { name, age} )
问:等价写法 this.$store.comit({type: 'mutations的名字'})
官网文档:vuex辅助函数-mapState
1.mapState作用: 将vuex中的数据映射
到组件的计算属性
中
this.$store.state.属性名
,单词太常,写起来很麻烦。this.属性名
来访问vuex中的数据2.mapState语法
2.1 在要使用的组件中导入mapState辅助函数: import { mapState } from 'vuex'
2.2 在这个组件的computeds中来映射计算属性
export default {
name: "my",
computed:{
//计算属性
//2.将vuex中的user映射成计算属性(与下面代码完全等价)
...mapState(['user'])
//计算属性:本质还是访问vuex中的数据
// user(){
// return this.$store.state.user
// }
}
};
…mapState( [‘属性名a’ , ‘属性名b’] )
属性名(){ return this.$store.state.属性名 }
my.vue
<template>
<div>
<h1>我的h1>
<div>
<h2>个人信息h2>
<p>我的名字是:{{ $store.state.user.name }}p>
<p>我的年龄是:{{ user.age }}p>
div>
<div>
<h2>图书管理信息h2>
<ul>
<li>li>
ul>
div>
div>
template>
<script>
//导入辅助函数mapState
import { mapState } from 'vuex'
export default {
name: "my",
computed:{
//计算属性
//2.将vuex中的user映射成计算属性(与下面代码完全等价)
...mapState(['user'])
//计算属性:本质还是访问vuex中的数据
// user(){
// return this.$store.state.user
// }
}
};
script>
<style scoped>
p {
color: green;
}
style>
为什么要有actions, 假如你有一个数据,需要通过ajax请求来获取。然后你想存入vuex,应该怎么做?
方案一:
1.在组件的created钩子中发送ajax请求
2.服务器响应数据之后,调用$store.commit()提交给mutations更新(手动挡)
方案二:
1.直接在actions中发送ajax请求
2.actions会自动帮你把数据提交到mutations更新(自动挡)
你会选择手动更新?还是自动更新呢?
一定要记住
:只有你的ajax数据需要存入vuex,才需要在actions中发送ajax请求。 如果不想存入vuex,还是在组件的created钩子中发送。(vuex说:你又不用我,你想干啥与我何干?)
图书接口:https://www.fastmock.site/mock/37d3b9f13a48d528a9339fbed1b81bd5/book/api/books
actions语法如下
这个地方的载荷一般为ajax接口需要的参数,如果没有就不用传
new Vuex.store({
// 省略其他...
actions: {
// context对象会自动传入,它与store实例具有相同的方法和属性
action的名字: function(context, 载荷) {
// 1. 发异步请求, 请求数据
// 2. commit调用mutation来修改数据
// context.commit('mutation名', 载荷)
}
}
})
this.$store.dispatch('actions的名字', 参数)
来调用action1.给state添加books数组
2.在vuex的index.js页面导入axios
3.给vuex的mutations添加修改books的方法
4.给vuex添加actions
./vuex/index.js
import Vue from "vue";
import Vuex from "vuex";
//导入axios
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
//1.state作用:存储数据
state: {
user: {
name: "ikun",
age: 30
},
books:[]
},
/* 2.2.getter作用:
官方术语:从state中派生状态(使用state中的数据,计算得出一些新数据)
说人话: 相当于vuex的计算属性
*/
getters: {
getInfo(state) {
//state:就是vuex上面的state
return `我是${state.user.name},我今年${state.user.age}岁了`;
}
},
//3.mutations作用:修改state中的数据
mutations: {
//更新user
setUser(state, newData) {
state.user = newData;
},
//更新books
setBooks(state, newData) {
state.books = newData;
},
},
//4.action作用: 异步更新数据
actions: {
getBooks(context) {
// 1. 发异步请求
axios({
url:
"https://www.fastmock.site/mock/37d3b9f13a48d528a9339fbed1b81bd5/book/api/books",
method: "GET"
}).then(res => {
console.log(res);
// 2. 调用mutation
context.commit("setBooks", res.data.data);
});
}
},
modules: {}
});
<template>
<div>
<h1>首页h1>
<button @click="doClick">点我修改vuex数据button><br>
<button @click="getBookList">点我发送ajax请求button>
<div>
<h2>个人信息h2>
<p>我的名字是:{{ $store.state.user.name }}p>
<p>我的年龄是:{{ $store.state.user.age }}p>
<p>自我介绍:{{ $store.getters.getInfo }}p>
div>
<div>
<h2>图书管理信息h2>
<ul>
<li v-for="(item,index) in $store.state.books" :key="index">
<span>书名:{{ item.name }}span> --
<span>价格:{{ item.price }}span>
li>
ul>
div>
div>
template>
<script>
export default {
name:'home',
methods: {
doClick(){
//调用mutaions
this.$store.commit('setUser', {
name:'黑马李宗盛',
age:38
})
},
getBookList(){
//调用actions
this.$store.dispatch('getBooks')
}
},
}
script>
<style scoped>
p{
color:red;
}
style>
为什么要有vuex? 假设你的项目非常的复杂,分为四个大模块:首页、个人中心、订单列表、设置中心
每一个模块有10个数据需要使用vuex,那么你的vuex的state中就需要声明40个属性。(是不是非常麻烦呢?)
- 当vuex中需要存储的数据很多的时候,就需要使用moudles进行模块化处理
- 一般实际开发中moudles使用不多哈。 只有那种特别复杂,庞大的项目才可能用到
export default new Vuex.Store({
// state: 用来保存所有的公共数据
state: {},
getters: {},
mutations: {},
actions: {},
modules: {
模块名1: {
// namespaced为true,则在使用mutations时,就必须要加上模块名
namespaced: true,
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
},
模块名2: {
// namespaced不写,默认为false,则在使用mutations时,不需要加模块名
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
}
}
})
也可以把每一个modules单独写在一个js文件中,然后导入进来
|--store /
|------- index.js # 引入模块
|------- modules
|-------------- / mod1.js # 模块1
|-------------- / mod2.js # 模块2
访问模块中的数据,要加上模块名
获取数据项: {{$store.state.模块名.数据项名}}
获取getters: {{$store.getters['模块名/getters名']}}
访问模块中的mutations/actions:
$store.commit('mutations名') // namespaced为false
$store.commit('模块名/mutations名') // namespaced为true
import Vue from "vue";
import Vuex from "vuex";
//导入axios
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
//默认的一套语法:放整个项目任何模块都需要使用的公共数据(全局的,例如用户信息)
state: {},
getters: {},
mutations: {},
actions: {},
//5.module作用:这里放一些功能模块的数据(例如订单列表、设置中心等)
modules: {
//模块名:可以用页面名字
home: {
state: {
user: {
name: "ikun",
age: 30
}
},
/* 2.2.getter作用:
官方术语:从state中派生状态(使用state中的数据,计算得出一些新数据)
说人话: 相当于vuex的计算属性
*/
getters: {
getInfo(state) {
//state:就是vuex上面的state
return `我是${state.user.name},我今年${state.user.age}岁了`;
}
},
//3.mutations作用:修改state中的数据
mutations: {
//更新user
setUser(state, newData) {
state.user = newData;
}
},
//4.action作用: 异步更新数据
actions: {},
modules: {}
},
//模块名:可以用页面名字
my: {
state: {
books: []
},
/* 2.2.getter*/
getters: {
},
//3.mutations
mutations: {
//更新books
setBooks(state, newData) {
state.books = newData;
}
},
//4.action作用
actions: {
getBooks(context) {
// 1. 发异步请求
axios({
url:
"https://www.fastmock.site/mock/37d3b9f13a48d528a9339fbed1b81bd5/book/api/books",
method: "GET"
}).then(res => {
console.log(res);
// 2. 调用mutation
context.commit("setBooks", res.data.data);
});
}
},
//5.module作用:模块化处理vuex数据
modules: {}
}
}
});
页面使用moudules的一些变化
home.vue
<template>
<div>
<h1>首页h1>
<button @click="doClick">点我修改vuex数据button><br>
<button @click="getBookList">点我发送ajax请求button>
<div>
<h2>个人信息h2>
<p>我的名字是:{{ $store.state.home.user.name }}p>
<p>我的年龄是:{{ $store.state.home.user.age }}p>
<p>自我介绍:{{ $store.getters['getInfo'] }}p>
div>
<div>
<h2>图书管理信息h2>
<ul>
<li v-for="(item,index) in $store.state.my.books" :key="index">
<span>书名:{{ item.name }}span> --
<span>价格:{{ item.price }}span>
li>
ul>
div>
div>
template>
<script>
export default {
name:'home',
methods: {
doClick(){
//调用mutaions
this.$store.commit('setUser', {
name:'黑马李宗盛',
age:38
})
},
getBookList(){
//调用actions
this.$store.dispatch('getBooks')
}
},
}
script>
<style scoped>
p{
color:red;
}
style>
mapState只是起到简化代码作用,实际开发中这是可选的。
用 或 不用,对我们使用vuex没有影响
掌握mapState的用法,将state中的变量映射到当前的组件中使用;
当访问某个数据项嵌套太深了,能不能优化一下访问的方式?
用mapState把公共数据(vuex.store) 映射 到本组件内部的计算属性中
// 1. 导入辅助函数mapState,它是在vuex中定义的一个工具函数。
// es6 按需导入 import { mapState } from 'vuex'
import { mapState } from 'vuex'
computed: {
// 说明1: ...对象 是把对象展开,合并到computed
// 说明2: mapState是一个函数
// ['数据项1', '数据项2']
...mapState(['xxx']),
...mapState({'新名字': 'xxx'})
}
this.xxx
{{xxx}}
// 步骤
// 1. 导入辅助函数mapState,它是在vuex中定义的一个工具函数。
// es6 按需导入 import { mapState } from 'vuex'
import { mapState } from 'vuex'
// 2. 在computed中使用 ...mapState(['books'])
// const res = mapState(['books'])
// res的结果是一个对象: { books: function() {}}
// console.log('mapState', res)
export default {
computed: {
c1 () {
return 'c1'
},
// books: function() {}
// ..res: 把res这个对象合并到computed对象中
// ...res
...mapState(['books'])
}
}
</script>
掌握mapState对数据重命名的用法。
vuex中的数据与本组件内的数据名相同
...mapState({'新名字': 'xxx'})
直接使用: this.$store.state.xxx;
map辅助函数:
computed: {
...mapState(['xxx']),
...mapState({'新名字': 'xxx'})
}
直接使用: this.$store.state.模块名.xxx;
map辅助函数:
computed: {
...mapState('模块名', ['xxx']),
...mapState('模块名', {'新名字': 'xxx'})
}
直接使用:this.$store.getters.xxx
map辅助函数:
computed: {
...mapGetters(['xxx']),
...mapGetters({'新名字': 'xxx'})
}
直接使用: this.$store.getters.模块名.xxx
map辅助函数:
computed: {
...mapGetters('模块名', ['xxx']),
...mapGetters('模块名',{'新名字': 'xxx'})
}
直接使用:this.$store.commit('mutation名', 参数)
map辅助函数:
methods: {
...mapMutations(['mutation名']),
...mapMutations({'新名字': 'mutation名'})
}
直接使用: this.$store.commit('模块名/mutation名', 参数)
map辅助函数:
methods: {
...mapMutations('模块名', ['xxx']),
...mapMutations('模块名',{'新名字': 'xxx'})
}
直接使用:this.$store.dispatch('action名', 参数)
map辅助函数:
methods: {
...mapActions(['actions名']),
...mapActions({'新名字': 'actions名'})
}
直接使用: this.$store.dispatch('模块名/action名', 参数)
map辅助函数:
methods: {
...mapActions('模块名', ['xxx']),
...mapActions('模块名',{'新名字': 'xxx'})
}
我们之前写过的TodoList和购物车,都可以使用vuex重新再也一次哟
而且使用vuex之后,可以省去很多 父子传值组件 呢
核心思路: 把输入框文本、数组全部存入vuex.
(1)所有的组件都需要再传值了,直接操作vuex数据
(2)actions没有网络请求用不上
(3)modules需要非常庞大的项目支撑,用不上
(4)vuex语法使用最多的是state与mutaions哈
这是之前父子组件传值方式
使用vuex
./store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//1.state作用:存储数据
state: {
list: [
{ name: "吃饭", isDone: true },
{ name: "睡觉", isDone: false },
{ name: "打豆豆", isDone: true }
]
},
/* 2.2.getter作用:
官方术语:从state中派生状态(使用state中的数据,计算得出一些新数据)
说人话: 相当于vuex的计算属性
*/
getters: {},
//3.mutations作用:修改state中的数据
mutations: {
//数组添加
setList(state,newList){
state.list = newList
},
//数组删除
delList(state,index){
state.list.splice(index,1)
},
},
//4.action作用: 异步更新数据
actions: {},
//5.module作用:模块化处理vuex数据
modules: {}
});
TodoList.vue(注意文件路径)
<template>
<div class="todoapp">
<TodoHeader >TodoHeader>
<TodoMain >TodoMain>
<TodoFooter>TodoFooter>
div>
template>
<script>
//1.导入css样式
import "../assets/css/base.css"
import "../assets/css/index.css"
//2.导入局部组件
import TodoHeader from "../components/TodoHeader.vue"
import TodoMain from "../components/TodoMain.vue"
import TodoFooter from "../components/TodoFooter.vue"
export default {
name:'TodoList',
//注册组件
components: {
TodoHeader,
TodoMain,
TodoFooter
},
computed: {
list(){
return this.$store.state.list
}
},
}
script>
<style scoped>style>
TodoMain.vue
<template>
<ul class="todo-list">
<li
v-for="(item, index) in list"
:key="index"
:class="{ completed: item.isDone }"
>
<div class="view">
<input v-model="item.isDone" class="toggle" type="checkbox" />
<label>{{ item.name }}label>
<button @click="doClick(index)" class="destroy">button>
div>
li>
ul>
template>
<script>
export default {
computed: {
list() {
return this.$store.state.list;
}
},
methods:{
doClick(index){
this.$store.commit('delList',index)
}
}
};
script>
TodoHeader.vue
<template>
<header class="header">
<h1>todosh1>
<input v-model="isAll" id="toggle-all" class="toggle-all" type="checkbox" />
<label for="toggle-all">label>
<input
@keydown.enter="add"
v-model.trim="taskname"
class="new-todo"
placeholder="输入任务名称-回车确认"
autofocus
/>
header>
template>
<script>
export default {
data() {
return {
taskname: ""
};
},
methods: {
add() {
//(1)非空判断
if (this.taskname == "") {
return alert("请输入任务名");
}
//(2)更新vuex
this.$store.commit("setList", [
...this.list,
{ name: this.taskname, isDone: false }
]);
//(3)清空输入框
this.taskname = "";
}
},
computed: {
list() {
return this.$store.state.list;
},
isAll: {
set(val) {
// 将所有单选框设置为 val
this.list.forEach(item => (item.isDone = val));
},
get() {
// 计算是否全部选中
return this.list.every(item => item.isDone);
}
}
}
};
script>
TodoFooter.vue
<template>
<footer class="footer">
<span class="todo-count">剩余<strong>{{ list.length }}strong>span>
<button @click="doClear" class="clear-completed" >清除已完成button>
footer>
template>
<script>
export default {
data() {
return {
}
},
computed:{
list(){
return this.$store.state.list
}
},
methods:{
doClear(){
this.$store.commit('setList',this.list.filter(item => !item.isDone))
}
}
}
script>