当前位置:   article > 正文

Vue3 源码阅读(10):组件化 —— 实现原理_vue3组件化开发

vue3组件化开发

 我的开源库:

组件是对页面中内容的一种模块化封装,这篇博客讲解 Vue 中组件化的实现方式。

1,Vue 中组件的定义方式

在讲解组件化的实现原理前,首先说明在 Vue 中,一个组件的本质到底是什么。Vue 中有两种形式的组件,分别是有状态组件和无状态组件,有状态组件的本质是一个对象字面量,无状态组件的本质是一个函数。也就是说,在 Vue 中,组件的本质是一个对象字面量或者一个函数,这里以有状态组件进行讲解。

2,VNode 简介

VNode 的本质就是一个普通的对象字面量,只不过这个对象字面量能够很好的描述真实 DOM,通过 VNode 可以渲染出页面中真实的 DOM。

VNode 是通过组件的 render 函数创建出来的,我们平时在开发中,一般都是使用 template 字符串描述页面内容,这个模板字符串会被 Vue 的编译器编译成 render 函数,所以在 Vue 的运行时,用于描述组件渲染内容的是 render 函数。

下面展示一下 Vue3 中 VNode 的 TypeScript 的类型定义:

  1. export interface VNode<
  2. HostNode = RendererNode,
  3. HostElement = RendererElement,
  4. ExtraProps = { [key: string]: any }
  5. > {
  6. __v_isVNode: true
  7. [ReactiveFlags.SKIP]: true
  8. type: VNodeTypes
  9. props: (VNodeProps & ExtraProps) | null
  10. key: string | number | symbol | null
  11. ref: VNodeNormalizedRef | null
  12. scopeId: string | null
  13. slotScopeIds: string[] | null
  14. children: VNodeNormalizedChildren
  15. component: ComponentInternalInstance | null
  16. dirs: DirectiveBinding[] | null
  17. transition: TransitionHooks<HostElement> | null
  18. el: HostNode | null
  19. anchor: HostNode | null // fragment anchor
  20. target: HostElement | null // teleport target
  21. targetAnchor: HostNode | null // teleport target anchor
  22. staticCount: number
  23. suspense: SuspenseBoundary | null
  24. ssContent: VNode | null
  25. ssFallback: VNode | null
  26. shapeFlag: number
  27. patchFlag: number
  28. dynamicProps: string[] | null
  29. dynamicChildren: VNode[] | null
  30. appContext: AppContext | null
  31. memo?: any[]
  32. isCompatRoot?: true
  33. ce?: (instance: ComponentInternalInstance) => void
  34. }

VNode 对象的属性还是很多的,这里不用看这么多,先关注一下 type 属性。

  1. export interface VNode<
  2. HostNode = RendererNode,
  3. HostElement = RendererElement,
  4. ExtraProps = { [key: string]: any }
  5. > {
  6. type: VNodeTypes
  7. }
  8. export type VNodeTypes =
  9. | string
  10. | VNode
  11. | Component
  12. | typeof Text
  13. | typeof Static
  14. | typeof Comment
  15. | typeof Fragment
  16. | typeof TeleportImpl
  17. | typeof SuspenseImpl

type 属性用于描述 VNode 的类型,VNode 的类型有很多种,这里我们看下 string 和 Component 类型,当 VNode 的 type 属性是字符串的时候,说明当前的 VNode 描述的是普通的元素,当 VNode 的 type 是 Component 的时候,说明当前的 VNode 描述的是一个组件。

假设我们的 Vue 中有一个 MyComponent 组件,我们在一个模板字符串中使用了这个组件,代码如下所示:

  1. <template>
  2. <MyComponent></MyComponent>
  3. </template>

上面的模板字符串会被编译成一个 render 函数,render 函数执行返回一个 VNode,这个 VNode 是一个组件类型的 VNode,表明需要渲染一个组件。

  1. componentVNode = {
  2. type: {
  3. ...组件的定义对象...
  4. },
  5. ......
  6. }

有了组件类型的 VNode,接下来看看这个组件 VNode 是如何渲染和更新的。

3,组件的挂载和更新

组件挂载和更新的逻辑都写在渲染器中,我们直接看源码。

  1. const patch: PatchFn = (
  2. n1,
  3. n2,
  4. container,
  5. anchor = null,
  6. parentComponent = null,
  7. parentSuspense = null,
  8. isSVG = false,
  9. slotScopeIds = null,
  10. optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  11. ) => {
  12. // 解构获取 n2 的 type、ref、shapeFlag
  13. const { type, ref, shapeFlag } = n2
  14. // 根据 n2 Vnode 的类型进行不同的处理
  15. switch (type) {
  16. ......
  17. default:
  18. if (shapeFlag & ShapeFlags.ELEMENT) {
  19. // 处理元素节点
  20. processElement()
  21. } else if (shapeFlag & ShapeFlags.COMPONENT) {
  22. // 处理组件节点
  23. processComponent(
  24. n1,
  25. n2,
  26. container,
  27. anchor,
  28. parentComponent,
  29. parentSuspense,
  30. isSVG,
  31. slotScopeIds,
  32. optimized
  33. )
  34. } else if (__DEV__) {
  35. // 如果以上条件都不满足,并且是在开发模式下的话,则打印出相关警告:违法的 vnode 类型
  36. warn('Invalid VNode type:', type, `(${typeof type})`)
  37. }
  38. }
  39. }

在 patch 函数中,会根据 VNode 类型的不同使用不同的函数进行处理,如果当前的 VNode 表示的是组件的话,则会使用 processComponent 函数进行处理,processComponent 函数的内容如下所示:

  1. const processComponent = (
  2. n1: VNode | null,
  3. n2: VNode,
  4. container: RendererElement,
  5. anchor: RendererNode | null,
  6. parentComponent: ComponentInternalInstance | null,
  7. parentSuspense: SuspenseBoundary | null,
  8. isSVG: boolean,
  9. slotScopeIds: string[] | null,
  10. optimized: boolean
  11. ) => {
  12. if (n1 == null) {
  13. mountComponent(
  14. n2,
  15. container,
  16. anchor,
  17. parentComponent,
  18. parentSuspense,
  19. isSVG,
  20. optimized
  21. )
  22. } else {
  23. updateComponent(n1, n2, optimized)
  24. }
  25. }

在这里,判断 oldVNode 是否存在,如果存在的话,则执行 updateComponent 函数进行组件的更新,如果不存在的话,则执行 mountComponent 函数进行组件的挂载,我们首先看组件的挂载。

3-1,组件的挂载

  1. // 挂载组件节点
  2. const mountComponent: MountComponentFn = (
  3. initialVNode,
  4. container,
  5. anchor,
  6. parentComponent,
  7. parentSuspense,
  8. isSVG,
  9. optimized
  10. ) => {
  11. // 创建组件实例对象
  12. const compatMountInstance =
  13. __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
  14. const instance: ComponentInternalInstance =
  15. compatMountInstance ||
  16. (initialVNode.component = createComponentInstance(
  17. initialVNode,
  18. parentComponent,
  19. parentSuspense
  20. ))
  21. // resolve props and slots for setup context
  22. // 解析初始化一些数据
  23. if (!(__COMPAT__ && compatMountInstance)) {
  24. setupComponent(instance)
  25. }
  26. setupRenderEffect(
  27. instance,
  28. initialVNode,
  29. container,
  30. anchor,
  31. parentSuspense,
  32. isSVG,
  33. optimized
  34. )
  35. }

在 mountComponent 函数中,首先创建组件的实例,每渲染一次组件,就会创建一个对应的实例,组件实例就是一个对象,这个对象维护着组件运行过程中的所有信息,例如:注册的生命周期函数、组件上次渲染的 VNode,组件状态等等。一个组件实例的内容如下所示:

  1. const instance: ComponentInternalInstance = {
  2. uid: uid++,
  3. vnode,
  4. type,
  5. parent,
  6. appContext,
  7. root: null!, // to be immediately set
  8. next: null,
  9. subTree: null!, // will be set synchronously right after creation
  10. effect: null!,
  11. update: null!, // will be set synchronously right after creation
  12. scope: new EffectScope(true /* detached */),
  13. render: null,
  14. proxy: null,
  15. exposed: null,
  16. exposeProxy: null,
  17. withProxy: null,
  18. provides: parent ? parent.provides : Object.create(appContext.provides),
  19. accessCache: null!,
  20. renderCache: [],
  21. // local resolved assets
  22. components: null,
  23. directives: null,
  24. // resolved props and emits options
  25. propsOptions: normalizePropsOptions(type, appContext),
  26. emitsOptions: normalizeEmitsOptions(type, appContext),
  27. // emit
  28. emit: null!, // to be set immediately
  29. emitted: null,
  30. // props default value
  31. propsDefaults: EMPTY_OBJ,
  32. // inheritAttrs
  33. inheritAttrs: type.inheritAttrs,
  34. // state
  35. ctx: EMPTY_OBJ,
  36. data: EMPTY_OBJ,
  37. props: EMPTY_OBJ,
  38. attrs: EMPTY_OBJ,
  39. slots: EMPTY_OBJ,
  40. refs: EMPTY_OBJ,
  41. setupState: EMPTY_OBJ,
  42. setupContext: null,
  43. // suspense related
  44. suspense,
  45. suspenseId: suspense ? suspense.pendingId : 0,
  46. asyncDep: null,
  47. asyncResolved: false,
  48. // lifecycle hooks
  49. // not using enums here because it results in computed properties
  50. isMounted: false,
  51. isUnmounted: false,
  52. isDeactivated: false,
  53. bc: null,
  54. c: null,
  55. bm: null,
  56. m: null,
  57. bu: null,
  58. u: null,
  59. um: null,
  60. bum: null,
  61. da: null,
  62. a: null,
  63. rtg: null,
  64. rtc: null,
  65. ec: null,
  66. sp: null
  67. }

上面的对象包含着很多的状态信息,是实现组件化一个很重要的内容。

创建完组件实例后,Vue 使用 setupComponent 函数进行一些数据的解析和初始化,下面调用的 setupRenderEffect 函数是重点。

  1. const setupRenderEffect: SetupRenderEffectFn = (
  2. instance,
  3. initialVNode,
  4. container,
  5. anchor,
  6. parentSuspense,
  7. isSVG,
  8. optimized
  9. ) => {
  10. const componentUpdateFn = () => {
  11. // 使用组件实例的 isMounted 属性判断组件是否挂载
  12. // 如果为属性为 false,说明还未挂载,所以执行挂载逻辑
  13. // 如果属性为 true 的话,说明已经挂载,所以执行更新逻辑
  14. if (!instance.isMounted) {
  15. const { bm, m } = instance
  16. // beforeMount hook
  17. // 触发执行 beforeMount 生命周期函数
  18. if (bm) {
  19. invokeArrayFns(bm)
  20. }
  21. // 执行 render 函数,获取组件当前的 VNode
  22. const subTree = (instance.subTree = renderComponentRoot(instance))
  23. // 使用 patch 函数进行组件内容的渲染
  24. patch(
  25. null,
  26. subTree,
  27. container,
  28. anchor,
  29. instance,
  30. parentSuspense,
  31. isSVG
  32. )
  33. // mounted hook
  34. // 触发执行 mounted 生命周期函数
  35. if (m) {
  36. queuePostRenderEffect(m, parentSuspense)
  37. }
  38. // 将组件实例的 isMounted 属性设为 true,表明当前的组件已经完成了挂载操作
  39. instance.isMounted = true
  40. } else {
  41. let { bu, u } = instance
  42. // beforeUpdate hook
  43. // 触发执行 beforeUpdate 生命周期函数
  44. if (bu) {
  45. invokeArrayFns(bu)
  46. }
  47. // render
  48. // 执行 render 函数,获取组件最新的 VNode
  49. const nextTree = renderComponentRoot(instance)
  50. // 获取组件上次渲染的 VNode
  51. const prevTree = instance.subTree
  52. instance.subTree = nextTree
  53. // 使用 patch 函数进行组件的更新
  54. patch(
  55. prevTree,
  56. nextTree,
  57. // parent may have changed if it's in a teleport
  58. hostParentNode(prevTree.el!)!,
  59. // anchor may have changed if it's in a fragment
  60. getNextHostNode(prevTree),
  61. instance,
  62. parentSuspense,
  63. isSVG
  64. )
  65. // updated hook
  66. // 触发执行 updated 生命周期函数
  67. if (u) {
  68. queuePostRenderEffect(u, parentSuspense)
  69. }
  70. }
  71. }
  72. // 组件的更新借助了响应式系统中的 ReactiveEffect 类
  73. const effect = (instance.effect = new ReactiveEffect(
  74. componentUpdateFn,
  75. () => queueJob(update),
  76. instance.scope // track it in component's effect scope
  77. ))
  78. const update: SchedulerJob = (instance.update = () => effect.run())
  79. update()
  80. }

上段代码中,借助 ReactiveEffect 类实现组件的更新,关于这个类的作用和源码可以看我的这篇博客,这里就不过多赘述了。

这里实现功能的重点在 componentUpdateFn 函数中,在上面代码的最后,执行了 update 函数,这会进而触发执行上面 componentUpdateFn 函数的执行,componentUpdateFn 函数的内部会执行组件的 render 函数,render 函数会读取组件的响应式数据,这会触发依赖收集

componentUpdateFn 函数的解析看上面的注释即可。

3-2,组件的更新

当后续 render 函数依赖的响应式数据发生变化的时候,会再次触发执行 componentUpdateFn 函数进行组件的重新渲染,详细解释看上面源码的注释。

4,组件的 render 函数执行时,如何通过 this 访问到组件的响应式数据

结论:Vue 通过代理让 render 函数执行时能够通过 this 访问到组件实例中的响应式数据。

Vue 通过 renderComponentRoot 函数执行 render 函数,获取 VNode。

instance.subTree = renderComponentRoot(instance)
  1. export function renderComponentRoot(
  2. instance: ComponentInternalInstance
  3. ): VNode {
  4. const {
  5. type: Component,
  6. vnode,
  7. proxy,
  8. props,
  9. propsOptions: [propsOptions],
  10. slots,
  11. attrs,
  12. emit,
  13. render,
  14. renderCache,
  15. data,
  16. setupState,
  17. ctx,
  18. inheritAttrs
  19. } = instance
  20. result = normalizeVNode(
  21. render!.call(
  22. proxy,
  23. proxy!,
  24. renderCache,
  25. props,
  26. setupState,
  27. data,
  28. ctx
  29. )
  30. )
  31. return result;
  32. }

render 函数执行的时候,函数中的 this 指向 instance.proxy,接下来看 instance.proxy 属性是如何创建出来的。

  1. export function setupComponent(instance) {
  2. ......
  3. setupStatefulComponent(instance);
  4. }
  5. function setupStatefulComponent(instance) {
  6. instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
  7. }
  8. export const PublicInstanceProxyHandlers = {
  9. get({ _: instance }: ComponentRenderContext, key: string) {
  10. const { ctx, setupState, data, props, accessCache, type, appContext } =
  11. instance
  12. if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
  13. accessCache![key] = AccessTypes.SETUP
  14. return setupState[key]
  15. } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  16. accessCache![key] = AccessTypes.DATA
  17. return data[key]
  18. } else if (
  19. (normalizedProps = instance.propsOptions[0]) &&
  20. hasOwn(normalizedProps, key)
  21. ) {
  22. accessCache![key] = AccessTypes.PROPS
  23. return props![key]
  24. } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
  25. accessCache![key] = AccessTypes.CONTEXT
  26. return ctx[key]
  27. }
  28. }
  29. };

可以发现,在 render 函数中通过 this 读取某些属性的时候,代理会判断 instance 的 setupState、data、props 中有没有同名的属性,如果有的话,就进行数据的读取和返回,并且这些被读取属性已经是响应式的了。
 

5,setup 函数的实现

setup 的官方文档点击这里

setup 函数只会在组件挂载的时候执行一次,setup 函数既可以返回一个对象,也可以返回一个函数,如果返回的是一个对象的话,这个对象中的数据可以像 data 和 props 一样使用,如果返回的是一个函数的话,这个函数会被当成组件的 render 函数。

源码如下所示:

  1. // 挂载组件节点
  2. const mountComponent: MountComponentFn = (
  3. initialVNode,
  4. container,
  5. anchor,
  6. parentComponent,
  7. parentSuspense,
  8. isSVG,
  9. optimized
  10. ) => {
  11. ......
  12. // 解析初始化一些数据
  13. if (!(__COMPAT__ && compatMountInstance)) {
  14. setupComponent(instance)
  15. }
  16. setupRenderEffect(
  17. instance,
  18. initialVNode,
  19. container,
  20. anchor,
  21. parentSuspense,
  22. isSVG,
  23. optimized
  24. )
  25. }
  26. export function setupComponent(instance) {
  27. ......
  28. setupStatefulComponent(instance);
  29. }
  30. function setupStatefulComponent(instance) {
  31. const Component = instance.type as ComponentOptions
  32. ......
  33. const { setup } = Component
  34. if (setup) {
  35. setCurrentInstance(instance)
  36. const setupResult = callWithErrorHandling(
  37. setup,
  38. instance,
  39. ErrorCodes.SETUP_FUNCTION,
  40. [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  41. )
  42. unsetCurrentInstance()
  43. handleSetupResult(instance, setupResult, isSSR)
  44. }
  45. }
  46. export function handleSetupResult(
  47. instance: ComponentInternalInstance,
  48. setupResult: unknown,
  49. isSSR: boolean
  50. ) {
  51. if (isFunction(setupResult)) {
  52. instance.render = setupResult as InternalRenderFunction
  53. } else if (isObject(setupResult)) {
  54. instance.setupState = proxyRefs(setupResult)
  55. }
  56. }

在 setupStatefulComponent 函数中,获取用户编写的 setup 函数,执行它并获取 setup 函数的返回值 setupResult。

接下来使用 handleSetupResult 函数处理结果,如果 setupResult 是一个函数的话,则将它赋值给组件实例的 render 属性,如果 setupResult 是一个对象的话,则将它赋值给组件实例的 setupState 属性上,当我们想在 render 函数中访问 setup 函数返回的数据时,Vue 会将读取操作代理到 setupState 属性上,源码如下所示:

  1. export const PublicInstanceProxyHandlers = {
  2. get({ _: instance }: ComponentRenderContext, key: string) {
  3. const { ctx, setupState, data, props, accessCache, type, appContext } =
  4. instance
  5. if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
  6. accessCache![key] = AccessTypes.SETUP
  7. return setupState[key]
  8. }
  9. ......
  10. }
  11. };

6,组件生命周期的实现原理

组件的生命周期原理很简单,主要分为两部分,分别是生命周期的注册以及生命周期的执行。

首先说生命周期的注册,这里以 setup 函数中进行的生命周期注册为例。

  1. function setupStatefulComponent(instance) {
  2. const Component = instance.type as ComponentOptions
  3. ......
  4. const { setup } = Component
  5. if (setup) {
  6. setCurrentInstance(instance)
  7. const setupResult = callWithErrorHandling(
  8. setup,
  9. instance,
  10. ErrorCodes.SETUP_FUNCTION,
  11. [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  12. )
  13. unsetCurrentInstance()
  14. handleSetupResult(instance, setupResult, isSSR)
  15. }
  16. }
  17. export let currentInstance: ComponentInternalInstance | null = null
  18. export const setCurrentInstance = (instance: ComponentInternalInstance) => {
  19. currentInstance = instance
  20. }
  21. export const unsetCurrentInstance = () => {
  22. currentInstance = null
  23. }

在生命周期函数执行前,会执行一个 setCurrentInstance 函数,这个函数的作用是将当前的组件实例设置到全局中。

接下来看生命周期注册函数的内容,以 onMounted 函数为例:

  1. export const onMounted = createHook(LifecycleHooks.MOUNTED)
  2. export const createHook =
  3. <T extends Function = () => any>(lifecycle: LifecycleHooks) =>
  4. (hook: T, target: ComponentInternalInstance | null = currentInstance) =>
  5. // post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
  6. (!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
  7. injectHook(lifecycle, hook, target)
  8. export function injectHook(
  9. type: LifecycleHooks,
  10. hook: Function & { __weh?: Function },
  11. target: ComponentInternalInstance | null = currentInstance,
  12. prepend: boolean = false
  13. ): Function | undefined {
  14. if (target) {
  15. const hooks = target[type] || (target[type] = [])
  16. const wrappedHook =
  17. hook.__weh ||
  18. (hook.__weh = (...args: unknown[]) => {
  19. if (target.isUnmounted) {
  20. return
  21. }
  22. // disable tracking inside all lifecycle hooks
  23. // since they can potentially be called inside effects.
  24. pauseTracking()
  25. // Set currentInstance during hook invocation.
  26. // This assumes the hook does not synchronously trigger other hooks, which
  27. // can only be false when the user does something really funky.
  28. setCurrentInstance(target)
  29. const res = callWithAsyncErrorHandling(hook, target, type, args)
  30. unsetCurrentInstance()
  31. resetTracking()
  32. return res
  33. })
  34. if (prepend) {
  35. hooks.unshift(wrappedHook)
  36. } else {
  37. hooks.push(wrappedHook)
  38. }
  39. return wrappedHook
  40. }
  41. }

当我们在 setup 函数中执行 onMounted 等生命周期注册函数时,Vue 会将我们想要注册的生命周期函数保存到组件实例中,组件实例用于保存生命周期函数的属性如下所示:

  1. const instance: ComponentInternalInstance = {
  2. // lifecycle hooks
  3. bc: null,
  4. c: null,
  5. bm: null,
  6. m: null,
  7. bu: null,
  8. u: null,
  9. um: null,
  10. bum: null,
  11. da: null,
  12. a: null,
  13. rtg: null,
  14. rtc: null,
  15. ec: null,
  16. sp: null
  17. }

知道了生命周期函数是如何注册的,接下来看看生命周期函数是如何触发的,生命周期函数触发的代码在 setupRenderEffect 函数中,代码如下所示:

  1. const setupRenderEffect: SetupRenderEffectFn = (
  2. instance,
  3. initialVNode,
  4. container,
  5. anchor,
  6. parentSuspense,
  7. isSVG,
  8. optimized
  9. ) => {
  10. const componentUpdateFn = () => {
  11. // 使用组件实例的 isMounted 属性判断组件是否挂载
  12. // 如果为属性为 false,说明还未挂载,所以执行挂载逻辑
  13. // 如果属性为 true 的话,说明已经挂载,所以执行更新逻辑
  14. if (!instance.isMounted) {
  15. const { bm, m } = instance
  16. // beforeMount hook
  17. // 触发执行 beforeMount 生命周期函数
  18. if (bm) {
  19. invokeArrayFns(bm)
  20. }
  21. // 执行 render 函数,获取组件当前的 VNode
  22. const subTree = (instance.subTree = renderComponentRoot(instance))
  23. // 使用 patch 函数进行组件内容的渲染
  24. patch(
  25. null,
  26. subTree,
  27. container,
  28. anchor,
  29. instance,
  30. parentSuspense,
  31. isSVG
  32. )
  33. // mounted hook
  34. // 触发执行 mounted 生命周期函数
  35. if (m) {
  36. queuePostRenderEffect(m, parentSuspense)
  37. }
  38. // 将组件实例的 isMounted 属性设为 true,表明当前的组件已经完成了挂载操作
  39. instance.isMounted = true
  40. } else {
  41. let { bu, u } = instance
  42. // beforeUpdate hook
  43. // 触发执行 beforeUpdate 生命周期函数
  44. if (bu) {
  45. invokeArrayFns(bu)
  46. }
  47. // render
  48. // 执行 render 函数,获取组件最新的 VNode
  49. const nextTree = renderComponentRoot(instance)
  50. // 获取组件上次渲染的 VNode
  51. const prevTree = instance.subTree
  52. instance.subTree = nextTree
  53. // 使用 patch 函数进行组件的更新
  54. patch(
  55. prevTree,
  56. nextTree,
  57. // parent may have changed if it's in a teleport
  58. hostParentNode(prevTree.el!)!,
  59. // anchor may have changed if it's in a fragment
  60. getNextHostNode(prevTree),
  61. instance,
  62. parentSuspense,
  63. isSVG
  64. )
  65. // updated hook
  66. // 触发执行 updated 生命周期函数
  67. if (u) {
  68. queuePostRenderEffect(u, parentSuspense)
  69. }
  70. }
  71. }
  72. // 组件的更新借助了响应式系统中的 ReactiveEffect 类
  73. const effect = (instance.effect = new ReactiveEffect(
  74. componentUpdateFn,
  75. () => queueJob(update),
  76. instance.scope // track it in component's effect scope
  77. ))
  78. const update: SchedulerJob = (instance.update = () => effect.run())
  79. update()
  80. }

在 componentUpdateFn 函数中,进行了组件的初始挂载和更新,生命周期函数就是在这些操作的前后触发执行的,在上面的源码中,使用 invokeArrayFns 函数进行生命周期函数的触发执行,它的源码如下所示:

  1. export const invokeArrayFns = (fns: Function[], arg?: any) => {
  2. for (let i = 0; i < fns.length; i++) {
  3. fns[i](arg)
  4. }
  5. }

7,结语

这篇博客讲解了 Vue3 中是如何实现组件化的,下面的博客详细讲讲异步组件以及 Vue 提供的一些内置组件。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/751951
推荐阅读
相关标签
  

闽ICP备14008679号