
其实在日常工作中,Vue.observable 用的并不多,但是是一个非常轻量级的vue原生状态管理工具。它是 Vue 2.x 提供的全局 API,用于将一个普通对象转化为响应式对象,返回的对象可以直接被 Vue 追踪依赖、触发视图更新。它是 Vue 响应式系统的底层能力暴露,也是 Vuex/组件 data 响应式的核心底层逻辑。
reactive 替代(底层由 Object.defineProperty 改为 Proxy);Vue.observable(obj)
obj — 普通纯对象(不支持基本类型、数组需特殊处理);ps:因为它时基于Object.definedProperty实现的,所以为了能够劫持数据实现修改和获取,在实际应用时对基本类型加一层{}就可以了;Vue.observable 的核心逻辑和组件 data 响应式完全一致,基于 Object.defineProperty 劫持属性的 getter/setter:
getter/setter;getter 会收集当前组件的 Watcher(依赖)到 Dep(依赖管理器);setter 触发 Dep 通知所有收集的 Watcher 执行更新(重新渲染组件)。// 核心:为对象属性定义响应式
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
const childOb = observe(val);
const dep = new Dep(); // 依赖管理器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集:将当前 Watcher 加入 Dep
if (Dep.target) {
dep.depend();
if (childOb) childOb.dep.depend(); // 嵌套对象的依赖收集
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 新值如果是对象,递归做响应式
observe(newVal);
// 派发更新:通知所有 Watcher 执行更新
dep.notify();
},
});
}
// 对外暴露的 observable 方法
Vue.observable = function (obj) {
observe(obj); // 为对象创建 Observer 实例
return obj;
};
// 递归观察对象
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
return new Observer(obj);
}
class Observer {
constructor(value) {
this.dep = new Dep();
// 遍历对象属性,逐个做响应式
Object.keys(value).forEach(key => {
defineReactive(value, key, value[key]);
});
}
}
Vue.observable 主要用于轻量级状态管理,替代 Vuex 适用于小型应用/组件间共享状态的场景。
适用于无需模块化、复杂逻辑的全局状态(如用户信息、主题切换、全局加载状态)。
// store.js - 全局状态管理
import Vue from 'vue';
// 创建响应式状态
export const state = Vue.observable({
userInfo: null,
theme: 'light',
loading: false,
});
// 定义修改状态的方法(规范操作)
export const mutations = {
setUserInfo(info) {
state.userInfo = info;
},
toggleTheme() {
state.theme = state.theme === 'light' ? 'dark' : 'light';
},
setLoading(flag) {
state.loading = flag;
},
};
组件中使用:
<template>
<div :class="state.theme">
<p>{{ state.userInfo?.name }}</p>
<button @click="mutations.toggleTheme">切换主题</button>
</div>
</template>
<script>
import { state, mutations } from './store.js';
export default {
computed: {
// 直接访问响应式状态(依赖收集)
state() {
return state;
},
},
methods: {
mutations,
},
};
</script>
无需通过 props/$emit/eventBus,直接通过响应式对象共享状态:
// 共享状态文件:sharedState.js
import Vue from 'vue';
export const sharedState = Vue.observable({
count: 0,
});
export const increment = () => {
sharedState.count++;
};
组件 A:
<template>
<div>组件A:{{ sharedState.count }}</div>
</template>
<script>
import { sharedState } from './sharedState.js';
export default {
computed: {
sharedState() {
return sharedState;
},
},
};
</script>
组件 B:
<template>
<button @click="increment">点击+1</button>
</template>
<script>
import { increment } from './sharedState.js';
export default {
methods: { increment },
};
</script>
将组件内的响应式状态抽离,实现逻辑复用:
// useCounter.js
import Vue from 'vue';
export default function useCounter() {
const state = Vue.observable({ count: 0 });
const increment = () => state.count++;
const decrement = () => state.count--;
return { state, increment, decrement };
}
组件中使用:
<script>
import useCounter from './useCounter.js';
export default {
setup() { // Vue 2.7+ 支持 setup
const { state, increment, decrement } = useCounter();
return { state, increment, decrement };
},
};
</script>
由于底层基于 Object.defineProperty,Vue.observable 存在以下局限性:
基本类型(字符串、数字、布尔)无法直接做响应式,必须包装为对象:
// ❌ 无效:基本类型无属性可劫持
const num = Vue.observable(123);
// ✅ 有效:包装为对象
const numObj = Vue.observable({ value: 123 });
由于vueObject.defineProperty 只能劫持已存在的属性,新增属性跟vue2更新对象/数组数据时一样需用 Vue.set,删除需用 Vue.delete:
const state = Vue.observable({ name: '张三' });
// ❌ 新增属性无响应式
state.age = 18;
// ✅ 用 Vue.set 新增响应式属性
Vue.set(state, 'age', 18);
// ❌ 删除属性不触发更新
delete state.name;
// ✅ 用 Vue.delete 删除
Vue.delete(state, 'name');
数组的索引修改、长度修改不触发更新,需使用数组变异方法(push/pop/shift/unshift/splice/sort/reverse)或 Vue.set:
const list = Vue.observable({ arr: [1, 2, 3] });
// ❌ 索引修改无响应式
list.arr[0] = 100;
// ✅ 用变异方法
list.arr.splice(0, 1, 100);
// ✅ 用 Vue.set
Vue.set(list.arr, 0, 100);
解构会将响应式属性转为基本类型,脱离 getter/setter 劫持,导致修改不触发更新:
const state = Vue.observable({ count: 0 });
// ❌ 解构后 count 是基本类型,修改无响应式
const { count } = state;
count++;
// ✅ 直接访问响应式对象的属性
state.count++;
Vue.observable 会递归处理嵌套对象,但若嵌套对象是后续新增的,需手动重新劫持:
const state = Vue.observable({ info: {} });
// ❌ 新增的嵌套对象无响应式
state.info.address = '北京';
// ✅ 先赋值响应式对象
state.info = Vue.observable({ address: '北京' });
Vue 3 废弃了 Vue.observable,改用 reactive/ref,核心差异如下:
| 特性 | Vue.observable (Vue2) | reactive (Vue3) | ref (Vue3) |
|---|---|---|---|
| 底层实现 | Object.defineProperty | Proxy | 包装对象(value 属性) |
| 响应式粒度 | 属性级别 | 对象级别(支持新增/删除属性) | 基本类型/对象(统一.value 访问) |
| 新增属性支持 | 不支持(需 Vue.set) | 天然支持 | 支持(ref 对象的 value) |
| 数组支持 | 索引修改无效(需变异方法) | 天然支持索引/长度修改 | 支持(ref 包裹数组) |
| 解构响应式 | 丢失响应式 | 需 toRefs 保留响应式 | 解构后仍需 .value 访问 |
| 基本类型支持 | 不支持(需包装为对象) | 不支持(需 ref) | 专门支持基本类型 |
Vue.observable 是 Vue2 响应式系统的底层暴露,适合小型应用的轻量级状态管理,无需引入 Vuex;Object.defineProperty,不支持新增属性、数组索引修改、基本类型等,需配合 Vue.set/变异方法使用;setup + reactive),Vue3 直接使用 reactive/ref;Vue.observable 封装全局状态时,统一通过方法修改状态(类似 Vuex mutations),避免直接修改;Vue.set,数组修改用变异方法。理解 Vue.observable 的原理,本质是理解 Vue 响应式系统的核心(getter/setter 劫持、依赖收集、派发更新),这也是掌握 Vue 响应式的关键。
