vue组件实现原理解析(下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 组件机制的设计,可以让开发者把一个复杂的应用分割成一个个功能独立组件,降低开发的难度的同时,也提供了极好的复用性和可维护性。本文我们一起从源码的角度,了解一下组件的底层实现原理。

还有一处重要的代码是installComponentHooks(data)。该方法会给组件vnodedata添加组件钩子,这些钩子在组件的不同阶段被调用,例如init钩子在组件patch时会调用。


function installComponentHooks (data: VNodeData) {
 const hooks = data.hook || (data.hook = {})
 for (let i = 0; i < hooksToMerge.length; i++) {
   const key = hooksToMerge[i]
   // 外部定义的钩子
   const existing = hooks[key]
   // 内置的组件vnode钩子
   const toMerge = componentVNodeHooks[key]
   // 合并钩子
   if (existing !== toMerge && !(existing && existing._merged)) {
     hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
   }
 }
}
// 组件vnode的钩子。
const componentVNodeHooks = {
 // 实例化组件
 init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
   if (
     vnode.componentInstance &&
     !vnode.componentInstance._isDestroyed &&
     vnode.data.keepAlive
   ) {
     // kept-alive components, treat as a patch
     const mountedNode: any = vnode // work around flow
     componentVNodeHooks.prepatch(mountedNode, mountedNode)
   } else {
     // 生成组件实例
     const child = vnode.componentInstance = createComponentInstanceForVnode(
       vnode,
       activeInstance
     )
     // 挂载组件,与vue的$mount一样
     child.$mount(hydrating ? vnode.elm : undefined, hydrating)
   }
 },
 prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
   const options = vnode.componentOptions
   const child = vnode.componentInstance = oldVnode.componentInstance
   updateChildComponent(
     child,
     options.propsData, // updated props
     options.listeners, // updated listeners
     vnode, // new parent vnode
     options.children // new children
   )
 },
 insert (vnode: MountedComponentVNode) {
   const { context, componentInstance } = vnode
   if (!componentInstance._isMounted) {
     componentInstance._isMounted = true
     // 触发组件的mounted钩子
     callHook(componentInstance, 'mounted')
   }
   if (vnode.data.keepAlive) {
     if (context._isMounted) {
       queueActivatedComponent(componentInstance)
     } else {
       activateChildComponent(componentInstance, true /* direct */)
     }
   }
 },
 destroy (vnode: MountedComponentVNode) {
   const { componentInstance } = vnode
   if (!componentInstance._isDestroyed) {
     if (!vnode.data.keepAlive) {
       componentInstance.$destroy()
     } else {
       deactivateChildComponent(componentInstance, true /* direct */)
     }
   }
 }
}
const hooksToMerge = Object.keys(componentVNodeHooks)


最后,与普通HTML标签一样,为组件生成vnode节点:


// 创建 vnode
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )


组件在patch时对vnode的处理与普通标签有所不同。


Vue 如果发现正在patchvnode是组件,那么调用createComponent方法。


function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      // 执行组件钩子中的init钩子,创建组件实例
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }
      // init钩子执行后,如果vnode是个子组件,该组件应该创建一个vue子实例,并挂载到DOM元素上。子组件的vnode.elm也设置完成。然后我们只需要返回该DOM元素。
      if (isDef(vnode.componentInstance)) {
        // 设置vnode.elm
        initComponent(vnode, insertedVnodeQueue)
        // 将组件的elm插入到父组件的dom节点上
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }


createComponent会调用组件vnodedata对象上定义的init钩子方法,创建组件实例。现在我们回过头来看下init钩子的代码:


// ... 省略其他代码
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 生成组件实例
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // 挂载组件,与vue的$mount一样
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }
  // ...省略其他代码


由于组件是初次创建,因此init钩子会调用createComponentInstanceForVnode创建一个组件实例,并赋值给vnode.componentInstance


export function createComponentInstanceForVnode (
  vnode: any, 
  parent: any,
): Component {
  // 内部组件选项
  const options: InternalComponentOptions = {
    // 标记是否是组件
    _isComponent: true,
    // 父Vnode
    _parentVnode: vnode,
    // 父Vue实例
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // new 一个组件实例。组件实例化 与 new Vue() 执行的过程相同。
  return new vnode.componentOptions.Ctor(options)
}


createComponentInstanceForVnode 中会执行 new vnode.componentOptions.Ctor(options)。由前面我们在创建组件vnode时可知,vnode.componentOptions的值是一个对象:{ Ctor, propsData, listeners, tag, children },其中包含了组件的构造函数Ctor。因此 new vnode.componentOptions.Ctor(options)等价于new VueComponent(options)


// 生成组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
// 挂载组件,与vue的$mount一样
child.$mount(hydrating ? vnode.elm : undefined, hydrating)


等价于:


new VueComponent(options).$mount(hydrating ? vnode.elm : undefined, hydrating)


这段代码想必大家都很熟悉了,是组件初始化和挂载的过程。组件的初始化和挂载与在前文中所介绍Vue初始化和挂载过程相同,因此不再展开说明。大致的过程就是创建了一个组件实例并挂载后。使用initComponent将组件实例的$el设置为vnode.elm的值。最后,调用insert将组件实例的DOM根节点插入其父节点。然后就完成了组件的处理。


总结


通过对组件底层实现的分析,我们可以知道,每个组件都是一个VueComponent实例,而VueComponent又是继承自Vue。每个组件实例独立维护自己的状态、模板的解析、DOM的创建和更新。篇幅有限,文中只分析了基本的组件的注册解析过程,未对异步组件keep-alive等做分析。等后面再慢慢补上。


相关文章
|
20天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
67 13
|
26天前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
60 17
|
2月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
76 1
|
2月前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
150 64
|
6天前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
44 14
|
15天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
67 1
|
1月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
57 8
|
2月前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
42 8
|
2月前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
2月前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
37 1

热门文章

最新文章

推荐镜像

更多