结合源码谈谈对vue组件化的理解

1.组件定义

image.png
全局组件和单文件组件两种方式

2.1 全局组件

源码分析1:src\core\global-api\assets.js

//是component,filter,directive三个的综合方法
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  //['component','filter','directive']循环遍历这三个方法
  ASSET_TYPES.forEach(type => {
    //动态附加三个静态方法,然后分别去定义
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        //如果def是对象,那么definition就是组件的配置对象
        if (type === 'component' && isPlainObject(definition)) {
          //定义组件name
          definition.name = definition.name || id
          //extend创建组件构造函数,def变成了构造函数
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        //注册 this.options[components][comp]=Ctor
        //在当前vue的选项中加上components选项,这样的话
        //定义全局组件,将来会被继承在所有的子组件里
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

再看看extend,js
image.png

1.2 单文件组件

其实写的并不是一个组件而是组件的一个配置对象。

vue-loader会编译template为render函数,最终导出的依然是组件配置对象。
(style里面的代码会用style-loader,css里的会用css-loader处理成css文件)
所以我们现在写的.vue文件最终都会导出为js对象,这些对象都是组件的配置对象而不是组件的构造函数。

2.组件化优点(提高维护性,测试性,复用性)

源码位置:lifecycle.js-mountComponent()
组件、watcher、渲染函数和更新函数之间的关系

mountComponent()定义了组件和watcher一一对应的关系。

//组件的实例在执行$mount的时候会调用的方法
export function mountComponent (
//创建一个组件的时候也创建了一个与之对应的watcher实例,
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
...
 updateComponent = () => {
      vm._update(vm._render(), hydrating)
      //调用组件的更新函数和渲染函数
    }

创建一个组件的时候也创建了一个与之对应的watcher实例,
如果这个组件中的数据发生了变化,其实只会调用该组件的渲染函数
如果在应用程序中,合理切割组件的力度,比如说将一个经常发生数据变化的一块内容,切割成一个组件,将来频繁执行的渲染函数和更新函数和打补丁的范围就变得更小了.所以把数据变化频繁的提取为组件,可以有效地提升性能。

3.组件化的实现

构造函数,src\core\global-api\extend.js
实例化及挂载,src\core\vdom\patch.js-createElm()

组件的构造函数通过extend来生成,extend执行的时间点不完全相同,如果说是全局注册组件,是立刻执行,甚至在当前实例化之前就执行了。如果是一个局部组件,声明在一个单文件组件中,可能在运行时的某个时刻才会去执行extend方法,具体是什么时刻可以看一下patch的源码。
我们都知道dom是一个树,那虚拟dom也是一个树,那么这棵树的起始点一定有一个根节点,实际上根节点就是起始点,那么根节点一定不是一个组件,而是vue的根实例。那就不存在自定义组件这些东西。
当vue实例化的时候,它执行挂载之后,会有一个初始化过程,要执行打补丁patch,在此过程中会执行createElm方法,因为根节点需要创建相对应的一个真实的dom,
看看createElm方法中有几行代码是和组件化息息相关的。

//如果要创建的是组件,走下面的流程,如果传入的是vue实例,就不执行
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }
//这里createComponent是把前面的那个执行的结果vnode转化为真实的dom
  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    //获取管理钩子函数
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      //存在init钩子,则执行之创建实例并挂载
//这个初始化函数是在create-component.js中定义的
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }
      //如果组件实例存在
      if (isDef(vnode.componentInstance)) {
        //属性初始化
        initComponent(vnode, insertedVnodeQueue)
        //dom插入操作
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

create-component.js

//默认组件管理钩子
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 {
//不是keep-alive而是要重新创建实例的时候
      //创建组件实例
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
//子组件里面的子元素或者子组件也进行了初始化
      //创建完成并挂载(挂载后才能从虚拟dom变成真实dom)
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

实例化的过程是自上而下的,从根往叶子节点进行创建,但是由于子节点的创建会先执行,子节点创建后就立刻挂载了,所以挂载是从下而上的。

4.总结

  • 基础部分
    1.组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一, 它使开发者使用小型、独立和通常可
    复用的组件构建大型应用;
    2.组件化开发能大幅提高应用开发效率、测试性、复用性等;
  • 落地
    3.组件使用按分类有:
    页面组件:用路由来导航一些页面组件,复用性不那么强,但是是组织页面直接来回切换的必备组件
    业务组件:登录组件,购物车里的徽章组件,有很强的业务性和通用性
    通用组件:按钮,表单,输入框等等;
  • 特点
  1. vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于
    VueComponent,这个类扩展于Vue,扩展的过程中,会继承于vue中已经有的一些选项。
  2. vue中常见组件化技术有:属性prop,自定义事件,插槽等,它们主要用于组件通信、扩展等;
  • 注意事项
    6.合理的划分组件,有助于提升应用性能;
    切割出经常发生数据变化的组件,将来watcher去更新的时候,只会重新渲染它对应的组件,而不会影响其他的地方。

7.组件应该是高内聚、低耦合的;
高内聚:组件本来就是一个独立功能单元,功能应该是单一的,独立的,这样组件才更容易复用。
低耦合:不应该与其他组件有太多的耦合关系。

8.遵循单向数据流的原则。
通过属性把值传进来或者通过vuex的方式直接把值注入进来。如果要改变值,通过事件派发出去,然后让父级接管这个事件,从而修改数据。如果使用vuex,使用commit或者dispatch的方式,让vuex去改变这个值。而不是组件自己去改数据。让组件功能更单一。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值