Vue.observable 实现响应式状态管理

使用 Vue.observable 创建轻量级的响应式数据,适合简单的跨组件状态共享

问题

Vue.observable 是什么?如何使用它来实现轻量级的状态管理?

解答

什么是 Vue.observable

Vue.observable 可以让一个普通对象变成响应式数据。Vue 内部使用它来处理 data 函数返回的对象。

返回的对象可以直接用于渲染函数和计算属性,发生变更时会触发相应的更新。它可以作为最小化的跨组件状态存储器使用。

Vue.observable({ count: 1 })

其作用等同于:

new Vue({ data: { count: 1 } })

版本差异:

  • Vue 2.x:传入的对象会直接被变更,返回的对象和传入的是同一个
  • Vue 3.x:返回一个响应式代理,源对象不会变成响应式

使用场景

在非父子组件通信时,如果功能不复杂,使用 bus 或 vuex 会显得繁琐。这时 observable 是更好的选择。

创建状态管理文件 store.js

import Vue from 'vue'

// 创建响应式状态
export const state = Vue.observable({
  name: '张三',
  age: 20
})

// 创建 mutations
export const mutations = {
  changeName(name) {
    state.name = name
  },
  setAge(age) {
    state.age = age
  }
}

在组件中使用:

<template>
  <div>
    姓名:{{ name }}
    年龄:{{ age }}
    <button @click="changeName('李四')">改变姓名</button>
    <button @click="setAge(18)">改变年龄</button>
  </div>
</template>

<script>
import { state, mutations } from '@/store'

export default {
  computed: {
    name() {
      return state.name
    },
    age() {
      return state.age
    }
  },
  methods: {
    changeName: mutations.changeName,
    setAge: mutations.setAge
  }
}
</script>

实现原理

源码位置:src/core/observer/index.js

核心流程是通过 observe 函数创建 Observer 实例,然后使用 defineReactive 将对象的每个属性转换为 getter/setter:

export function observe(value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  // 创建 Observer 实例
  return new Observer(value)
}

Observer 类会遍历对象的所有属性:

export class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep()
    
    if (Array.isArray(value)) {
      // 处理数组
    } else {
      this.walk(value)
    }
  }
  
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}

defineReactive 使用 Object.defineProperty 实现响应式:

export function defineReactive(obj, key, val, customSetter, shallow) {
  const dep = new Dep()
  
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  
  const getter = property && property.get
  const setter = property && property.set
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      const value = getter ? getter.call(obj) : val
      // 依赖收集
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    set(newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value) return
      
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 触发更新
      dep.notify()
    }
  })
}

关键点

  • Vue.observable 将普通对象转换为响应式数据,适合轻量级状态管理
  • 相比 Vuex,它更简单,适合不复杂的跨组件通信场景
  • Vue 2.x 直接修改原对象,Vue 3.x 返回代理对象
  • 底层通过 Observer 类和 defineReactive 方法实现,使用 Object.defineProperty 劫持属性
  • 配合 computed 和 methods 可以实现类似 Vuex 的 state 和 mutations 模式