先记住这个答案
可通过清楚参数传入的依赖,通常直接交给组合式函数,调用关系显式且容易测试。多个深层组件需要同一子树上下文时,可以由祖先 provide,后代 inject,再用组合式函数封装读取和动作接口。它们不是互相替代的两种状态库:composable 不天然共享状态,provide/inject 也不会自动为每个调用创建副本。选择时要确定依赖所有者、共享范围、最近提供者覆盖以及缺失时的处理。
- 显式参数适合边界清楚、可独立测试的逻辑依赖
- provide/inject 适合组件子树共享上下文,避免逐层转交
- 可以用 composable 封装注入,但应保留依赖存在性的检查
什么情况下直接传参更清楚
例如排序逻辑只需要一个列表和比较函数,显式参数已经足够,不需要为了复用额外引入组件树依赖。调用者能看出输入来源,测试也能用任意数据调用。把这种函数内部改成偷偷 inject,会让它只能在特定组件环境中工作,增加不必要限制。
多个组合式函数互相协作时,也可以把一个返回的 ref 或动作传给另一个。这种依赖表达适合范围不深、调用点明确的场景。重要的是保持参数契约,说明输入是固定值还是需要持续追踪的来源。
表单上下文为何适合子树提供
一个表单下面可能有布局、分组和多个字段组件,中间层并不使用字段注册、错误状态等能力,却需要逐层转交。由表单根提供上下文,字段通过 inject 获取,可以减少这种中转。页面同时放两个表单时,各自提供者还能形成独立的子树状态。
同一个键存在多个祖先提供者时,后代通常取得最近的一个。因此嵌套表单或局部覆盖需要清楚约定,不能把 inject 理解成始终读取全局单例。使用 Symbol 或类型化注入键有助于区分接口,但不会自动解决提供者范围错误。
封装注入时保留可诊断性
可以用 useFormContext 统一读取依赖,并在必需提供者缺失时给出清楚错误;可选依赖则提供明确默认行为。不要返回一个看似完整的空对象,让字段在很晚之后才因调用不存在的方法失败。测试应覆盖正常提供、缺失提供和嵌套覆盖。
提供可写 ref 会让后代拥有直接修改能力,若更新需要统一规则,可提供只读视图和动作。SSR 中仍应按应用或请求创建上下文,不能把模块共享状态换个 inject 包装就称为隔离。组合式函数负责接口组织,真正的数据归属需要在提供者创建处落实。
容易答错的地方
- 用了 composable 就不需要 provide/inject
- 逻辑封装与依赖传递服务不同问题,深层共享上下文可以由注入提供,再由 composable 封装。
- inject 会给每个组件复制一份状态
- 通常拿到的是提供的值或引用,是否独立由提供者范围和状态创建方式决定。排查“Vue composable 参数传递与 provide inject 选型”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
面试官还会怎么问?
祖先提供普通对象会自动变响应式吗?
不会只因 provide 就自动获得完整响应式能力,需要提供合适的 ref 或 reactive 来源。
注入 ref 后如何保持关联?
Vue 注入的 ref 保留引用关系,脚本按 ref 的方式读取和使用,避免先取出原始值后误认为仍能持续追踪。
如何兼顾方便使用与独立测试?
将纯逻辑保留为接收参数的函数,再提供一个在组件中读取注入并调用它的薄封装,可以分别测试逻辑和上下文连接。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。