VueJS组件通信

多组件共用相同的状态时,在深层嵌套组件间传递属性过于冗长,并且不能简单地在同级别的组件间传递,直接引用 父/子 实例,又或是通过事件来修改和同步多份状态副本。这样的模型是脆弱的,代码很快会变得不可维护。
理解Vue组件之间的数据传递关系到应用的健壮性和可维护性。

通信原则 Props Down Events Up

父组件向子组件传递数据使用 props
子组件向父组件传递数据使用 event

单向数据流

组件实例的作用域是孤立的
不应该在子组件的模板内直接引用父组件的数据

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——
这会让应用的数据流难以理解。

//在父组件中使用props传递给子组件
<child message="hello!"></child>

//定义子组件
Vue.component('child', {
// 声明 props
props: ['message'],
// props可以像data一样,使用this.message访问
template: '<span>{{ message }}</span>'
})

自定义事件

在父组件中使用指令 v-on:cusEvent 绑定自定义事件
在子组件中使用指令 v-on:click=”this.$emit(‘cusEvent’, args1, args2 )” 来触发父组件的事件,并传递参数

//父组件绑定自定义事件increment
<button-counter v-on:increment="incrementTotal"></button-counter>

//子组件通过this.$emit('increment')触发父组件绑定的事件
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})

全局事件总线(global event bus)

在父组件中利用vue实例的$on注册一个事件,在子组件中使用vue实例的$emit来触发父组件中的事件

var bus = new Vue()

//父组件中注册一个自定义事件 id-selected
bus.$on('id-selected', function (id) {
// ...
})

//在子组件中触发事件id-selected并传递数据id
bus.$emit('id-selected', 1)

避免使用 vm.$parent / vm.$root / vm.$children

状态管理器 Vuex

一个专门为 Vue.js 应用设计的 状态管理模型 + 库
为应用内的所有组件提供集中式存储服务,其中的规则确保状态只能按预期方式

在 vue 应用中,vuex 就充当了数据提供者的角色,vue 则只需要关注页面的展示与交互。


state (状态),驱动我们应用的真实的源;
view (视图),对应着 状态 的声明式映射;
actions (动作),用户在 视图 上的输入引起状态的更改的可能方式。

Vuex 核心概念

应用场景
页面状态数据:路由、加载状态、异步数据、开关、分页页码、表单数据

state ( store 的 data)

存放整个应用状态,作为应用的唯一数据源驱动UI视图的更新
尽量初始化详细的state数据

组件中直接访问

computed: {
count () {
return this.$store.state.count
}
}

使用工具函数 mapSate 访问
用于将独立的state数据映射到组件的 computed 属性中

import { mapState } from 'vuex'

export default {
// ...
computed: mapState({
// 箭头函数可以让代码非常简洁
count: state => state.count,
// 传入字符串 'count' 等同于 `state => state.count`
countAlias: 'count',
// 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
actions ( store 的 methods)

不改变状态,只提交(commit) mutation。
可以包含任意异步操作。

组件中直接访问

mounted(){
this.$store.dispatch('getUserData')
}

工具函数 mapActions 访问
用于将action方法映射到组件的 methods

import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 到 this.$store.dispatch('increment')
]),
...mapActions({
add: 'increment' // map this.add() to this.$store.dispatch('increment')
})
}
}
mutations ( store 的 methods)

定义了 同步 改变 state 的唯一方法
在store中,实际改变 状态(state) 的唯一方式是通过 提交(commit) 一个 mutation

组件中使用

methods:{
add(){
this.$store.commit('ADD_NUMBER',{num: 1})
}
}

使用工具函数 mapMutations
将mutation映射到组件的 methods 中

import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations({
add: 'increment' // 映射 this.add() 到 this.$store.commit('increment')
})
}
}
getters ( store 的 computed)

和计算属性功能相同,基于多个状态生成新的状态

组件中使用

computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}

工具函数 mapGetters
用于将getter属性映射到组件的computed中

import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象扩展操作符把 getter 混入到 computed 中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}

组件仍然可以具有局部状态

使用 Vuex 并不意味你应该把 所有 状态都放在 Vuex 中去管理。尽管把更多的状态放到 Vuex 管理,会让状态变化变得更加清晰和可调试,但有时也能使代码变得冗余和不直观。如果某部分状态严格属于一个单独的组件,那就只把这部分状态作为局部状态就好了。

理解:状态分为 应用级状态组件级状态
原子类组件,尽量由父组件传递状态数据使用
当组件状态不影响父组件和其它同级组件时,可做为组件内部状态
页面级的数据应该做为应用级状态管理

Redux 的作者有一句话说的不错(redux与vuex都是在flux模式上的改进):
原文:Flux libraries are like glasses: you’ll know when you need them.
译文:Flux 库就像眼镜:当你需要它们的时候你会懂的。