跳到主要内容

vue3问题总结

react 和 vue 有哪些区别?

1. 设计哲学与定位

  • React: 严格来说是一个(Library),专注于视图层(View Layer)。
  • Vue: 是一个渐进式框架(Progressive Framework)。

2. 模板 vs JSX

  • Vue: 默认使用基于 HTML 的模板语法(Template)。
  • React: 使用 JSX(JavaScript XML),

3. 数据绑定与响应式系统

  • Vue: 核心是响应式系统。Vue 2 使用 Object.defineProperty,Vue 3 升级为基于 Proxy。开发者只需修改数据,Vue 会自动追踪依赖并更新 DOM,实现了双向绑定(通过 v-model 指令)。这使得数据操作非常直观。
  • React: 采用单向数据流。状态(State)是不可变的,需要通过 setStateuseState Hook 显式地更新状态来触发组件重新渲染。

4. 状态管理

  • React: 内置了 useStateuseReducer 等 Hook 来管理组件状态。
  • Vue: 提供了 refreactive 等 API 来定义响应式数据。

7. 性能

  • Vue 3 引入了编译时优化(如静态提升、Patch 标记),在某些场景下可能比 React 更高效。
  • ReactFiber 架构Concurrent Mode(并发模式)旨在提供更流畅的用户体验,特别是在处理大型应用和复杂交互时。

Object.defineProperty 有哪些局限性

Object.defineProperty 是 ES5 中引入的强大特性,Vue 2 正是利用它来实现响应式系统的核心。然而,它存在一些显著的局限性,这些局限性也是 Vue 3 升级到 Proxy 的主要原因。

以下是 Object.defineProperty 的主要局限性:

1. 无法监听数组索引的变化(直接通过索引设置元素)

这是最广为人知的局限性。

  • 问题:当你直接通过索引修改数组元素(如 arr[0] = newValue)或修改数组长度(如 arr.length = 0)时,Object.defineProperty 无法检测到这些变化
    const arr = [1, 2, 3]
    Object.defineProperty(arr, '0', {
    set(value) {
    console.log('索引0的值被修改了')
    // 实际上,这个set函数在这里不会被触发
    },
    })
    arr[0] = 4 // 不会触发 set
  • Vue 2 的解决方案:Vue 2 通过重写数组的原型方法(如 push, pop, shift, unshift, splice, sort, reverse)来“劫持”这些方法,在调用原方法后手动触发视图更新。但这只解决了通过方法修改数组的情况,直接通过索引赋值或修改 length 仍然无效。
  • 后果:使用 Vue.set(vm.arr, indexOfItem, newValue)vm.$set(vm.arr, indexOfItem, newValue) 来确保响应式更新。

2. 无法监听对象属性的动态添加或删除

Object.defineProperty 只能对对象上已经存在的属性进行劫持。

  • 问题:如果你向一个已经通过 Object.defineProperty 使其响应式的对象添加一个新属性,或者删除一个已有属性,这个操作不会触发视图更新

    const obj = { a: 1 }
    Object.defineProperty(obj, 'a', {
    get() {
    console.log('读取a')
    },
    set(value) {
    console.log('设置a')
    },
    })

    obj.a = 2 // 会触发 set
    obj.b = 3 // 新增属性b,无法被监听!
    delete obj.a // 删除属性a,无法被监听!
  • Vue 2 的解决方案

    • 添加属性:使用 Vue.set(vm.obj, 'newProp', value)vm.$set(obj, 'newProp', value)
    • 删除属性:使用 Vue.delete(vm.obj, 'propToRemove')vm.$delete(obj, 'propToRemove')

3. 需要递归遍历才能实现深度监听(Deep Watching)

Object.defineProperty 只能监听对象的单层属性

  • 问题:如果对象的某个属性值本身也是一个对象,那么这个嵌套对象的属性变化是无法被监听到的。
    const obj = { nested: { c: 3 } }
    // 如果只对 obj 的 'nested' 属性进行 defineProperty
    // 那么 obj.nested.c = 4 这样的操作不会触发任何监听
  • 解决方案:必须递归地遍历对象的所有嵌套属性,对每一层的每个属性都调用 Object.defineProperty。这不仅代码复杂,而且在对象层级很深或属性非常多时,会造成性能开销内存占用
  • Vue 2 的实践:Vue 2 在初始化时会对 data 对象进行递归遍历,将所有属性转换为响应式。这也是为什么 Vue 2 推荐在 data 中预先声明好所有需要响应的属性。

4. 对整个对象的监听需要逐个属性定义

Object.defineProperty 的作用目标是单个属性,而不是整个对象。

  • 问题:要使一个对象的所有属性都具有响应性,必须对它的每一个属性都单独调用 Object.defineProperty
  • 后果:这使得实现一个通用的、自动化的响应式系统变得复杂,需要大量的循环和递归代码。

5. 兼容性问题

  • Object.defineProperty 是 ES5 的特性,不支持 IE8 及更早版本。虽然现代开发已基本不考虑这些旧浏览器,但在某些特殊场景下仍需注意。

总结

Object.defineProperty 的核心局限在于:

  1. 监听不完整:无法监听数组索引赋值、属性增删。
  2. 监听不彻底:只能监听已有属性,且需要手动递归实现深度监听。
  3. 使用不友好:开发者需要记住并使用 Vue.set/Vue.delete 等特殊 API 来规避限制。

Vue 3 的 ref() 如何实现对简单数据类型的响应式

const name = ref('hello')

这里的 "hello" 是基本类型(字符串),它不会被 reactive() 包装,而是直接放进 ref.value 里。

机制拆解:

  1. 初始化阶段

    • ref("hello") 调用 createRef("hello")
    • 判断值是不是对象:字符串不是对象,所以不会走 reactive,直接存到 _value
    _value = 'hello'
  2. getter 拦截

    • 当你访问 name.value 时,会触发 getter:
      • 调用 track(),把当前副作用(effect)收集起来;
      • 返回存储的 _value(也就是 "hello")。
    get value() {
    track(this, 'value')
    return this._value
    }
  3. setter 拦截

    • 当你修改 name.value = "world" 时,会触发 setter:
      • 先判断新值和旧值是否不同;
      • 如果不同,就更新 _value
      • 然后调用 trigger(),通知所有依赖更新。
    set value(newVal) {
    if (hasChanged(newVal, this._value)) {
    this._value = newVal
    trigger(this, 'value')
    }
    }
  4. 响应式触发

    const name = ref('hello')

    effect(() => {
    console.log(name.value) // 依赖收集
    })

    name.value = 'world' // 触发 trigger → effect 重新执行

所以字符串(还有数字、布尔、nullundefined 等)在 ref() 里都是靠 .value 的 getter/setter 来实现响应式,而不是 Proxy。


换句话说:

  • 对象传给 ref.value 里套了个 Proxy(reactive)。
  • 字符串/数字传给 ref.value 是普通值,依赖收集和触发全靠 .value 的 getter/setter。

要不要我再帮你画一张「字符串 ref 的响应式流程图」,让它和对象 ref 的区别更直观?

Vue 3 的 ref() 如何实现对复杂数据类型的响应式

Vue3 里的 ref() 本质是给单值做响应式包装,但当你传入一个对象时,它内部会偷偷把这个对象用 reactive() 包一层。

拆解一下:

  1. ref 初始化

    function ref(rawValue) {
    return createRef(rawValue, false)
    }
  2. createRef 内部逻辑

    • 如果 rawValue 是对象,会调用 reactive(rawValue)
    • 如果是基本类型,就直接保存。

    源码里类似这样:

    function createRef(rawValue, shallow) {
    return {
    __v_isRef: true,
    _value: shallow ? rawValue : toReactive(rawValue),
    get value() {
    track(this, 'value')
    return this._value
    },
    set value(newVal) {
    if (hasChanged(newVal, this._value)) {
    this._value = shallow ? newVal : toReactive(newVal)
    trigger(this, 'value')
    }
    },
    }
    }

    function toReactive(value) {
    return isObject(value) ? reactive(value) : value
    }
  3. 对象响应式的实现

    • reactive(obj) 内部用 Proxy 拦截 get/set
    • 这样 ref({ foo: 1 }).value 本身是响应式对象,访问 myRef.value.foo 时,会走 Proxy → 触发依赖收集。
    • 修改时,比如 myRef.value.foo = 2,会走 Proxy 的 set → 触发更新。

所以:

  • ref() 的响应式靠 .value 的 getter/setter。
  • 如果 .value 是对象,就进一步用 reactive() 包装,这样对象的属性也能保持响应式。

一个小例子:

const state = ref({ count: 0 })

effect(() => {
console.log(state.value.count) // 依赖收集
})

state.value.count++ // 触发 Proxy.set → trigger → effect 重新执行

这里 响应式触发的源头 并不是 ref.value 的 setter(因为 .value 本身没变),而是内部的 reactive Proxy 在发挥作用。