组件间状态管理流程
Vue中最核心的功能,分别是数据驱动和组件化。
使用基于组件化的开发,可以提高开发效率,带来更好的可维护性。
每个组件中都有自己的:
- state - 数据,也称为状态
- 每个组件内部都可以管理自己的内部状态
- view - 模板,也称为视图
- 每个组件都有自己的视图,把状态绑定到视图上,呈现给用户
- 当用户和视图交互的时候可能会更改状态
- 当状态发生变化后,会自动更新到视图
- actions - 方法,也称为行为
- 更改状态的行为
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `<div>{{ count }}</div>`,
// actions
methods: {
increment () {
this.count++
}
}
})
实际开发中,可能会需要在多个组件中共享状态。
状态管理
所谓的状态管理,就是通过状态集中管理和分发,解决多个组件共享状态的问题。
状态管理的组成:
- state - 驱动应用的数据源
- view - 以声明方式将 state 映射到视图
- actions - 用户和视图交互,改变状态的方式
单向数据流
图中的箭头是数据的流向,数据的流向是单向的。
- state(状态/数据)绑定到view(视图)展示给用户
- 当用户和视图交互,通过actions更改state后
- 再把更改后的state重新绑定到view
单向数据流程特别清晰,但是多个组件共享数据的时候会破坏这种简单的结构。
组件间通信方式回顾
再大多数场景下,组件并不是单独存在的。
多数情况下,组件都需要相互协作,共同构成一个复杂的业务功能。
常见的三种组件间通信方式
- 父组件给子组件传值 - Props Down
- 子组件中通过 props 接收父组件传递的数据
- 父组件中给子组件通过相应属性传值
- 子组件给父组件传值 - Event Up
- 使用自定义事件 + $emit 方式
- 不相关组件之间传值 - Event Bus
- 同样使用自定义事件,但是不能通过子组件触发emit
- 需要创建一个公共的实例( Event Bus)
- 这个实例用于作为事件中介,或者事件中心
- 调用它的 $on $emit,用来注册和触发事件
其他常见方式
可以通过以下属性获取组件中的成员,实现组件间通信:
- $root - 根组件
- 所有子组件都可以通过$root访问根实例上的成员
- 所有的子组件都可以将这个实例作为一个全局 store 来访问或使用
- 但这是不推荐的
- $parent - 父组件
- $children - 子组件
- $refs - 访问子组件实例或子元素
- 普通HTML标签 - 获取DOM
- 组件标签 - 获取组件实例
- 依赖注入:祖先组件向子孙组件传值
- 使用:
- provide - 祖先组件通过 provide 指定向下提供的数据或方法
- inject - 子孙组件通过 inject 接收祖先组件提供的数据或方法,并注入到当前实例
- 相比 props 传值的优点:
- 祖先组件不需要知道哪些后代组件使用它提供的 property
- 后代组件不需要知道被注入的 property 来自哪里
- 同 props 一样:
- property 是非响应式的
- 相比状态集中管理,使用这种方式耦合起来的组件,难以重构
- 使用:
但是这些都是不被推荐的实现方式,容易导致数据管理的混乱。
只有在项目非常小,或开发自定义组件的时候才会使用到。
如果是开发大型项目还是推荐vuex来管理状态
简易的状态管理方案
如果多个组件之间需要共享状态/数据:
-
多个视图依赖同一状态
- 使用组件间传值的方式实现很麻烦,并且很难跟踪到数据的变化。出现问题很难定位。
-
来自不同视图的行为需要变更同一状态
- 使用父子组件的方式获取状态进行修改,或者通过事件机制改变同步状态的变化。
- 但是这些方式很脆弱,通常会导致无法维护的代码。
为了解决这些问题,可以把不同组件之间的共享状态抽取出来,存储到一个全局对象中,并且将来使用的时候,要保证它是响应式的。
对象创建好之后,任何组件都可以获取或修改全局对象中的状态。
简单 store 模式 - 集中式状态管理
- 使用全局唯一的 store 对象管理状态
- 任意组件都可以导入 store 模块(store.js),使用/更改其中的状态
- 约定:
- 组件不允许直接修改 store 对象的 state 属性
- 如果想要改变 state,需要调用 action 来改变 store 中的状态
- 约定的好处:
- 可以记录 store 中所有状态的变更
- 从而实现高级的调试功能,例如:time-travel 时光旅行和历史回滚
这里使用的 store 就类似 vuex 中的仓库。
// store
export default {
// debug 方便调试
debug: true,
// 状态:用于存储数据
state: {
user: {
name: 'xiaoming',
age: 18,
sex: '男'
}
},
// action:用于用户和视图交互的时更改数据
setUserNameAction (name) {
if (this.debug) {
console.log('setUserNameAction triggered:', name)
this.state.user.name = name
}
}
}
// compa.vue
<template>
<div>
<h1>componentA</h1>
user name: {{ sharedState.user.name }}
<button @click="change">Change Info</button>
</div>
</template>
<script>
import store from './store'
export default {
data () {
return {
privateState: {},
sharedState: store.state
}
},
methods: {
change () {
store.setUserNameAction('componentA')
}
}
}
</script>
<style></style>
// compb.vue
<template>
<div>
<h1>componentB</h1>
user name: {{ sharedState.user.name }}
<button @click="change">Change Info</button>
</div>
</template>
<script>
import store from './store'
export default {
data () {
return {
privateState: {},
sharedState: store.state
}
},
methods: {
change () {
store.setUserNameAction('componentB')
}
}
}
</script>
<style></style>