文章目录
1、为什么要用 vuex ?
组件通信的类型
- 父子通信
- 跨级通信
- 兄弟通信
- 路由视图级别通信
2、通信解决方案 - 面试中可能会问到
通信核心:找到数据交互之间的桥梁。如果两者之间能直接沟通,那就直接沟通,如果不能,则找一个两者都能说得上话的人。
- props/$emit(父子通信)
- $refs/ref(父子通信)
- children/parent(父子通信)
- attrs/listeners(父子通信)
- provide/inject(父子通信、跨级通信)
- eventBus(父子通信、跨级通信、兄弟通信)
- vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)
- localStorage/sessionStorage等基于浏览器客户端的存储(父子通信、跨级通信、兄弟通信、路由视图级别通信)
2-1 props/$emit(父子通信)
父级
- 通过给子级标签添加动态属性传值
- 子集通过
props
参数接收父级数据
<template>
<div>
<h2>MyParent1</h2>
<MyChild1 :data="name" @sendData="getChildData"></MyChild1>
<p>我接收到了子级的数据-{{childData}}</p>
</div>
</template>
<script>
import MyChild1 from "@/components/MyChild1";
export default {
name: "MyParent1",
data() {
return {
name: "MyParent1-Name",
childData: ""
};
},
components: {
MyChild1
},
methods: {
getChildData(data) {
this.childData = data
}
}
};
</script>
子集
- 子集通过
$emit
方法提交给父级对应事件名传值 - 父级对应事件名触发自身方法
<template>
<div>
<h2>MyChild1</h2>
<p>我接收来自父级传入的数据:{{ data }}</p>
<button @click="sendDataToParent">把数据传递给父级</button>
</div>
</template>
<script>
export default {
name: "MyChild1",
props: ["data"],
data() {
return {
name: "MyChid1-Name"
};
},
methods: {
sendDataToParent() {
this.$emit("sendData", this.name);
}
}
};
</script>
2-2 $refs/ref(父子通信)
- 与props/emit 不同的是
- ref不需要子集事先向父级提交
- 而是父级直接拿到子组件的实例中的数据
父组件
<template>
<div>
<h2>MyParent1</h2>
<MyChild1 :data="name" ref="myChild"></MyChild1>
<button @click="getChildData">通过ref获取子集数据</button>
<p>我接收到了子级的数据-{{childData}}</p>
</div>
</template>
<script>
import MyChild1 from "@/components/MyChild1";
export default {
name: "MyParent1",
data() {
return {
name: "MyParent1-Name",
childData: ""
};
},
components: {
MyChild1
},
methods: {
getChildData() {
console.log(this.$refs.myChild) // 拿到子级的组件实例对象
this.childData = this.$refs.myChild.name;
}
}
};
</script>
子组件
<template>
<div>
<h2>MyChild1</h2>
<p>我接收来自父级传入的数据:{{ data }}</p>
</div>
</template>
<script>
export default {
name: "MyChild1",
props: ["data"],
data() {
return {
name: "MyChid1-Name"
};
},
};
</script>
2-3 children/parent(父子通信)
console.log(this.$children) // 所有子组件实例的数组
获取子组件数据就更方便了
methods: {
getChildData() {
console.log(this.$children) // 所有子组件的数组
this.childData = this.$children[0].name;
}
}
同理,子组件可通过this.$parent
获取父组件数据
methods: {
getParentData() {
console.log(this.$parent);
this.parentData = this.$parent.name;
}
},
2-4 attrs/listeners(父子通信)
attrs 获取父级在子组件标签上写的非props属性(非动态属性)
methods: {
getAttrs() {
console.log(this.$attrs); // 获取非props属性
}
},
但是不能获取事件,要获取事件可通过$listeners
获取
getListeners() {
console.log(this.$listeners); // 获取事件
},
2-5 provide/inject(父子通信、跨级通信)
组件与组件之间是隔离的,没有所谓的作用域链
所以如何实现爷爷组件和孙子组件之间的数据传递呢?
方式一:通过$parent
拿爷爷传给父组件的数据
<p>爷爷的名字-{{$parent.data}}</p>
或者直接链式获取
<p>爷爷的名字-{{$parent.$parent.name}}</p>
但是这种方法很麻烦,易出错
方式二:provide/inject 跨级通信
提供数据/注入数据
爷爷组件provide选项
provide() {
return {
pName: this.name
}
},
孙子组件inject
选项
export default {
name: "MyChidl1Child2",
inject: ["pName"]
};
命名冲突的问题解决
如果自己本身有同样的命名,就会出现命名冲突的问题
export default {
name: "MyChidl1Child2",
inject: {
parentPname: {
from: "pName"
}
}
};
2-6- localStorage/sessionStorage等基于浏览器客户端的存储(父子通信、跨级通信、兄弟通信、路由视图级别通信)
- 如果采用以上的方式进行兄弟组件数据传递的话,会一层层传递,非常繁琐,
- 所以可以利用第三方来帮助数据传递:例如localstorage
存储
methods: {
saveToLS(){
localStorage.setItem("pName", this.pName)
}
},
获取
methods: {
getLS() {
this.pName = localStorage.getItem("pName")
}
},
存在问题
- 只能直接传字符串,不能是对象
- 函数很难传递
2-7 eventBus(父子通信、跨级通信、兄弟通信)
就是一个全局对象,例如挂载到windows
下
通过Vue实例化一个eventBus对象
import Vue from "vue";
let eventBus = new Vue({})
export default eventBus;
组件引入eventBus
并注册一个事件
import eventBus from "@/eventBus";
export default {
name: "MyChild2",
data() {
return {
pName: ""
};
},
created() {
eventBus.$on("bus", data => {
this.pName = data;
});
}
};
另一个组件通过eventBus
触发这个事件,实现数据存储到Vue全局,数据传递
import eventBus from "@/eventBus"
export default {
name: "MyChidl1Child2",
inject: {
parentPname: {
from: "pName"
}
},
data() {
return {
pName: "我是兄弟组件的pName"
}
},
methods: {
saveDataToEventBus(){
eventBus.$emit("bus", this.pName)
}
},
};
3、vuex 是什么?
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式,类似 redux
vuex(父子通信、跨级通信、兄弟通信、路由视图级别通信)
这种状态管理模式包含:
- State : 状态数据源
- View : 使用状态数据源的视图
- Actions : 修改更新数据源的操作
这种模式遵循的是 单向数据流 模式
4、vuex 的工作流
- State : 存储应用状态数据(React 中的 State)
- Vue Component : 消费 State
- Actions : 提交修改 State 的动作(包括异步行为)(React 中的 action)
- Mutations : 唯一更改 State 的位置(React 中的 Reducer)
5、安装 vuex
npm i vuex
// or
yarn add vuex
6、引入 vuex
通过 <script> 引入
<script src="vue.js"></script>
<script src="vuex.js"></script>
通过 <script> 方式引入,vuex 会自动安装(也就是主动调用 Vue.use(Vuex)
)
通过 import
引入
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
通过 import
方式引入,需要手动安装(手动调用 Vue.use(Vuex)
)
7、从 Store 开始
**Store
** 就是仓库,我们前面提到的 state
就存储在 store
中,同时提交动作、修改状态的方法也都由 store
提供和管理
7-1、创建一个 Store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {}
})
必须在 Vue.use(Vuex)
之后创建 store
8、核心概念
- state
- getters
- mutations
- actions
8-1、state
8-1-1、state 的创建
存储应用状态数据的对象,state
的值可以是一个对象,也可以是一个返回对象的函数,类似 vue 中组件的 data
,使用函数的方式返回对象可以避免对象引用导致的副作用问题
// let state = {
// a: 1
// }
let state = _=>({a:1})
const store = new Vuex.Store({
state
})
const store2 = new Vuex.Store({
state
})
console.log(store.state == store2.state)
store.state.a = 100;
console.log(store.state.a, store2.state.a);
- 通过
store.state
访问状态数据 state
数据与组件data
一样是被追踪的
8-1-2、在组件中使用 store
store文件设置state
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 一个用于管理维护以及提供数据修改接口的对象
// 数据最好有一些规范,保证数据操作的安全性
const store = new Vuex.Store({
// 存储基础数据的位置,类似组件中的data
state: {
title: "gaibain",
}
});
console.log(store)
export default store;
将store引入Vue实例
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: function (h) { return h(App) }
}).$mount('#app')
使用state中的数据
<template>
<div class="home">
<h2>Home</h2>
仓库中数据title: {{$store.state.title}}
</div>
</template>
<script>
export default {
name: "Home"
};
</script>
简化数据
<template>
<div class="home">
<h2>Home</h2>
title
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
title: this.$store.state.title
}
},
};
</script>
简化后出现的 问题: state
的更新并不会更新视图,因为组件中的data和store中的data并无更新关联
解决
使用 computed包裹store数据
<template>
<div class="home">
<h2>{{title}}</h2>
<div>{{content}}</div>
</div>
</template>
<script>
import store from '@/stores'
export default {
name: 'home',
computed: {
title() {
return store.state.title
},
content() {
return store.state.content
}
}
}
</script>
8-1-3、store 配置
如果每个组件在使用 store
的时候都 import
会比较繁琐,这个时候,我们通过 vuex 提供的 store
选项把 store
对象注入到 vue 的原型上
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/stores'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
配置注入后,我们就可以在组件实例中使用 this.$store
来访问 store
对象了
<template>
<div class="home">
<h2>{{title}}</h2>
<div>{{content}}</div>
</div>
</template>
<script>
// import store from '@/stores' // 可以去掉了
export default {
name: 'home',
computed: {
title() {
return this.$store.state.title
},
content() {
return this.$store.state.content
}
}
}
</script>
8-1-4、使用辅助函数 mapState
- 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。
- 为了解决这个问题,我们可以使用
mapState
辅助函数帮助我们生成计算属性,让你少按几次键 - 通常我们把
store
的state
通过mapState
函数映射到组件的computed
上
<template>
<div class="home">
<h2>Home</h2>
{{title}}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Home",
computed: mapState(["title", "user"])
};
// function mapState(keys) {
// return keys
// .map(key => {
// return {
// [key]() {
// return this.$store.state[key];
// }
// };
// })
// .reduce((prev, curr) => {
// prev[curr.key] = curr.value;
// return prev;
// }, {});
// }
</script>
通过对象方式进行映射
8-1-5、使用扩展运算符组合mapState
通过对象扩展运算符,可以把 mapState
返回的 state
属性与组件已有计算属性进行融合
<script>
import {mapState} from 'vuex'
export default {
computed: {
data() { return "test"},
...mapState(["title", "user"])
}
}
</script>
当组件中已有与 store
同名的数据名称
使用对象的格式给mapState
传参
<template>
<div class="home">
<h2>Home</h2>
{{stateTitle}} - {{title}}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Home",
computed: {
title() {
return "myTitle";
},
...mapState({
stateTitle: "title",
stateUser: "user"
})
}
};
</script>
或者通过函数返回数据展示的格式
<template>
<div class="home">
<h2>Home</h2>
{{user}}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Home",
computed: {
title() {
return "myTitle";
},
...mapState({
stateTitle: "title",
user(state) {
return state.user.name + "/" + state.user.age
}
})
}
};
</script>
9、getters
有时候我们需要从 store 中的 state 中派生出一些状态,类似组件的 data
与 computed
,store
也提供了一个 getters
对象来处理派生数据
9-1、getters 函数
与组件属性一样,我们是通过定义一个函数的形式来返回派生数据的,getters
函数接受两个参数
- 第一个参数:
state
对象 - 第二个参数:
getters
对象
9-2、通过属性访问
同样的,与组件计算属性一样,默认是通过属性的方式去访问 getters
中的数据的,这种方式与组件的计算属性一样,也是会缓存结果的
// 类似组件中的computed
getters: {
test1(state){
return `我是${state.user.name} 通过属性访问`
},
}
import { mapState } from "vuex";
export default {
name: "Home",
computed: {
title() {
return "myTitle";
},
...mapState({
stateTitle: "title",
user(state) {
return state.user.name + "/" + state.user.age
},
test1() {
return this.$store.getters.test1
}
}),
}
};
9-3、通过方法访问
我们还可以通过闭包函数的形式返回一个函数,来实现给 getters
函数传参,需要注意的是这种方式不支持结果缓存
getters: {
test1(state){
return `我是${state.user.name} 通过属性访问`
},
test2:(state) => (id) =>{
return `我是${state.user.name} 通过方法访问,传入id为:${id}`
}
}
computed: {
// ...
test2() {
return this.$store.getters.test2(1);
}
}
9-4、使用辅助函数 mapGetters
与 mapState
函数类似,通常映射到组件的 computed
上
10、mutations
-
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation(类似 redux 中的 action + reducer)
-
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
-
mutation
中的函数不要直接调用
10-1、提交
store.commit(type, payload)
// or
store.commit({
type: ...,
payload: ...
})
type
要提交的 mutation
回调函数名称,type 为固定的 key
payload
载荷:提交的额外数据,任意格式
methods: {
editTitle() {
// 直接修改会让数据比较乱
// this.$store.state.title = "修改了title"
this.$store.commit("editTitle", "newTitle");
}
}
10-2、mutation 函数
mutation
中的函数被 commit
执行的时候,接收两个参数
- 第一个参数:
state
对象 - 第二个参数:
commit
提交的payload
在 mutation
函数中,我们就可以通过 state
对象进行状态数据的修改
// 存储修改数据的方法
mutations: {
editTitle(state, newTitle) {
state.title = newTitle;
}
}
10-3、使用辅助函数 mapMutations
mapMutations
函数的使用与 mapState
和 mapGetters
类似,但是其一般是把组件的 methods
映射为 store
的 mutations
的 commit
调用
10-4、mutation 函数必须是同步的,不支持异步
-
commit
方法没有返回值,直接就是同步执行 -
提交了commit以后,组件中可能需要根据提交后的结果去做一些处理,比如显示修改信息(成功了,失败了,这个提示视图有关,那么这个逻辑代码就不方便放在store去做了
methods: {
editTitle() {
// 直接修改会让数据比较乱
// this.$store.state.title = "修改了title"
this.$store.commit("editTitle", "newTitle");
console.log(this.$store.state.title)
}
}
11、actions
action
中的函数与 mutation
中的函数类似,但是它主要用来进行异步任务的处理,然后通过提交 mutation
来修改 state
注意:action
中的函数不要直接修改 state
,数据的修改还是需要mutation
11-1、提交
store.dispatch(type, payload)
// or
store.dispatch({
type: ...,
payload: ...
})
action
任务需要通过 dispatch
方法来提交(派发),与 commit
类似
dispatch
方法有返回值,且一定返回一个 promise
对象
11-2、action 函数
action
中的函数执行的过程中也接受两个参数
- 第一个参数:
store
对象 - 第二个参数:
dispatch
提交的payload
// 存储修改数据的方法
mutations: {
editTitle(state, newTitle) {
state.title = newTitle;
}
},
actions: {
editTitle(store, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
store.commit("editTitle", payload);
resolve();
}, 1000)
});
}
}
支持异步
methods: {
async editTitle() {
await this.$store.dispatch("editTitle", "newTitle");
console.log(this.$store.state.title);
},
11-3、使用辅助函数 mapActions
与 mapMutations
函数类似,把组件的 methods
映射为 store
的 actions
的 dispatch
调用
再看vuex 的工作流
- State : 存储应用状态数据(React 中的 State)
- Vue Component : 消费 State
- Actions : 提交修改 State 的动作(包括异步行为)(React 中的 action)
- Mutations : 唯一更改 State 的位置(React 中的 Reducer)
12 Module
这个更多的是基于一种代码组织结构上的辅助
数据特别多的时候可以用这个模块化方式管理
modules