声明响应式状态的两种方法:ref 和 reactive

ref

  • 逻辑中需要.value访问内部值

  • 模板中直接使用

import {ref} from 'vue'

const count = ref(0)
const count1 = ref(0)

function increment (event) {
  console.log(event.target)
  count1.value++   // 逻辑中需要.value访问内部值
}

<button type="button" @click="count++">count is {{ count }}</button>  // 模板中直接使用{{count}}
<button type="button" @click="increment">count1 is {{ count1 }}</button>

reactive

import {reactive} from 'vue'

const user = reactive({
    name: 'jack'
})

console.log(user.name)

ref和reactive底层实现的伪代码

// ref 的简化实现
function myRef(value) {
  return {
    get value() {
      // 收集依赖
      return value
    },
    set value(newVal) {
      // 触发更新
      value = newVal
    }
  }
}
// reactive 的简化实现
function myReactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      // 收集依赖
      return target[key]
    },
    set(target, key, value) {
      // 触发更新
      target[key] = value
      return true
    }
  })
}

ref和reactive的区别

ref和reactive都是vue3创建响应式数据的API,但设计理念和使用场景不同:

  • ref主要用于包装原始值(字符串、数字、布尔等)(但其实像对象、map任何类型的数据都可以),而reactive主要用于处理对象数组Map、Set集合
  • ref需要通过.value的方式访问内部值,reactive则可以直接访问属性
  • 实现原理:ref内部是用reactive包装的{value: xxx}对象,而reactive是用ES6 Proxy代理原始对象
  • 模板中,ref自动解包,无需写.value,而reactive保持原样访问

为什么ref需要.value,而reactive不需要?

这是由javascript语言特性决定的。vue3的响应式基于ES6 Proxy,但Proxy只能代理对象,无法代理原始值(number、string、bool这些)。为了让原始值也能响应式,必须把它包装成对象,ref(0)实际上是创建了{value: 0}这个包装对象,所以访问需要.value

  • 对比vue2:虽然vue2没有这个烦恼,但它把所有的data都变成了对象属性(this.count),导致this指向混乱和类型丢失。vue3的ref虽然要写.value,但解决了这些根本痛点。

什么时候用ref,什么时候用reactive?

  • 数据类型上来说:简单的原始值用ref,像表单对象就用reactive
  • 是否需要重新赋值:需要重新赋值用ref,不需要用reactive(不能直接赋值,会失去响应性)
  • 代码组织上:组合函数返回用ref保持接口统一
  • 要看团队约定一致性,一个数据源只用一种方式,避免如下混合写法
// 场景2:需要重新赋值 - 必须用 ref
const filters = ref({ keyword: '', category: 'all' })
// 可以完全替换
filters.value = { keyword: 'vue', category: 'tech' }

const userReactive = reactive({
  name: 'Jack',
  age: 25
})
// 不能重新赋值,会失去响应式
userReactive = {name: 'Jack', age: 30}
// 场景3:从组合函数返回 - 推荐 ref
function useCounter() {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }  // 保持 .value 接口统一
}
// 场景4:混合写法,不推荐
const user = reactive({ 
  name: ref('jack'),
  age: 20
})

如何将reactive转为ref

  • toRef(reactiveObj, key):将reactive对象的单个属性转为ref
  • toRefs(reactiveObj):将reactive对象的所有属性转为ref

results matching ""

    No results matching ""