reactive 详解

reactive() 将一个普通对象转为深度响应式代理对象。与 ref 不同,reactive 返回的对象可以直接通过属性访问,不需要 .value

reactive()

reactive() 创建深度响应式对象代理——所有嵌套对象也会自动变为响应式。

tsx
import { reactive, watchEffect } from 'vitarx'

const state = reactive({
  name: '张三',
  profile: {
    age: 25,
    city: '北京'
  }
})

watchEffect(() => {
  console.log('姓名:', state.name)
  console.log('城市:', state.profile.city)
})

// ✅ 修改根级属性——触发更新
state.name = '李四'

// ✅ 修改嵌套属性——也触发更新(深度响应式)
state.profile.city = '上海'

属性级信号

Vitarx 的 reactive 采用属性级信号机制——对象的每个属性都有独立的依赖追踪。这意味着只有被访问的属性变化时才会触发更新,未访问的属性变化不会影响。

tsx
import { reactive, watchEffect } from 'vitarx'

const state = reactive({ name: '张三', age: 25 })

watchEffect(() => {
  // 只访问了 name 属性,只追踪 name 的依赖
  console.log('姓名:', state.name)
})

// ✅ 触发更新——name 变了
state.name = '李四'

// ❌ 不会触发更新——age 变了,但副作用中没有访问 age
state.age = 30

这种机制使得更新非常精确——只有真正依赖的数据变化时才会重新执行副作用。

shallowReactive()

shallowReactive() 创建浅层响应式对象——只有根级属性是响应式的,嵌套对象不会被代理。

tsx
import { shallowReactive, watchEffect } from 'vitarx'

const state = shallowReactive({
  name: '张三',
  profile: { age: 25, city: '北京' }
})

watchEffect(() => {
  console.log('state 变化了')
})

// ✅ 触发更新——修改根级属性
state.name = '李四'

// ❌ 不会触发更新——修改嵌套属性
state.profile.city = '上海'

何时使用 shallowReactive

  • 对象嵌套层级很深,深层代理有性能问题
  • 嵌套数据由第三方库管理,不需要框架代理
  • 你只关心根级属性的变化

集合类型支持

reactive() 支持以下集合类型:MapSetWeakMapWeakSet

Map

tsx
import { reactive, watchEffect } from 'vitarx'

const userMap = reactive(new Map<string, number>())

watchEffect(() => {
  console.log('Map 大小:', userMap.size)
})

userMap.set('张三', 25) // 触发更新
userMap.delete('张三') // 触发更新
userMap.clear() // 触发更新

Set

tsx
import { reactive, watchEffect } from 'vitarx'

const tagSet = reactive(new Set<string>())

watchEffect(() => {
  console.log('Set 大小:', tagSet.size)
})

tagSet.add('前端') // 触发更新
tagSet.delete('前端') // 触发更新

INFO

集合类型(Map、Set、WeakMap、WeakSet)的响应式代理不区分深度/浅层,始终使用相同的代理策略。

ref 自动解包

reactive 对象中,如果属性值是 ref,读取时会自动解包(自动取 .value),不需要手动访问 .value

tsx
import { ref, reactive } from 'vitarx'

const count = ref(0)
const state = reactive({ count })

// 读取时自动解包——不需要写 state.count.value
console.log(state.count) // 0(自动取了 count.value)

// 设置时也会自动解包——直接赋值即可
state.count = 10
console.log(count.value) // 10

WARNING

自动解包只发生在读取时。如果你在 reactive 对象中新增一个 ref 属性,它也会被自动解包。但如果你直接替换整个 reactive 对象的 ref 属性为另一个 ref,需要手动处理。

解构陷阱

直接解构 reactive 对象会丢失响应性——解构出来的变量只是普通值,不再是响应式的:

tsx
import { reactive } from 'vitarx'

const state = reactive({ name: '张三', age: 25 })

// ❌ 解构后失去响应性
const { name, age } = state
// name 和 age 只是普通字符串和数字

使用 toRefs 保持响应性

使用 toRefs() 将 reactive 对象的每个属性转为 ref,解构后仍然保持响应性:

tsx
import { reactive, toRefs } from 'vitarx'

const state = reactive({ name: '张三', age: 25 })

// ✅ 使用 toRefs 解构,保持响应性
const { name, age } = toRefs(state)

// name 和 age 是 PropertyRef,与原属性双向绑定
name.value = '李四'
console.log(state.name) // '李四'

更多 toRefs 的用法请参考 ref 详解

toRaw()

toRaw() 获取响应式对象的原始对象(未经代理的普通对象):

tsx
import { reactive, toRaw } from 'vitarx'

const state = reactive({ name: '张三', age: 25 })

const raw = toRaw(state)
// raw 是原始对象,不是代理对象
console.log(raw === state) // false

何时使用 toRaw

  • 需要将响应式对象传给第三方库,避免代理带来的兼容性问题
  • 需要读取原始值而不触发依赖追踪
  • 调试时查看原始数据

markRaw()

markRaw() 标记一个对象,使其永远不会被转为响应式代理:

tsx
import { reactive, markRaw, isReactive } from 'vitarx'

// 标记对象为"原始"
const config = markRaw({ theme: 'dark', version: 2 })

// 在 reactive 中使用——不会被代理
const state = reactive({ name: '张三', config })

// config 仍然是普通对象
console.log(isReactive(state.config)) // false

何时使用 markRaw

  • 第三方库的实例对象(如 Chart 实例、DOM 元素),不需要也不应该被代理
  • 包含不可变数据的对象,避免代理带来的性能开销
  • 包含函数或方法的对象,代理可能导致 this 绑定问题

WARNING

markRaw() 只能标记对象类型,传入非对象会抛出 TypeError。标记是永久的,无法撤销。

isReactive()

isReactive() 判断一个值是否为 reactive()shallowReactive() 创建的响应式对象:

tsx
import { reactive, shallowReactive, ref, isReactive } from 'vitarx'

const state = reactive({ count: 0 })
const shallow = shallowReactive({ count: 0 })
const countRef = ref(0)

isReactive(state) // true
isReactive(shallow) // true
isReactive(countRef) // false
isReactive({}) // false

完整示例

下面是一个综合使用 reactive 相关 API 的示例:

tsx
import {
  reactive,
  shallowReactive,
  toRefs,
  toRaw,
  markRaw,
  isReactive,
  ref,
  createApp
} from 'vitarx'

function App() {
  // 深度响应式
  const user = reactive({
    name: '张三',
    profile: { age: 25, city: '北京' }
  })

  // 浅层响应式
  const settings = shallowReactive({
    theme: 'light',
    config: { fontSize: 14 }
  })

  // ref 自动解包
  const count = ref(0)
  const state = reactive({ count })

  // toRefs 解构
  const { name, profile } = toRefs(user)

  // markRaw 标记不需要代理的对象
  const chartInstance = markRaw({ type: 'bar', data: [] })

  const increment = () => {
    state.count++ // 自动解包,不需要写 count.value
  }

  const changeCity = () => {
    // 深度响应式——修改嵌套属性也会触发更新
    profile.value.city = '上海'
  }

  const toggleTheme = () => {
    // 浅层响应式——修改根级属性触发更新
    settings.theme = settings.theme === 'light' ? 'dark' : 'light'
  }

  const logRaw = () => {
    // 获取原始对象
    console.log('原始对象:', toRaw(user))
  }

  return (
    <div style={{ padding: '20px' }}>
      <h2>reactive 详解示例</h2>

      <div style={{ marginTop: '16px' }}>
        <h3>深度响应式</h3>
        <p>姓名: {name}</p>
        <p>年龄: {profile.value.age}岁</p>
        <p>城市: {profile.value.city}</p>
        <button onClick={changeCity}>切换城市</button>
      </div>

      <div style={{ marginTop: '16px' }}>
        <h3>ref 自动解包</h3>
        <p>计数: {state.count}</p>
        <button onClick={increment}>+1</button>
      </div>

      <div style={{ marginTop: '16px' }}>
        <h3>浅层响应式</h3>
        <p>主题: {settings.theme}</p>
        <button onClick={toggleTheme}>切换主题</button>
      </div>

      <div style={{ marginTop: '16px' }}>
        <button onClick={logRaw}>打印原始对象</button>
        <p>markRaw 对象是否为响应式: {String(isReactive(chartInstance))}</p>
      </div>
    </div>
  )
}

createApp(App).mount('#app')

下一步