找回密码
 立即注册
首页 业界区 业界 面试官:来谈谈Vue3的provide和inject实现多级传递的原 ...

面试官:来谈谈Vue3的provide和inject实现多级传递的原理

邹弘丽 2025-6-6 10:24:44
前言

没有看过provide和inject函数源码的小伙伴可能觉得他们实现数据多级传递非常神秘,其实他的源码非常简单,这篇文章欧阳来讲讲provide和inject函数是如何实现数据多级传递的。ps:本文中使用的Vue版本为3.5.13。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
看个demo

先来看个demo,这个是父组件,代码如下:
  1. <template>
  2. <template>
  3.   <GrandChild />
  4. </template><ChildDemo />
  5. </template>
复制代码
在父组件中使用provide为后代组件注入一个count响应式变量。
再来看看子组件child.vue代码如下:
  1. <template>
  2. <template>
  3.   <GrandChild />
  4. </template><ChildDemo />
  5. </template>
复制代码
从上面的代码可以看到在子组件中什么事情都没做,只渲染了孙子组件。
我们再来看看孙子组件grand-child.vue,代码如下:
  1. [/code]从上面的代码可以看到在孙子组件中使用inject函数拿到了父组件中注入的count响应式变量。
  2. [size=6]provide函数[/size]
  3. 我们先来debug看看provide函数的代码,给父组件中的provide函数打个断点,如下图:
  4. [align=center] 1.png [/align]
  5. 刷新页面,此时代码将会停留在断点处。让断点走进provide函数,代码如下:
  6. [code]function provide(key, value) {<template>
  7. <template>
  8.   <GrandChild />
  9. </template><ChildDemo />
  10. </template>if (!currentInstance) {<template>
  11. <template>
  12.   <GrandChild />
  13. </template><ChildDemo />
  14. </template><template>
  15. <template>
  16.   <GrandChild />
  17. </template><ChildDemo />
  18. </template>if (!!(process.env.NODE_ENV !== "production")) {<template>
  19. <template>
  20.   <GrandChild />
  21. </template><ChildDemo />
  22. </template><template>
  23. <template>
  24.   <GrandChild />
  25. </template><ChildDemo />
  26. </template><template>
  27. <template>
  28.   <GrandChild />
  29. </template><ChildDemo />
  30. </template>warn$1(`provide() can only be used inside setup().`);<template>
  31. <template>
  32.   <GrandChild />
  33. </template><ChildDemo />
  34. </template><template>
  35. <template>
  36.   <GrandChild />
  37. </template><ChildDemo />
  38. </template>}<template>
  39. <template>
  40.   <GrandChild />
  41. </template><ChildDemo />
  42. </template>} else {<template>
  43. <template>
  44.   <GrandChild />
  45. </template><ChildDemo />
  46. </template><template>
  47. <template>
  48.   <GrandChild />
  49. </template><ChildDemo />
  50. </template>let provides = currentInstance.provides;<template>
  51. <template>
  52.   <GrandChild />
  53. </template><ChildDemo />
  54. </template><template>
  55. <template>
  56.   <GrandChild />
  57. </template><ChildDemo />
  58. </template>const parentProvides = currentInstance.parent && currentInstance.parent.provides;<template>
  59. <template>
  60.   <GrandChild />
  61. </template><ChildDemo />
  62. </template><template>
  63. <template>
  64.   <GrandChild />
  65. </template><ChildDemo />
  66. </template>if (parentProvides === provides) {<template>
  67. <template>
  68.   <GrandChild />
  69. </template><ChildDemo />
  70. </template><template>
  71. <template>
  72.   <GrandChild />
  73. </template><ChildDemo />
  74. </template><template>
  75. <template>
  76.   <GrandChild />
  77. </template><ChildDemo />
  78. </template>provides = currentInstance.provides = Object.create(parentProvides);<template>
  79. <template>
  80.   <GrandChild />
  81. </template><ChildDemo />
  82. </template><template>
  83. <template>
  84.   <GrandChild />
  85. </template><ChildDemo />
  86. </template>}<template>
  87. <template>
  88.   <GrandChild />
  89. </template><ChildDemo />
  90. </template><template>
  91. <template>
  92.   <GrandChild />
  93. </template><ChildDemo />
  94. </template>provides[key] = value;<template>
  95. <template>
  96.   <GrandChild />
  97. </template><ChildDemo />
  98. </template>}}
复制代码
首先判断currentInstance是否有值,如果没有就说明当前没有vue实例,也就是说当前调用provide函数的地方是不在setup函数中执行的,然后给出警告provide只能在setup中使用。
然后走进else逻辑中,首先从当前vue实例中取出存的provides属性对象。并且通过currentInstance.parent.provides拿到父组件vue实例中的provides属性对象。
这里为什么需要判断if (parentProvides === provides)呢?
因为在创建子组件时会默认使用父组件的provides属性对象作为父组件的provides属性对象。代码如下:
  1. const instance: ComponentInternalInstance = {<template>
  2. <template>
  3.   <GrandChild />
  4. </template><ChildDemo />
  5. </template>uid: uid++,<template>
  6. <template>
  7.   <GrandChild />
  8. </template><ChildDemo />
  9. </template>vnode,<template>
  10. <template>
  11.   <GrandChild />
  12. </template><ChildDemo />
  13. </template>type,<template>
  14. <template>
  15.   <GrandChild />
  16. </template><ChildDemo />
  17. </template>parent,<template>
  18. <template>
  19.   <GrandChild />
  20. </template><ChildDemo />
  21. </template>provides: parent ? parent.provides : Object.create(appContext.provides),<template>
  22. <template>
  23.   <GrandChild />
  24. </template><ChildDemo />
  25. </template>// ...省略}       
复制代码
从上面的代码可以看到如果有父组件,那么创建子组件实例的时候就直接使用父组件的provides属性对象。
所以这里在provide函数中需要判断if (parentProvides === provides),如果相等说明当前父组件和子组件是共用的同一个provides属性对象。此时如果子组件调用了provide函数,说明子组件需要创建自己的provides属性对象。
并且新的属性对象还需要能够访问到父组件中注入的内容,所以这里以父组件的provides属性对象为原型去创建一个新的子组件的,这样在子组件中不仅能够访问到原型链中注入的provides属性对象,也能够访问到自己注入进去的provides属性对象。
最后就是执行provides[key] = value将当前注入的内容存到provides属性对象中。
inject函数

我们再来看看inject函数是如何隔了一层子组件从父组件中如何取出数据的,还是一样的套路,给孙子组件中的inject函数打个断点。如下图:
2.png

将断点走进inject函数,代码如下:
  1. export function inject(<template>
  2. <template>
  3.   <GrandChild />
  4. </template><ChildDemo />
  5. </template>key: InjectionKey | string,<template>
  6. <template>
  7.   <GrandChild />
  8. </template><ChildDemo />
  9. </template>defaultValue?: unknown,<template>
  10. <template>
  11.   <GrandChild />
  12. </template><ChildDemo />
  13. </template>treatDefaultAsFactory = false,) {<template>
  14. <template>
  15.   <GrandChild />
  16. </template><ChildDemo />
  17. </template>// fallback to `currentRenderingInstance` so that this can be called in<template>
  18. <template>
  19.   <GrandChild />
  20. </template><ChildDemo />
  21. </template>// a functional component<template>
  22. <template>
  23.   <GrandChild />
  24. </template><ChildDemo />
  25. </template>const instance = currentInstance || currentRenderingInstance<template>
  26. <template>
  27.   <GrandChild />
  28. </template><ChildDemo />
  29. </template>// also support looking up from app-level provides w/ `app.runWithContext()`<template>
  30. <template>
  31.   <GrandChild />
  32. </template><ChildDemo />
  33. </template>if (instance || currentApp) {<template>
  34. <template>
  35.   <GrandChild />
  36. </template><ChildDemo />
  37. </template><template>
  38. <template>
  39.   <GrandChild />
  40. </template><ChildDemo />
  41. </template>const provides = currentApp<template>
  42. <template>
  43.   <GrandChild />
  44. </template><ChildDemo />
  45. </template><template>
  46. <template>
  47.   <GrandChild />
  48. </template><ChildDemo />
  49. </template><template>
  50. <template>
  51.   <GrandChild />
  52. </template><ChildDemo />
  53. </template>? currentApp._context.provides<template>
  54. <template>
  55.   <GrandChild />
  56. </template><ChildDemo />
  57. </template><template>
  58. <template>
  59.   <GrandChild />
  60. </template><ChildDemo />
  61. </template><template>
  62. <template>
  63.   <GrandChild />
  64. </template><ChildDemo />
  65. </template>: instance<template>
  66. <template>
  67.   <GrandChild />
  68. </template><ChildDemo />
  69. </template><template>
  70. <template>
  71.   <GrandChild />
  72. </template><ChildDemo />
  73. </template><template>
  74. <template>
  75.   <GrandChild />
  76. </template><ChildDemo />
  77. </template><template>
  78. <template>
  79.   <GrandChild />
  80. </template><ChildDemo />
  81. </template>? instance.parent == null<template>
  82. <template>
  83.   <GrandChild />
  84. </template><ChildDemo />
  85. </template><template>
  86. <template>
  87.   <GrandChild />
  88. </template><ChildDemo />
  89. </template><template>
  90. <template>
  91.   <GrandChild />
  92. </template><ChildDemo />
  93. </template><template>
  94. <template>
  95.   <GrandChild />
  96. </template><ChildDemo />
  97. </template><template>
  98. <template>
  99.   <GrandChild />
  100. </template><ChildDemo />
  101. </template>? instance.vnode.appContext && instance.vnode.appContext.provides<template>
  102. <template>
  103.   <GrandChild />
  104. </template><ChildDemo />
  105. </template><template>
  106. <template>
  107.   <GrandChild />
  108. </template><ChildDemo />
  109. </template><template>
  110. <template>
  111.   <GrandChild />
  112. </template><ChildDemo />
  113. </template><template>
  114. <template>
  115.   <GrandChild />
  116. </template><ChildDemo />
  117. </template><template>
  118. <template>
  119.   <GrandChild />
  120. </template><ChildDemo />
  121. </template>: instance.parent.provides<template>
  122. <template>
  123.   <GrandChild />
  124. </template><ChildDemo />
  125. </template><template>
  126. <template>
  127.   <GrandChild />
  128. </template><ChildDemo />
  129. </template><template>
  130. <template>
  131.   <GrandChild />
  132. </template><ChildDemo />
  133. </template><template>
  134. <template>
  135.   <GrandChild />
  136. </template><ChildDemo />
  137. </template>: undefined<template>
  138. <template>
  139.   <GrandChild />
  140. </template><ChildDemo />
  141. </template><template>
  142. <template>
  143.   <GrandChild />
  144. </template><ChildDemo />
  145. </template>if (provides && key in provides) {<template>
  146. <template>
  147.   <GrandChild />
  148. </template><ChildDemo />
  149. </template><template>
  150. <template>
  151.   <GrandChild />
  152. </template><ChildDemo />
  153. </template><template>
  154. <template>
  155.   <GrandChild />
  156. </template><ChildDemo />
  157. </template>return provides[key]<template>
  158. <template>
  159.   <GrandChild />
  160. </template><ChildDemo />
  161. </template><template>
  162. <template>
  163.   <GrandChild />
  164. </template><ChildDemo />
  165. </template>} else if (arguments.length > 1) {<template>
  166. <template>
  167.   <GrandChild />
  168. </template><ChildDemo />
  169. </template><template>
  170. <template>
  171.   <GrandChild />
  172. </template><ChildDemo />
  173. </template><template>
  174. <template>
  175.   <GrandChild />
  176. </template><ChildDemo />
  177. </template>return treatDefaultAsFactory && isFunction(defaultValue)<template>
  178. <template>
  179.   <GrandChild />
  180. </template><ChildDemo />
  181. </template><template>
  182. <template>
  183.   <GrandChild />
  184. </template><ChildDemo />
  185. </template><template>
  186. <template>
  187.   <GrandChild />
  188. </template><ChildDemo />
  189. </template><template>
  190. <template>
  191.   <GrandChild />
  192. </template><ChildDemo />
  193. </template>? defaultValue.call(instance && instance.proxy)<template>
  194. <template>
  195.   <GrandChild />
  196. </template><ChildDemo />
  197. </template><template>
  198. <template>
  199.   <GrandChild />
  200. </template><ChildDemo />
  201. </template><template>
  202. <template>
  203.   <GrandChild />
  204. </template><ChildDemo />
  205. </template><template>
  206. <template>
  207.   <GrandChild />
  208. </template><ChildDemo />
  209. </template>: defaultValue<template>
  210. <template>
  211.   <GrandChild />
  212. </template><ChildDemo />
  213. </template><template>
  214. <template>
  215.   <GrandChild />
  216. </template><ChildDemo />
  217. </template>} else if (__DEV__) {<template>
  218. <template>
  219.   <GrandChild />
  220. </template><ChildDemo />
  221. </template><template>
  222. <template>
  223.   <GrandChild />
  224. </template><ChildDemo />
  225. </template><template>
  226. <template>
  227.   <GrandChild />
  228. </template><ChildDemo />
  229. </template>warn(`injection "${String(key)}" not found.`)<template>
  230. <template>
  231.   <GrandChild />
  232. </template><ChildDemo />
  233. </template><template>
  234. <template>
  235.   <GrandChild />
  236. </template><ChildDemo />
  237. </template>}<template>
  238. <template>
  239.   <GrandChild />
  240. </template><ChildDemo />
  241. </template>} else if (__DEV__) {<template>
  242. <template>
  243.   <GrandChild />
  244. </template><ChildDemo />
  245. </template><template>
  246. <template>
  247.   <GrandChild />
  248. </template><ChildDemo />
  249. </template>warn(`inject() can only be used inside setup() or functional components.`)<template>
  250. <template>
  251.   <GrandChild />
  252. </template><ChildDemo />
  253. </template>}}
复制代码
首先拿到当前渲染的vue实例赋值给本地变量instance。接着使用if (instance || currentApp)判断当前是否有vue实例,如果没有看看有没有使用app.runWithContext手动注入了上下文,如果注入了那么currentApp就有值。
接着就是一串三元表达式,如果使用app.runWithContext手动注入了上下文,那么就优先从注入的上下文中取出provides属性对象。
如果没有那么就看当前组件是否满足instance.parent == null,也就是说当前组件是否是根节点。如果是根节点就取app中注入的provides属性对象。
如果上面的都不满足就去取父组件中注入的provides属性对象,前面我们讲过了在inject函数阶段,如果子组件内没有使用inject函数,那么就会直接使用父组件的provides属性对象。如果子组件中使用了inject函数,那么就以父组件的provides属性对象为原型去创建一个新的子组件的provides属性对象,从而形成一条原型链。
所以这里的孙子节点的provides属性对象中当然就能够拿到父组件中注入的count响应式变量,那么if (provides && key in provides)就满足条件,最后会走到return provides[key]中将父组件中注入的响应式变量count原封不动的返回。
还有就是如果我们inject一个没有使用provide存入的key,并且传入了第二个参数defaultValue,此时else if (arguments.length > 1)就满足条件了。
在里面会去判断是否传入第三个参数treatDefaultAsFactory,如果这个参数的值为true,说明第二个参数defaultValue可能是一个工厂函数。那么就执行defaultValue.call(instance && instance.proxy)将defaultValue的当中工厂函数的执行结果进行返回。
如果第三个参数treatDefaultAsFactory的值不为true,那么就直接将第二个参数defaultValue当做默认值返回。
总结

这篇文章讲了使用provide和inject函数是如何实现数据多级传递的。
在创建vue组件实例时,子组件的provides属性对象会直接使用父组件的provides属性对象。如果在子组件中使用了provide函数,那么会以父组件的provides属性对象为原型创建一个新的provides属性对象,并且将provide函数中注入的内容塞到新的provides属性对象中,从而形成了原型链。
在孙子组件中,他的parent就是子组件。前面我们讲过了如果没有在组件内使用provide注入东西(很明显这里的子组件确实没有注入任何东西),那么就会直接使用他的父组件的provides属性对象,所以这里的子组件是直接使用的是父组件中的provides属性对象。所以在孙子组件中可以直接使用inject函数拿到父组件中注入的内容。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
3.jpeg

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册