想象一家老餐馆的后厨:食材堆在仓库(data),菜刀挂在墙上(methods),灶台在最里面(computed)。做一道红烧肉,你需要在两三个区域之间来回跑,食材、调料、步骤散落各处。如果同时出十道菜,后厨立刻乱成一锅粥。 Vue3 的 Composition API 就是一次“厨房革命”。它把后厨重新装修成“料理台模式”——每一道菜,从备菜到出锅的全套家伙都摆在同一张台面上,按“菜”(功能)聚类,而不是按“工具类型”分类。你要做红烧肉,就让红烧肉的所有步骤都在眼前;做酸菜鱼,就换一张台面。ref、reactive、computed、watch、自定义 Hook、provide/inject 这些新工具,就是新厨房里一个个顺手的小物件。 这篇文章从零开始,带你把组件写法从“老厨房”搬到“新厨房”,并讲清楚每个工具在实战中怎么用、什么时候用、为什么好用。

岗位关联

掌握 Composition API 是 Vue3 实战开发的分水岭,也是 AI 辅助编程时代高效描述业务逻辑、与团队协作的基本功。

目录
  1. 可视化演示
  2. 逻辑重组革命
  3. setup语法糖
  4. 响应式数据选型
  5. 计算属性进阶
  6. 侦听器进阶用法
  7. 自定义Hook封装
  8. 跨层级依赖注入
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Composition API三大核心:响应式系统数据流(ref/reactive→effect→DOM更新)、自定义Hook组合复用模式、以及组件生命周期钩子时序。交互切换不同机制视角。

本节配有交互动效(进入视口后自动加载,约 3KB)。

响应式系统数据流 ref/reactive/computed 依赖收集→effect触发→DOM更新 effect = () => { track(ref); render(DOM) } Proxy拦截实现依赖追踪与自动更新

逻辑重组革命

回到那家老厨房。你收到的订单是“一份糖醋排骨、一份清蒸鲈鱼”。老规矩要求:所有食材按类型放——糖和醋要去调料库取,排骨要去冷藏室拿,锅铲挂在工具墙。于是你做一道菜,要在仓库、工具墙、灶台之间折返十几次。更糟的是,如果厨房同时出十道菜,每个人的食材会混在一起,找一样东西要翻半天。

这是 Vue2 的 Options API 写复杂组件的真实体验:data 里堆数据、methods 里堆函数、computed 里堆派生值。一个“登录功能”用到的用户名、登录方法、验证逻辑、错误提示,分别散落在三个选项里。代码一旦超过三百行,想改一个功能的逻辑,得在文件中跳来跳去,维护成本急剧上升。

Composition API 的核心改革是“以功能为单位组织代码”:每个功能相关的状态、计算、监听、生命周期,全部写在一个代码块里。setup() 是统一入口,组件初始化时先进入 setup,所有组合式逻辑都在这里运行。数据用 ref、reactive 声明,计算用 computed,监听用 watch,生命周期钩子也可以直接在 setup 内调用。于是,组件的逻辑不再被“选项类型”切碎,而是被“业务功能”聚合。

老厨房的“分类储物”逻辑单看没错,但人脑是按“菜”思考的,不是按“食材类型”思考的。Composition API 让代码的思考方式和人的思考方式对齐——料理台模式,一菜一台,逻辑自然清晰。

C=∑i=1nSi聚类S总代码C = \frac{\displaystyle\sum_{i=1}^{n} S_{i}^{\text{聚类}}}{S_{\text{总代码}}}
这个公式衡量一个组件的“逻辑聚合度”:分子是把同一功能聚在一起的代码量,分母是组件的总代码量。C 越接近 1,说明代码越按功能块集中,阅读和维护越轻松。
  1. 分子求和第 i 个功能聚拢的代码量记为 $S_{i}^{\text{聚类}}$,n 表示组件里有 n 个独立功能,连加符号把这些功能聚拢的代码全部加总。
    ∑i=1nSi聚类\sum_{i=1}^{n} S_{i}^{\text{聚类}}
  2. 分母总代码$S_{\text{总代码}}$ 是整个组件的全部代码行数,作为对比基准。
    S总代码S_{\text{总代码}}
  3. 比值含义比值 $C$ 越接近 1,说明代码高度按功能聚类;越接近 0,说明代码被按类型切碎后散落在各处。
    C∈[0,1]C \in [0, 1]

setup语法糖

料理台模式听着好,但老板有顾虑:会不会每次做菜都要写三页申请单?Vue3 的 script setup 语法糖就是那张“简化开工单”——把流程里能自动化的环节全部自动化。

在传统 setup() 写法里,你需要在函数中手动声明一切,用 return 将变量暴露给模板,还要通过 components 选项注册子组件。script setup 把这些仪式感全部删掉:顶层声明的变量自动暴露给模板;import 进来的组件直接可用;defineProps 和 defineEmits 是编译器宏,无需导入直接使用;ref 在模板中自动解包,不用写 .value。

要注意,语法糖不改变响应式逻辑本身,它只是把“必须手写的样板”交给编译器处理。这就像料理包把“切菜备料”自动化,但你依然要决定放多少糖、焖几分钟——业务判断人来做,重复劳动机器做。开工单从三页缩成一页,师傅们开火更快,厨房产出自然更高。

R=1−Sscript setupSsetup()传统R = 1 - \frac{S_{\text{script setup}}}{S_{\text{setup()} \text{传统}}}
这个公式描述“代码精简率”:左手是 script setup 写法的代码行数,右手是传统 setup() 写法的代码行数。R 越大,语法糖帮团队省下的样板代码越多。
  1. 分子$S_{\text{script setup}}$ 表示使用 script setup 语法糖后的代码行数,通常更短。
    Sscript setupS_{\text{script setup}}
  2. 分母$S_{\text{setup()} \text{传统}}$ 表示传统 setup() 加 export default 写法的代码行数。
    Ssetup()传统S_{\text{setup()} \text{传统}}
  3. 精简率$R$ 为正说明代码量下降,R 越接近 1,说明语法糖带来的简化效果越显著。
    R=1−Sscript setupSsetup()R = 1 - \frac{S_{\text{script setup}}}{S_{\text{setup()}}}

响应式数据选型

新厨房里有两类基础容器:调味瓶(ref)和食材收纳盒(reactive)。调味瓶装的是“单颗粒子”——盐、糖、胡椒粉;收纳盒装的是“成套食材”——一块五花肉连同它的纹理、脂肪、部位信息,是一个整体。

ref 用来包装基本类型(number、string、boolean),也可以包对象。用 ref 包装后,读取和修改都要经过 .value——就像从调味瓶倒调料要拧开瓶盖。reactive 只能包装引用类型(对象、数组),它通过 Proxy 深度代理,访问属性直接用 obj.prop,无需取 .value。但 reactive 有一个著名陷阱:解构出来的属性会丢失响应性——就像把肉从收纳盒里拿出来放到案板上,案板上的肉和盒子里的温度计不再联动。需要保持响应性时,用 toRefs 把属性批量变成 ref。

调味瓶虽要求拧盖,但瓶盖设计顺手;收纳盒虽能整盒保鲜,但拆开单独用就失联。认清两者的脾气,选型就不再纠结。

选择={ref,基本类型或需整体替换reactive,嵌套对象且无需解构\text{选择} = \begin{cases} \text{ref}, & \text{基本类型或需整体替换} \\ \text{reactive}, & \text{嵌套对象且无需解构} \end{cases}
这是一个“选型分段函数”:遇到基本类型或需要整体替换的值,优先用 ref;遇到深层嵌套对象且不需要解构出单个属性的场景,可以用 reactive。不确定时默认 ref,它内部也会自动套用 reactive 处理对象。
  1. 上分支$\text{ref}$ 对应基本类型,比如一个计数器 count、一段加载状态 isLoading。
    ref\text{ref}
  2. 下分支$\text{reactive}$ 对应对像,比如一个包含多个字段的表单模型。
    reactive\text{reactive}
  3. 选型经验场景满足上层条件走 ref,满足下层条件走 reactive,而实战中“默认 ref”是最稳妥的起点。
    默认 ref,对象亦可\text{默认 ref,对象亦可}

计算属性进阶

新厨房有个自动调味器:你告诉它“每 100 克肉配 15 克糖”,它不会每次重新称肉算糖,而是记住上次配比,只有当肉的分量真的变了,才重新算一遍。这就是 computed——带缓存的派生值。

computed 接收一个 getter 函数,返回派生值并缓存。只有 getter 依赖的响应式数据变化时,它才重新求值;否则每次访问都直接返回缓存,比普通方法高效。此外,computed 还可以传 { get, set } 对象,支持自定义 setter,实现“可写计算属性”,比如通过 v-model 绑定一个基于其它数据的派生值。而普通方法每次访问都会重新执行,没有缓存——就像每问一次盐量,厨师都要把整道菜重新尝一遍。

自动调味器把常用配比记在脑海里,省去重复尝菜的时间。这就是 computed 在长列表过滤、购物车合计等高频场景中又快又清晰的秘密。

y={ycache,依赖 d=doldf(d1,d2,…,dn),依赖变化y = \begin{cases} y_{\text{cache}}, & \text{依赖 } d = d_{\text{old}} \\ f(d_1, d_2, \dots, d_n), & \text{依赖变化} \end{cases}
这是 computed 的“缓存命中判断”:依赖集合没变,直接返回缓存值 y_cache,零开销;任一依赖变化,才调用计算函数 f 重新求值。
  1. 缓存分支当依赖集合 $d$ 与上次相同时,直接返回缓存值 $y_{\text{cache}}$,不需要任何计算。
    ycachey_{\text{cache}}
  2. 重算分支当任一依赖 $d_i$ 变化,调用函数 $f$ 对所有依赖重新求值。
    f(d1,d2,…,dn)f(d_1, d_2, \dots, d_n)
  3. 依赖追踪Vue 在 getter 执行时自动收集读取到的响应式数据,构成依赖集合,这是判断缓存是否有效的依据。
    d={d1,d2,…,dn}d = \{d_1, d_2, \dots, d_n\}

侦听器进阶用法

厨房里的“火候报警器”(watch):你设定“汤沸腾时调小火”,它盯着锅,一旦状态达到条件,立刻按约定行动。Vue3 的 watch 就是这样一个状态警报器。

watch 监听一个响应式数据源,当它的值变化时执行回调。相比 Vue2,Vue3 的 watch 更灵活:可以监听 ref、reactive 对象的某个属性、getter 函数,甚至数组。选项 { deep: true } 开启深层监听,{ immediate: true } 让回调在设置时就先执行一次。watchEffect 则不用显式指定数据源,凡是回调里用到的响应式数据变化,它都自动触发——适合“按需收集依赖”的场合,比如日志上报、接口联调。此外,watch 回调中可以用 onCleanup 清理上一次的异步副作用,比如防抖请求的定时器。

报警器不是你自己每隔五秒跑去看锅,而是锅自己告诉你“我开了”。watch 让组件对状态变化做出反应,而不是靠事件驱动反复检查。

trigger={1,vnew≠vold0,vnew=vold\text{trigger} = \begin{cases} 1, & v_{\text{new}} \neq v_{\text{old}} \\ 0, & v_{\text{new}} = v_{\text{old}} \end{cases}
这是 watch 的触发判定:新值不等于旧值时,触发器为 1,回调执行;新旧值相同时不触发,避免无意义的重复操作。
  1. 新值与旧值$v_{\text{new}}$ 是当前最新值,$v_{\text{old}}$ 是上一次的值,两者比较是触发的基础。
    vnew≠voldv_{\text{new}} \neq v_{\text{old}}
  2. 触发器条件成立时 $\text{trigger}=1$,watch 的回调被执行。
    trigger=1\text{trigger}=1
  3. 深层监听对于 reactive 对象,Vue 会隐式开启深度比较,所以嵌套属性变化也能被捕获——这是 watch 与手动轮询的本质区别。
    vnew=对象深层变化v_{\text{new}} = \text{对象深层变化}

自定义Hook封装

如果糖醋排骨在店里卖得最好,厨师不会每天重新研究配方,而是提前把秘制酱料做成“预制料理包”。自定义 Hook 就是这个料理包:把一段可复用的逻辑——状态、计算、监听、生命周期——打包进一个函数,随用随拆。

自定义 Hook 是 Composition API 最具复用性的设计。命名约定以 use 开头,内部可以使用 ref、computed、watch、生命周期钩子等任意组合式 API,返回需要暴露的状态和方法。比如 useCounter 封装计数逻辑,useMouse 封装鼠标坐标监听,useLocalStorage 封装本地存储同步。组件中调用一个 Hook,就像拆开一包料理包倒进锅里,几行代码就获得完整功能,还能在不同组件间共享同一份逻辑。

料理包不是越多越好,关键是每一包都摸准一种做菜场景。Hook 同理:从重复代码中提炼,而不是为封装而封装。

U=NreusedNtotal×100%U = \frac{N_{\text{reused}}}{N_{\text{total}}} \times 100\%
这个公式量化“Hook 复用率”:被多个组件复用的 Hook 数量除以项目里 Hook 总数,U 越高说明抽出的 Hook 越贴近真实业务,团队封装越健康。
  1. 分子$N_{\text{reused}}$ 是被两个及以上组件复用的自定义 Hook 数量。
    NreusedN_{\text{reused}}
  2. 分母$N_{\text{total}}$ 是项目中所有自定义 Hook 的总数量。
    NtotalN_{\text{total}}
  3. 复用率乘以 100% 把比值转为百分比,U 越高代表抽取越精准。
    U=NreusedNtotalU = \frac{N_{\text{reused}}}{N_{\text{total}}}

跨层级依赖注入

老厨房里,老板在最顶层,帮厨在最底层。一道新菜上线,老板要把食材从顶层传到 B1、B2,再到帮厨手里,中间每层都要转手——这就是 props 逐层透传的痛苦。provide/inject 则是老板装了一部传菜电梯:顶层直接供应,底层直接取用,中间楼层完全不用管。

在顶层组件的 setup 里,用 provide(key, value) 提供数据;任意后代组件用 inject(key, defaultValue) 消费数据。value 可以是任意值,包括 ref 和 reactive——这样传递的响应式数据在注入端也能实时更新。需要注意,provide/inject 主要用于跨多层级的共享,比如主题配置、用户登录态、全局偏好;它不是响应式数据流的替代品,业务数据流向仍建议保持清晰。

传菜电梯让顶层和底层直接对话,中间层不再当二传手。代码里少掉十几个无关 props,维护时也不用再一层层翻找。

E=DskipDtotalE = \frac{D_{\text{skip}}}{D_{\text{total}}}
这是“注入效率比”:跳过中间层数除以组件树总层数。E 越大,说明注入方案比逐层透传节省的传递成本越高,组件树越深收益越大。
  1. 分子$D_{\text{skip}}$ 是使用注入后跳过的不再参与传参的中间层级数。
    DskipD_{\text{skip}}
  2. 分母$D_{\text{total}}$ 是组件树从顶层到底层的总层级数。
    DtotalD_{\text{total}}
  3. 效率比$E$ 接近 1 说明几乎所有层级都被跳过,注入收益最大化;接近 0 则说明层级很浅,直接透传也不麻烦。
    E∈[0,1]E \in [0, 1]

AI 应用场景

AI 编程助手生成代码的整理与重构

阶段:AI 辅助前端开发

解决的问题:Copilot、ChatGPT 等 AI 工具生成的 Vue3 代码往往把逻辑平铺在 setup 里,缺少 Hook 意识,导致状态和方法混杂。利用 Composition API 的“按功能聚类”标准,开发者能迅速把 AI 输出重构为清晰的逻辑单元。

具体案例:开发智能客服工作台时,用 AI 生成用户登录、消息推送的初始代码,随后用 useAuth、useWebSocket 两个自定义 Hook 封装相关状态和副作用,组件体积减少约 40%,AI 生成的代码也从“能跑”升级为“好维护”。

LLM 流式输出的前端数据流管理

阶段:大模型应用状态管理

解决的问题:大模型流式返回 token 时,需要实时把增量文本渲染到页面上。传统的事件拼接方式容易漏帧、难清理。用 ref 包装流状态,用 computed 派生处理文本,用 watch 监听完成状态触发后续动作,整个数据流清晰可控。

具体案例:AI 对话页面中,const message = ref('') 接收各个流式 chunk,computed 计算累计字数与预计剩余时间,watch 监听 done 状态后自动滚动到底部。整个链路只用三个响应式 API 就搭好了。

AI 模型调用链路的可观测面板

阶段:AI 可观测性与监控

解决的问题:AI 服务的耗时、错误率需要实时上报,手动轮询和事件上报容易漏数据或过度开销。用 watch 监听请求状态池的变化,用自定义 Hook 封装 useModelMonitor,把上报逻辑收敛到一处。

具体案例:监控面板每秒收集 100 个请求的耗时分布,watch 聚合到图表数据源并自动刷新,配合 computed 计算 P95 分位数,帮助算法团队快速定位模型服务波动。

Python 演示

python105 行
# -*- coding: utf-8 -*-"""用 Python 模拟 Vue3 的 ref / computed / watchEffect 最小响应式系统。"""  current_effect = None  # 当前正在执行的副作用函数  class Ref:    """模拟 Vue3 ref:读取时收集依赖,写入时触发更新。"""     def __init__(self, value=0):        self._value = value        self._deps = set()     @property    def value(self):        if current_effect is not None:            self._deps.add(current_effect)        return self._value     @value.setter    def value(self, new_value):        if new_value != self._value:            self._value = new_value            for effect in tuple(self._deps):                effect()  def watch_effect(fn):    """模拟 watchEffect:立即运行一次并在依赖变化时自动重跑。"""     def run():        global current_effect        current_effect = run        try:            fn()        finally:            current_effect = None     run()    return run  def watch(source_ref, callback):    """简易 watch:只在值变化后执行回调。"""     last = {"value": source_ref.value}     def run():        current = source_ref.value        if current != last["value"]:            last["value"] = current            callback(current)     watch_effect(run)    return callback  class Computed:    """模拟 Vue3 computed:有缓存,依赖变化才重新求值。"""     def __init__(self, compute_fn):        self._fn = compute_fn        self._cache = None        self._dirty = True        self._subs = set()        global current_effect        old_effect = current_effect         def mark_dirty():            self._dirty = True            for effect in tuple(self._subs):                effect()         current_effect = mark_dirty        try:            self._fn()  # 读取依赖,注册 mark_dirty        finally:            current_effect = old_effect     @property    def value(self):        global current_effect        if current_effect is not None:            self._subs.add(current_effect)        if self._dirty:            self._cache = self._fn()            self._dirty = False        return self._cache  if __name__ == "__main__":    price = Ref(10)    quantity = Ref(3)    total = Computed(lambda: price.value * quantity.value)     def on_total_change():        print("total changed ->", total.value)     watch_effect(on_total_change)    watch(price, lambda v: print("price ->", v))     print("initial total:", total.value)    price.value = 20    quantity.value = 5
目的:用纯 Python 实现一个极简的响应式引擎,演示 Vue3 中 ref、computed、watchEffect 三者如何协作——读取时收集依赖、写入时触发更新。
  1. current_effect = None全局变量,记录当前正在执行的副作用函数,这是实现依赖收集的核心机制。
  2. self._deps.add(current_effect)读取 ref.value 时把当前 effect 记入依赖集合,对应 Vue 的 track 操作。
  3. for effect in tuple(self._deps): effect()写入新值时遍历依赖集合逐个触发,对应 Vue 的 trigger 操作。
  4. watch_effect(on_total_change)立即执行一次回调,并自动追踪其中读取到的所有响应式数据。
  5. last = {"value": source_ref.value}watch 内部记录旧值,新旧比对后才调用回调,避免初始重复触发。
  6. self._subs.add(current_effect)computed 自己也有订阅者集合,实现“依赖的依赖”多级联动。

常见误区

误区听说 reactive 能处理对象,就觉得它比 ref 更高级、更全面,于是所有状态都用 reactive 包一层,包括数字和字符串。

正解reactive 只能接收对象、数组等引用类型,传基本类型会直接报错;解构出来的属性还会丢失响应性。ref 才是更通用的容器,它内部对对象也会自动套用 reactive。实战选型应该“默认 ref,对象也可用 ref,确需整体代理对象时用 reactive”。

为何常见:不少教程示例喜欢用 reactive({ ... }) 展示对象响应式,给人留下“reactive 才是正规写法”的错觉,忽略了两种 API 各自的边界。

误区在 JS 逻辑中要用 `count.value` 读取 ref,于是模板里也写 `{{ count.value }}`,认为不写就不响应。

正解在 script setup 中,ref 在模板里会自动解包,直接写 {{ count }} 即可获得数值。模板中写 count.value 反而会先在解包后的值上取 value 属性,得到 undefined。只有 JS 逻辑中才需要显式 .value。

为何常见:初学者刚学会 JS 中的 .value 用法,把上下文直接迁移到模板,忽略了语法糖层级的自动解包机制。

误区遇到需要计算的场景就无脑用 computed,包括异步请求、定时器、随机数生成也塞进 getter,觉得反正都是“算一个值”。

正解computed 必须同步返回一个值,并且应当是“纯函数”——不产生副作用。异步请求放进 getter 不会得到正确更新,随机数会让依赖追踪失真,反复触发重算。异步派生值应该用 watch + ref 或自定义 Hook 管理。

为何常见:computed 用起来很顺手,很多人把它当作普通函数的替代品,忽略了它对“同步”与“纯净”的隐含要求。

误区把普通字符串或静态对象 `provide` 出去,子组件 `inject` 后直接使用,发现数据更新时子组件不跟着变,还以为是 bug。

正解provide 的普通值在注入端只是初始快照,不会随源端更新。要实现响应式共享,provide 应当传一个 ref 或 reactive 对象,注入端通过 .value 或属性访问最新值。

为何常见:Vue2 时代的 provide/inject 本身就不是响应式的,带来的惯性思维让一部分开发者忽略了 Vue3 中“传引用类型才能联动”的关键细节。

面试题

Q1. Vue3 Composition API 的入口函数是以下哪个?

单选题 · easy

  • A.created() 钩子函数
  • B.setup() 入口函数← 正确答案
  • C.mounted() 钩子函数
  • D.data() 方法函数

答案:B。Composition API 的一切逻辑都从 setup() 开始,它取代了 Options API 中分散的 data、computed、methods、生命周期选项。created 和 mounted 是生命周期钩子,data 是声明数据的方法,都不作为入口。

Q2. ref 和 reactive 的主要区别是什么?

单选题 · easy

  • A.ref 包装基本类型,reactive 包装对象← 正确答案
  • B.两者语义完全相同可以任意互换
  • C.ref 只能用于字符串和布尔类型
  • D.reactive 缺少深层响应式能力

答案:A。ref 主要包装基本类型,读取修改要 .value;reactive 只能包装对象/数组,访问属性直接取值,且默认深度代理。B 显然错误,C 忽略了 ref 也可以包对象,D 是常识性错误。

Q3. 在 script setup 中引入组件后如何使用?

单选题 · mid

  • A.需要 components 选项手动注册
  • B.引入后直接使用无需注册← 正确答案
  • C.只能通过动态组件方式使用
  • D.必须用 defineComponent 包裹

答案:B。script setup 语法糖会自动把 import 的组件暴露给模板,无需 components 注册,也无需 defineComponent 包裹。组件引入即用,是语法糖最直观的体验提升。

Q4. computed 相比普通方法的核心优势是什么?

单选题 · mid

  • A.代码更短但性能没有差别
  • B.依赖不变时可读取缓存结果← 正确答案
  • C.每次访问都会强制重新计算
  • D.只能用来修改 data 里的值

答案:B。computed 会追踪依赖并缓存结果,依赖不变时多次访问零开销。普通方法每次渲染都会重新执行。A 说“没有差别”错误,C 是普通方法的行为,D 完全误解了 computed 的用途。

Q5. 关于 watch 侦听器,哪些说法是正确的?

多选题 · mid

  • A.deep 选项可监听对象深层变化← 正确答案
  • B.immediate 让回调立即执行一次← 正确答案
  • C.监听 ref 时需要加 .value 后缀
  • D.watchEffect 无需指定监听目标← 正确答案

答案:ABD。deep: true 开启深层监听,immediate: true 让回调立即执行一次,watchEffect 自动追踪回调内使用的数据。C 错误,监听 ref 直接传 ref 对象即可,不需要 .value。

Q6. 自定义 Hook 的命名必须以什么开头?

单选题 · hard

  • A.use 前缀以关联组合式 API 约定← 正确答案
  • B.setup 前缀以区分普通函数
  • C.watch 前缀以方便依赖追踪
  • D.get 前缀以表示取值逻辑

答案:A。use 前缀是 Vue3 社区的命名约定,用于标识“内部使用组合式 API 的复用函数”,也让 lint 工具能识别它应当只在 setup 上下文中调用。其余前缀没有这种约定支撑。

Q7. 关于 provide/inject 的特点,哪些描述是准确的?

多选题 · hard

  • A.解决深层组件属性透传问题← 正确答案
  • B.值可以传递 ref 保持响应性← 正确答案
  • C.只允许根组件向孙组件传递
  • D.inject 只能传递静态字符串

答案:AB。provide/inject 专门解决跨层级透传,且任意祖先都可 provide,值可以是 ref/reactive。C 错在“只允许根组件”,任意层级的组件都能 provide;D 错在 inject 接受任意值类型。

Q8. reactive 解构后为什么丢失响应性?

单选题 · easy

  • A.解构得到普通值不再被代理← 正确答案
  • B.JavaScript 不支持对象解构
  • C.解构会自动复制整个原始对象
  • D.reactive 本身就不是响应式的

答案:A。reactive 基于 Proxy 代理对象本身,解构操作会把属性值复制成普通变量带走,这些变量不再经过代理,因此失去响应性。需要保持响应性时用 toRefs 转换。

Q9. script setup 中声明组件 props 的正确方式是?

单选题 · mid

  • A.使用 defineProps 编译器宏← 正确答案
  • B.在 components 选项中注册
  • C.在 data 里初始化同名属性
  • D.在 emits 配置里声明事件

答案:A。defineProps 是 script setup 的编译器宏,无需导入即可声明 props 类型与默认值。components 选项用于注册组件,data 用于普通状态,emits 用于声明事件,均与 props 声明无关。

Q10. 关于 Vue3 响应式原理,哪些描述是正确的?

多选题 · extreme

  • A.ref 底层对对象使用 reactive 代理← 正确答案
  • B.模板中 ref 变量会自动解包← 正确答案
  • C.watchEffect 需要显式声明依赖
  • D.Proxy 可以监听属性新增操作← 正确答案

答案:ABD。ref 包装对象时内部会调用 reactive;模板中 ref 自动解包;Proxy 支持监听属性新增和删除。C 错误,watchEffect 不需要声明依赖,它会自动追踪回调内读取到的响应式数据,这正是它和 watch 的最大区别。