ref() 与 reactive()

发布于:2025-06-26 ⋅ 阅读:(16) ⋅ 点赞:(0)

下面,我们来系统的梳理关于 ref() 与 reactive() 的基本知识点:


一、响应式编程核心概念

1.1 什么是响应式编程?

响应式编程是一种声明式编程范式,它使数据变化能够自动传播到依赖它的代码部分。在 Vue 中,响应式系统实现了:

  • 数据驱动视图:数据变化自动更新DOM
  • 依赖追踪:自动跟踪数据依赖关系
  • 高效更新:最小化不必要的DOM操作

1.2 Vue 响应式系统演进

版本 响应式实现 特点
Vue 2 Object.defineProperty 拦截getter/setter,不支持数组索引变化
Vue 3 Proxy API 全面拦截对象操作,支持数组/集合类型

二、reactive() 深度解析

2.1 基本使用

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'Alice',
    profile: {
      level: 5
    }
  }
})

// 修改属性
state.count++ // 触发更新
state.user.name = 'Bob' // 深层响应

2.2 实现原理

Get
Set
Delete
reactive对象
Proxy拦截器
操作类型
追踪依赖
触发更新
收集当前effect
通知相关effect

2.3 核心特性

  • 深层响应:嵌套对象自动转为响应式
  • 类型保留:Array/Map/Set等特殊类型保持原型方法
  • 引用稳定:reactive() 返回同一个代理对象
    const obj = {}
    const proxy1 = reactive(obj)
    const proxy2 = reactive(obj)
    console.log(proxy1 === proxy2) // true
    

2.4 局限性

// 1. 原始值无法使用
const count = reactive(0) // 无效

// 2. 属性解构丢失响应性
const { count } = state // 非响应式

// 3. 新属性添加需要特殊处理
const state = reactive({})
state.newProp = 'value' // 非响应式

// 正确添加响应式属性
import { set } from 'vue'
set(state, 'newProp', 'value')

三、ref() 深度解析

3.1 基本使用

import { ref } from 'vue'

// 创建ref
const count = ref(0)
const user = ref({ name: 'Alice' })

// 模板中使用
// <div>{{ count }}</div> 自动解包.value

// JS中访问
console.log(count.value) // 0
count.value++ // 触发更新

3.2 实现原理

// 简化的ref实现
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value') // 依赖收集
      return value
    },
    set value(newVal) {
      value = newVal
      trigger(refObject, 'value') // 触发更新
    }
  }
  return refObject
}

3.3 核心特性

  • 包装原始值:使基本类型具有响应性
  • 模板自动解包:在模板中无需.value
  • 响应式替换:整个对象可替换
    const user = ref({ name: 'Alice' })
    
    // 替换整个对象
    user.value = { name: 'Bob' } // 保持响应性
    

3.4 ref 的特殊用法

// DOM元素引用
const inputRef = ref(null)

onMounted(() => {
  inputRef.value.focus()
})

// 组件引用
<ChildComponent ref="child" />

const child = ref(null)
child.value.doSomething()

// 函数式ref
<div :ref="(el) => { /* 操作元素 */ }"></div>

四、ref() vs reactive() 对比

4.1 核心差异对比

特性 ref() reactive()
包装对象 RefImpl 对象 Proxy 对象
值访问 需要 .value 直接访问
原始值支持
深层响应
类型保留 完整保留 部分特殊处理
解构响应性 支持解构属性 需要toRefs
整体替换 ❌ (会破坏响应性)

4.2 使用场景指南

场景 推荐 说明
基本类型值 ref 字符串、数字等
复杂对象 reactive 表单对象、状态对象
组件状态 reactive 逻辑相关的状态组
模板引用 ref DOM元素/组件实例
函数参数 ref 避免响应式对象解构问题
组合函数返回值 ref 更灵活的响应式值

4.3 性能考量

  • 创建开销:reactive() 的 Proxy 创建比 ref() 稍重
  • 内存占用:ref() 有额外包装对象开销
  • 更新效率:两者更新效率相当
  • 最佳实践
    // 大型对象使用reactive更高效
    const bigData = reactive(largeDataset)
    
    // 独立值使用ref
    const loading = ref(false)
    

五、响应式系统高级用法

5.1 响应式转换工具

import { toRef, toRefs, isRef, isReactive, isProxy } from 'vue'

const state = reactive({ count: 0 })

// 对象属性转为ref
const countRef = toRef(state, 'count')

// 整个对象转为ref集合
const refs = toRefs(state) // { count: Ref<number> }

// 类型检查
console.log(isRef(countRef)) // true
console.log(isReactive(state)) // true
console.log(isProxy(state)) // true

5.2 浅层响应式

import { shallowReactive, shallowRef } from 'vue'

// 浅层reactive:只响应根级属性
const shallowState = shallowReactive({
  nested: { value: 1 } // 内部非响应
})

// 浅层ref:不自动解包内部值
const shallowObj = shallowRef({ count: 0 })
shallowObj.value.count++ // 不会触发更新

5.3 自定义Ref

import { customRef } from 'vue'

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

// 使用
const text = useDebouncedRef('', 500)

5.4 响应式工具函数

import { markRaw, readonly, watchEffect } from 'vue'

// 1. 标记非响应式对象
const nonReactiveObj = markRaw({ shouldNotBeTracked: true })

// 2. 创建只读响应式对象
const readOnlyState = readonly(state)

// 3. 响应式副作用
watchEffect(() => {
  console.log(`Count: ${state.count}`) // 自动追踪依赖
})

六、响应式原理深度剖析

6.1 依赖收集与触发

组件渲染 响应式对象 依赖管理器 访问属性 track(收集当前effect) 记录依赖关系 数据变化时 trigger(通知更新) 执行effect更新 组件渲染 响应式对象 依赖管理器

6.2 Proxy 拦截器实现

const reactiveHandlers = {
  get(target, key, receiver) {
    track(target, key) // 收集依赖
    const res = Reflect.get(target, key, receiver)
    if (isObject(res)) {
      return reactive(res) // 递归响应式
    }
    return res
  },
  set(target, key, value, receiver) {
    const oldValue = target[key]
    const result = Reflect.set(target, key, value, receiver)
    if (hasChanged(value, oldValue)) {
      trigger(target, key) // 触发更新
    }
    return result
  }
  // 省略deleteProperty/has等其他拦截
}

6.3 响应式系统三大核心

  1. Effect(副作用)

    let activeEffect
    class ReactiveEffect {
      run() {
        activeEffect = this
        this.fn() // 执行过程中收集依赖
      }
    }
    
  2. Dep(依赖集合)

    class Dep {
      constructor() {
        this.subscribers = new Set()
      }
      depend() {
        if (activeEffect) this.subscribers.add(activeEffect)
      }
      notify() {
        this.subscribers.forEach(effect => effect.run())
      }
    }
    
  3. TargetMap(目标映射)

    const targetMap = new WeakMap()
    
    function track(target, key) {
      if (!activeEffect) return
      let depsMap = targetMap.get(target)
      if (!depsMap) targetMap.set(target, (depsMap = new Map()))
      let dep = depsMap.get(key)
      if (!dep) depsMap.set(key, (dep = new Dep()))
      dep.depend()
    }
    

七、最佳实践与性能优化

7.1 选择指南

场景 推荐 替代方案 原因
独立原始值 ref reactive 更简洁
相关状态组 reactive 多个ref 逻辑聚合
组件props ref - 标准做法
全局状态 reactive ref 结构化数据
需要解构 ref + toRefs reactive 保持响应性

7.2 性能优化策略

  1. 避免大型响应式对象

    // 避免
    const hugeObj = reactive(/* 大型数据集 */)
    
    // 推荐:使用shallowRef或手动管理
    const data = ref({})
    data.value = largeDataset // 替换而非响应式
    
  2. 减少不必要的响应式

    // 静态配置无需响应式
    const config = markRaw({
      itemsPerPage: 10,
      maxRetries: 3
    })
    
  3. 合理使用计算属性

    const sortedList = computed(() => 
      [...list.value].sort((a, b) => a.id - b.id)
    )
    
  4. 批量更新优化

    import { nextTick } from 'vue'
    
    const updateMultiple = () => {
      state.a = 1
      state.b = 2
      nextTick(() => {
        // DOM更新后执行
      })
    }
    

7.3 响应式数据模式

// 组合式函数模式
export function useCounter(initial = 0) {
  const count = ref(initial)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  
  return {
    count,
    increment,
    decrement
  }
}

// 在组件中使用
const { count, increment } = useCounter()

八、常见问题与解决方案

8.1 响应性丢失问题

问题:解构导致响应性丢失

const state = reactive({ count: 0 })
const { count } = state // 失去响应性

解决方案

// 1. 使用toRefs
const { count } = toRefs(state)

// 2. 直接访问原对象
state.count

// 3. 使用computed
const count = computed(() => state.count)

8.2 循环引用问题

问题:对象循环引用导致无限递归

const obj = reactive({})
obj.self = obj // 循环引用

解决方案

// 1. 避免循环引用
// 2. 使用markRaw切断响应链
obj.self = markRaw(obj)

8.3 数组操作问题

问题:直接索引修改数组不触发更新

const list = reactive([1, 2, 3])
list[0] = 9 // 不会触发更新

解决方案

// 1. 使用变异方法
list.splice(0, 1, 9)

// 2. 整个替换
list.value = [9, 2, 3]

// 3. 使用Vue.set (Vue 3中为set)
import { set } from 'vue'
set(list, 0, 9)

网站公告

今日签到

点亮在社区的每一天
去签到