Skip to content

常见问题

为什么data属性是一个函数而不是一个对象?

通过构造函数创建全局vue实例的时候,定义data属性既可以是一个对象,也可以是一个函数;组件中定义data属性,只能是一个函数,如果为组件data属性直接定义为一个对象,则会得到警告信息。

原因分析

  • JS通过构造函数来创建实例;当data被定义成对象时,这个对象会作为构造函数的一个属性,所有通过该构造函数创建的实例,data都是同一个内存地址的引用,其中一个实例修改data会影响到其他实例。
  • 当我们将组件中的data属性写成一个函数,数据以函数的返回值形式定义,每复用一次组件,就会在内存中分配新的空间存储,不会受到其他实例对象数据的污染。
  • 全局vue实例通常只有一个,不存在复用和数据共享的问题,所以此时data属性可以定义为一个对象。

动态给data添加一个新的属性时会发生什么?怎样解决?

  • vue2通过Object.defineProperty实现数据响应式,对象和数组的新增属性可能无法劫持到进而触发视图更新。
  • vue3通过proxy实现数据响应式,直接动态添加新属性仍可以劫持到。

原因分析

  • vue2中采用Object.defineProperty来劫持对象的属性,初始化时递归遍历data属性,给每个属性添加gettersetter方法;当访问初始化已有属性或者设置已有属性的值时能够触发gettersetter方法;但是为对象添加新属性时,并没有经过拦截,所以无法触发响应式;如果存在深层的嵌套对象关系,需深层监听,造成性能问题。

  • 数组部分方法(pushpopshiftunshiftsplicesortreverse)经过重写,用Object.defineProperty包装过,所以有响应式效果。

解决方案

  • Vue.set(): 通过Vue.set(调用defineReactive => Object.defineProperty)向响应式对象中添加一个property,并确保这个新property同样是响应式的,且触发视图更新。

  • Object.assign(): 合并原对象和混入对象的属性,再赋值给原对象。

  • $forceUpdated(): 强制更新,迫使实例重新渲染(不建议)。

  • Proxy实现数据响应式: Proxy的监听是针对一个对象的,对这个对象的所有操作会进入监听操作。

computed VS watch

  • computed计算属性,是基于响应式依赖来创建一个属性,目的是根据其他响应式数据计算得出一个新的值,并且这个值会被缓存,只有当依赖的响应式数据发生变化时才会重新触发计算。

    • 创建计算属性时调用 ReactiveEffect 构造函数生成一个effect副作用函数实例。
    • 在访问计算属性的value属性时,触发getter函数,此时effect.run()收集依赖。
    • 判断_dirty,是否需要重新计算:
      • true时,重新计算新的值赋值给_value,并标记_dirtyfalse
      • false时,直接返回已缓存的_value
    • 当依赖的数据变化时,会触发这些响应式数据的setter函数,进而触发effect副作用函数的调度器函数,将_dirty标记为true,下次访问value属性时要重新计算。
  • watch侦听器,用于监听一个或者多个响应式数据的变化,并在数据变化时执行响应的回调函数,目的在与数据变化时执行副作用,比如发送网络请求、获取DOM等。

    • 解析监听源,标准化 sourcegetter 函数。
    • 根据flush创建调度器(控制回调执行时机)。
    • 调用 ReactiveEffect 构造函数生成一个effect副作用函数实例,调度器作为副作用函数的调度器函数传入。
    • 定义回调函数,对比新旧值是否变化,再更新旧值。
    • effect.run()初始化旧值和依赖收集。
    • 在依赖变化时,执行调度器函数,进而触发回调函数执行。
对比项computedwatch
依赖声明隐式(自动追踪)显式(需指定监听源)
缓存支持缓存,只有当依赖的数据发生变化时,才会重新计算,否则会从缓存中读取之前的计算结果,可以避免不必要的计算开销不支持缓存,每当监听的数据变化时,watch都会执行回调函数
异步不支持异步,需要立刻返回计算结果支持异步操作,在数据变化后执行回调
返回值计算属性内部函数需要返回计算结果不需要返回值
初始化执行
首次访问时会执行设置immediate控制

ref VS reactive

对比项refreactive
接受类型任意类型仅对象类型
访问方式通过.value访问直接访问属性
模板解包自动解包(无须.value)无须解包
watch对于引用类型,watch默认不会开启深度监听默认开启深度监听
引用替换保持响应(.value = 新引用)完全丢失响应
深层响应默认支持默认支持
解构处理需配合toRefs需配合toRefs
性能优化shallowRefshallowReactive
使用场景基本类型、跨组件传递数据、与原生DOM交互处理复杂对象和数组、状态管理

深入Vue3响应式:手写实现reactive与ref上篇文章介绍了Vue3响应式的两个核心API,知道了两者的用法于区别 - 掘金

toRef VS toRefs

对比项toReftoRefs
作用将响应式对象的单个属性转换为一个Ref引用将响应式对象的所有属性批量转为Ref类型,并包装成一个普通对象
参数接收三个参数:
响应式对象obj
属性名key
默认值defaultValue
仅接收一个参数:响应式对象obj
不存在的属性传入不存在的key也会返回一个Ref引用返回的对象只会包含源响应式对象所包含的属性
适用场景指定解构某个单一属性的情况(给hooks解构props的某个属性)适用于需要解构reactive对象(多个属性)的场景

Composition Api 相比 Options Api的优势

  • 逻辑聚合更清晰: 同一业务相关逻辑,Options Api分散在不同的选项中, 而Composition Api可以集中放在一个函数里,有更高的代码可读性和维护性。
  • 代码复用更加灵活: Composition Api,通过自定义Hooks可以抽离通用逻辑,复用方式比mixins更加清晰,不会出现命名冲突,也能明确知道复用逻辑的来源。
  • 类型推断更友好: 结合TS时,Composition Api的函数式写法能让类型推导更自然,而Options Api依赖this上下文,类型定义需要额外处理。
  • 按需导入减小包体积: Composition Api支持按需导入,而Options Api的选项是固定结构,打包时可能包含一些未使用的逻辑。

封装一个组件的思考步骤

  • 明确组件的功能和用途。
    • 要实现的功能或解决什么问题(降低组件复杂度?UI组件?业务组件?)。
    • 考虑组件的使用场景和复用性。
  • 确定代码写在哪个目录下。
    • 全局通用组件/模块通用组件/普通子组件放在不同的地方。
  • 设计组件的接口。
    • props确定组件要接收的外部数据。
    • slots判断组件是否需要预留插槽,以允许父组件自定义内容。
    • event确定组件要触发哪些自定义事件,以便父组件监听拓展功能。
  • 根据视觉稿,规划组件的结构和样式。
  • 实现代码逻辑,必要的代码注释不能少。
  • 调试代码,有条件的情况下编写测试用例。
  • 编写组件文档。

Released under the MIT License.