likes
comments
collection
share

vue2技术栈思考 & vue-class-component原理分析

作者站长头像
站长
· 阅读数 10

实习入职第四天了,通过阅读项目代码发现业务开发中前辈们使用vue2都是基于class-styled-component去开发的,不去采用options风格去开发组件自然有它的出发点与考量,我认为核心在与class-styled-componet对于ts有更好的支持。并且基于这个问题的思考我翻看了一下vue-class-component和vue-property-decorator的源码,理清了他们的底层运行逻辑,并做了下文的记录。

vue与ts

说到vue3相较于vue2的优势,大概率会提到vue3有更好的typescript支持,社区里有不少人说因为vue3是用ts写的,确实用ts写的话自然vue项目打包出来就顺带得到了.d.ts类型声明文件,我觉着这个勉强算个原因,但不主要。

我认为vue2之所以对ts支持差,是因为它options api(配置式)的风格,它的组件就是一个对象,方法、计算属性、watch回调...啥都属于这个组件对象,但ts的类型检查能力不是针对对象的!而是针对函数和类class的,这也就是为啥vue2中如果想用ts,一般都会选择Vue2+TypeScript+vue-class-component+vue-property-decorator的技术栈体系,以OO(面向对象)的方式开发Vue程序。

本质上vue-class-component就是把class风格的组件最终转化成options配置组件,至于vue-property-decorator就是基于vue-class-component提供的自定义装饰器的能力,把原先的option配置通过装饰器的形式提供给我们,方便开发。

至于vue3,它组合式api的风格,把vue2中的各种配置都转化为了用函数来定义,这样自然而然就与ts的类型检查能力相契合了。

vue-class-component原理

我们写一个类组件时会用Component装饰器去修饰class,下面是Component源码:

vue-class-component/src/index.ts

function Component <V extends Vue>(options: ComponentOptions<V> & ThisType<V>): <VC extends VueClass<V>>(target: VC) => VC
function Component <VC extends VueClass<Vue>>(target: VC): VC
function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any {
  if (typeof options === 'function') {
    return componentFactory(options)
  }
  return function (Component: VueClass<Vue>) {
    return componentFactory(Component, options)
  }
}

说白了Component就是一个重载函数,目的就是支持多种传参形式,本质是一个装饰器工厂(即return function xxx创建装饰器函数),而装饰器函数的逻辑就是对componentFactory函数的调用并返回。

vue-class-component/src/component.ts

vue2技术栈思考 & vue-class-component原理分析 经过上面的源码分析,给人的最直观的感觉就是:@Component装饰器在装饰class类组件时做的事情类似于一种对所有class属性的“拦截”,拦截过程中同步构造options对象组件。 所以我们也可以明确,vue-class-component将class组件处理成options组件也不是在编译时,而是运行时。

vue-property-decorator原理

我们实际开发用的不是vue-class-component,而是基于它二次开发的库vue-property-decorator,它提供了若干class属性的装饰器,如@Watch@Prop等,其实本质就是让我们借助这些装饰器将class属性转化为vue组件的options对象的属性。

vue-class-component拓展机制

要想理解vue-property-decorator的原理,首先我们要明白vue-class-component是如何对外提供扩展能力的:

回顾@Component装饰器的实现,见上面github源码截图的第70-75行:

vue2技术栈思考 & vue-class-component原理分析

上面的代码我们尝试从Component,也就是class组件类身上拿到 __decorator__ 数组并遍历,从代码可知, __decorator__ 中存储的是函数回调,我们依次触发所有回调时会把 @Component装饰器正在构造的组件options对象 作为参数 传递给回调函数。

也就是说如果我们想基于vue-class-component拓展能力的话,我们可以通过给class添加__decorator__数组来完成(这样我们就有能力介入options组件对象的构造)。

这里有个需要注意的知识点——装饰器的执行顺序:首先执行属性装饰器,然后是方法装饰器,最后是类装饰器。

所以说@Component装饰器执行时可以保证class内部的所有逻辑已经执行完毕,即__decorator__已经收集完毕。

同时vue-class-component对外暴露了createDecorator这个api,让我们方便的基于vue-class-component进行拓展,vue-class-component/src/util.ts

vue2技术栈思考 & vue-class-component原理分析

以@Ref为例分析

如果我们在class组件中写如下代码,通过上文的分析我们知道对于普通的class方法都是直接把函数体赋值到options对象身上的,所以经过@Component装饰器的处理,MyMethod方法的函数体仍然会是this.myDom,但是MyMethod方法中通过this.myDom我们预期的是访问this.$refs.myDom才对!

@Component
export default class YourComponent extends Vue {
  // vue的ref的装饰器写法
  @Ref() myDom: HTMLDivElement
  
  myMethod() {
    this.myDom.style.color = '#f00';
  }
}

所以@Ref要做的事情就是让class组件中的this.myDom“映射”成options组件对象中的this.$refs.myDom,我们可以借助计算属性来完成这个转化,所以@Ref要做的就是给options对象添加一个名为myDom的计算属性,让它指向this.$refs.myDom即可!

下面我们看源码就不难理解了:

vue-property-decorator/src/decorators/Ref.ts

import Vue from 'vue'
import { createDecorator } from 'vue-class-component'

/**
 * decorator of a ref prop
 * @param refKey the ref key defined in template
 */
export function Ref(refKey?: string) { // refKey可以省略,我们忽略它往下看
  
  // createDecorator做的事情就是把函数参数封装一层(目的是把options传给函数参数)然后push的__decorator__数组中,等待@Component装饰器执行的最后调用
  return createDecorator((options, key) => {
    // 给options添加计算属性
    options.computed = options.computed || {} 
    options.computed[key] = {
      cache: false,
      get(this: Vue) {
        return this.$refs[refKey || key]
      },
    }
  })
}

上面的@Ref源码中,主体逻辑就是return createDecoratorcreateDecorator做的事情就是把函数参数封装一层(目的是把options传给函数参数)然后push的__decorator__数组中,等待@Component装饰器执行时调用。自然传递给createDecorator的函数逻辑就是给options对象添加computed计算属性了。

总结

  1. Vue2+TypeScript+vue-class-component+vue-property-decorator的技术栈体系目的是让vue2有更好的ts支持
  2. vue-class-component提供的核心装饰器@Component,是在class实例化的运行时,而非编译时,通过装饰器提供的类似于“拦截”的能力同步构建options对象组件,从而实现class组件到普通配置型对象组件的转化。
  3. vue-class-component提供了一定的拓展能力,vue-property-decorator就是基于vue-class-component自定义了一些常用的属性/方法装饰器,从而实现class组件中一些常用配置的便捷定义。
转载自:https://juejin.cn/post/7304551591527923749
评论
请登录