父子组件、兄弟组件间的数据传递、事件触发场景比较常见,总结出以下8种通信方法。
通信分类:A. 父子组件间的通信;B. 非父子组件间的通信
props 和$emit
适用于父子组件通信,父组件用props传参给子组件;子组件触发事件传递参数,父组件进行监听。
// 父组件
<template>
<div class="parent">
<comp-a :propA="parentValue" @eventA="handleEventA"></comp-a>
</div>
</template>
<script>
import compA from '../../compA';
<script>
export default {
components: {
compA,
},
data() {
return {
parentValue: 'Hello World',
message: ''
}
},
methods: {
handleEventA(param) {
this.message = param;
}
}
</script>
// 子组件
<template>
<div id="compA">
<p>{{propA}}</p>
<button @click="changeText"></button>
</div>
</template>
<script>
export default {
props: {
propA: {
type: String,
default: '',
required: true,
},
},
data() {
return {}
},
methods: {
changeText() {
let paramA = 'ssss'
this.$emit('eventA', paramA)
}
}
</script>
$children和 $parent
适用于父子组件通信。父组件有一个$children 的子组件数组,子组件有父组件对象 $parent。获取到的元素就是vue实例,推荐使用prop和 $emit通信。
provide 和inject
适用于父子、隔代组件传递数据。如字面意思,父组件“提供”数据,子组件“注入”数据。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
注:provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
// vue2.5.0+
const Child = {
inject: {
foo: {
from: 'bar', // 标注来源
default: 'foo' // 默认值
}
}
}
ref 和refs
即通过refs获取对应定义ref值的组件实例。官网API示例:
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>
<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
EventBus
EventBus 又称为事件总线。在Vue中可以使用 EventBus
来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
来源:https://www.jianshu.com/p/4fa3bf211785 作者:易水人去丶明月如霜
使用:
- 初始化
// 两种形式
// 1. 单独的js文件,应用时需要引入
// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue()
// 2. 挂载到vue下
Vue.prototype.$eventBus = new Vue();
// 或者这么些
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$eventBus: {
get: function () {
return EventBus
}
}
})
- 事件传递
基于vue的$emit发布 和 $on监听模式,进行组件间的事件传递
var EventBus = new Vue();
this.$bus.$emit('nameOfEvent',{ ... pass some event data ...});
this.$bus.$on('nameOfEvent',($event) => {
// ...
})
- 事件关闭
this.$bus.$off('nameOfEvent'); // 关闭单个
this.$bus.$off(); // 关闭所有
Vuex状态管理
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex是响应式的。相当于将公共状态抽离在单独的对象里,并运用固定的规则进行维护和管理。首先创建仓库store,预定义state用来存储状态,通过getters获取state中的状态,提交mutation修改state值,分发action执行逻辑操作。
逻辑梳理
官网示意图:
视图==>操作==>状态==>视图
Vuex状态管理
主要组成部分
- store,状态管理器的仓库;
- state,类似于实例中的data,不可在外部直接改写,用于数据存储驱动应用的数据源;
- getters,类似于实例中的computed,提供格式化后的state数据;
- mutations,类似于methods,定义更改state的入口,不支持异步逻辑;
- actions,类似于methods,可以进行异步操作;
- module,根据模块划分状态。
应用示例
在仓库文件store.js中定义对应的状态,并声明好获取、维护状态的操作
// ********** store.js********
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
// 根据业务模块,可以分开定义状态模块
import draft from './draft';
import collaborator from './collaborator';
const state = {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
};
// 从 store 中的 state 中派生出一些状态
const getters = {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
};
// 更改 store 中的状态,通过 store.commit 调用, payload支持传参
const mutations = {
increment (state,payload) {
// 变更状态
state.count++
}
};
// 提交 mutation,可包含异步操作,通过 store.dispatch 调用
const actions = {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
};
export default new Vuex.Store({
state,
actions,
getters,
mutations,
modules: {
draft,
collaborator,
},
});
在main.js中进行状态管理器的注册
// ************main.js*********
import store from './store/store.js';
// 热重载
if (module.hot) {
// 使 action 和 mutation 成为可热重载模块
module.hot.accept(['./mutations', './modules/a'], () => {
// 获取更新后的模块
// 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
const newMutations = require('./mutations').default
const newModuleA = require('./modules/a').default
// 加载新模块
store.hotUpdate({
mutations: newMutations,
modules: {
a: newModuleA
}
})
})
}
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
});
在界面应用处进行状态的引用与维护,state、getters、mutations和actions都有两种方式调用,一种是直接调用挂载在$store下的对应模块(mutations\actions需要commit和dispatch调用),都可以由载荷的形式进行传递;另一种是直接通过mapState、mapGetters、mapMutations、mapActions引入状态作为this下的参数和方法,直接进行调用。
// *************App.vue********
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
data: {
...
},
computed: {
...mapState({
'todos': state => state.todos
}),
...mapGetters([
'doneTodos',
])
},
mounted() {
// 直接取值
console.log(this.$store.state.todos);
let specialTarget = this.$store.getters.getTodoById(2);
this.$store.commit('increment', {
amount: 10
}); // 直接提交,另一种方法是在methods中引入,直接类似函数调用
// 以对象形式分发
this.$store.dispatch({
type: 'incrementAsync',
amount: 10
})
// 以载荷形式分发
this.$store.dispatch('incrementAsync', {
amount: 10
})
},
methods: {
...mapMutations([
‘increment’
]),
...mapActions([
‘incrementAsync’
]),
},
submit() {
this.increment();
this.incrementAsync();
}
}
对应module的状态应用只是在引入的时候添加路径参数。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
// **********app.vue***
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
// 或者
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
以上,官网讲解的很详细。
localStorage和sessionStorage
前者将数据缓存到电脑本地,后者混存数据到本tab页。数据存储比较简单,容易混乱,不易维护。两者都是只能存储string类型数据,复杂类型数据可以通过JSON.stringfy方法处理后进行存储。可以与vuex结合进行数据的本地存储。
$attrs和 $listeners
$attrs: { [key: string]: string }
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
inheritAttrs:默认值true,继承所有的父组件属性(除props的特定绑定)作为普通的HTML特性应用在子组件的根元素上,如果你不希望组件的根元素继承特性设置inheritAttrs: false,但是class属性会继承
$listeners: { [key: string]: Function | Array }
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
相当于隔代组件可以直接抛出父组件监听的事件,这里隔代组件之间传递参数时需要加上v-on=" $listeners"
总结
父子组件通信:
- props和$emit
- vuex
- ref和$refs
- $children和 $parent
- provide和inject
- eventbus
- $attrs和 $liseners
兄弟组件通信
- eventBus
- vuex
隔代组件通信
- eventBus
- vuex
- $attrs
- provide和inject