浅析Vue.observable()实现类似vuex的状态管理功能创建响应式全局数据
一、说明
我们习惯于用Vuex去解决状态的共享问题,但是在小项目中使用就会有增大代码体积和将代码复杂化的烦恼,所以在Vue(2.6.0)的版本中新增了一个跨组件通信方案:Vue.observable(object)。
其作用是让一个对象可响应,Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于 渲染函数 和 计算属性 内,并且会在发生改变时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}
二、如何使用
用过vuex的都知道,在我们添加了vuex依赖后,我们为首先会在src目录下新建一个store文件夹,用来管理我们的状态,没有用过的同学可以先去学习一下。
仿照那种格式,我们也在src目录下新建store文件夹,并新建子文件 index.js,state.js,mutations.js。这里,我分别介绍一下这些文件的作用,并附上代码:
1、state.js:就是一个状态,存储全局数据
import Vue from 'vue'
export default Vue.observable({
count: 0
})
2、mutations.js:用来更新state;将state.js的数据导入,用于操作
import state from './state'
export default {
addCount () {
state.count++
}
}
3、index.js:用来将state与mutations合并成一个对象,并导出;将数据与计数器方法合并为一个store并导出
import state from './state'
import mutations from './mutations'
export default {
state,
mutations
}
4、在页面中使用数据(将store 首先导入)
import store from '@/store' // 注意这点并不是vuex
// 这样就可以直接使用
store.state
store.mutations
当然也可以不分文件,直接写在同一个js里。这样其实更方便
// 文件路径 - /store/store.js
import Vue from 'vue'
export const store = Vue.observable({ count: 0 })
export const mutations = {
setCount (count) {
store.count = count
}
}
使用
<template>
<div>
<label for="bookNum">数 量</label>
<button @click="setCount(count+1)">+</button>
<span>{{count}}</span>
<button @click="setCount(count-1)">-</button>
</div>
</template>
<script>
import { store, mutations } from '../store/store' // Vue2.6新增API Observable
export default {
name: 'Add',
computed: {
count () {
return store.count
}
},
methods: {
setCount: mutations.setCount
也可以直接 ...mutations,就可以使用mutations里声明的方法了
}
}
</script>
就这么简单,轻量好用。
三、源码解读
从源码可以看出Vue.observable实际就是封装了observe
首先判断是否包含__ob__这个属性,然后实例化一个Observer对象:
由于传入的不是数组,进入walk(),在walk中遍历key,并使用defineReactive$$1创建响应式对象
Walk through all properties and convert them into getter/setters. This method should only be called when value type is Object.
遍历所有属性并将它们转换为getter/setter。仅当值类型为Object时才应调用此方法。
通过property.get进行取值,通过property.set进行赋值
接下来调用Object.defineProperty()给对象定义响应式属性(Object.defineProperty是vue.js实现「响应式系统」的关键之一)
- enumerable,属性是否可枚举,默认 false。
- configurable,属性是否可以被修改或者删除,默认 false。
- get,获取属性的方法。(进行依赖收集)(数据劫持)
- set,设置属性的方法。(进行响应式更新)
dep.notify():通过dep.notify()对观察者watchers进行通知,然后state就成全局响应式对象了。
需要注意的是:在 Vue 2.x 中,被传入的对象会直接被 Vue.observable 改变;在 Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行修改仍然是不可响应的。因此,为了向前兼容,官方推荐始终操作使用 Vue.observable 返回的对象,而不是传入源对象。