深入理解Vue中的Typescript(二)-vue_component源码解读

vue_component源码分析和Typescript语法

想查看更多的文章请关注公众号:IT巡游屋
在这里插入图片描述

1.概述

接着上篇文章,我们在Typescript定义一个组件,可以将组件定义成下面类样式

<template>
    <div>
    	<button @click="handleClick">{{count}}</button>
        <hello-world></hello-world>
    </div>
</template>
<script>
import Vue from 'vue'
import Component from 'vue-class-component'    
import HelloWorld = from './HelloWorld.vue'
    
@Component({
    components: {
        'hello-world': HelloWorld
    }    
})
export default class Counter extends Vue {
    count = 0   
    created(){
      	this.count = 1  
    }
    handleClick(){
        this.count++
    }
}
</script>

上代码用到@Component修饰符,而这个修饰符来源于vue-class-component项目,我们从它源代码的角度,看下它到底做了些什么?下面是这个项目的地址https://github.com/vuejs/vue-class-component

2.index.ts的源码预览

2.1入口文件index.js

首先看下这个项目的入口文件src/index.ts

import Vue, { ComponentOptions } from 'vue'
import { VueClass } from './declarations'
import { componentFactory, $internalHooks } from './component'

export { createDecorator, VueDecorator, mixins } from './util'

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.registerHooks = function registerHooks (keys: string[]): void {
  $internalHooks.push(...keys)
}

export default Component

分析上面代码,不需要弄明白所有代码的细节,需要抓住要点,看懂两点:

(1) Component方法定义

(2) componentFactory方法作用

即要弄懂下面语句

// (1)Component方法的定义
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') {
    //(2)componentFactory方法的作用
    return componentFactory(options)
  }
  return function (Component: VueClass<Vue>) {
    //(2)componentFactory方法的作用
    return componentFactory(Component, options)
  }
}

要弄懂上面语句,我们得明白Typescript语法.下面对这两部分的语法进行讲解

3.Typescript语法

3.1 方法的重载

首先方法的重载的含义是指,可以定义同名的方法,在调用方法的时候,根据传参不同,调用不同的方法.但是在原生javasript当中不支持方法的重载,例如下面语句

function fn (a) { //第1个方法,接受1个参数
    console.log(a)
}
function fn (a, b) { //第2个方法,覆盖之第一个方法,接受2个参数
    console.log(a,b)
}

fn(1) //始终调用第2个方法

如果要根据参数不同执行不同的结果,将2个方法合并成一个方法,那么在原生javascript当中应该写成下面样式

function fn(a, b){
    if(b!==undefined){
        console.log(a, b)
    }else{
        console.log(a)
    }
}

typescript中,不能改变javascript不支持方法重载的情况,但在定义方法和使用方法的时候,做语法验证和更容易读懂,例如下typescript语句

function fn(a); //方法调用形式1,接收1个参数
function fn(a,b); //方法调用形式2,接收2个参数
function fn(a,b?){ //最终的函数定义,2种形式的结合.参数后面加'?',代表这个参数非必传参数
    if(b){
        console.log(a, b)
    }else{
        console.log(a)
    }
}
fn(1) //正确
fn(1,2) //正确
fn() //错误,编辑器报错,不符合函数定义
fn(1,2,3) //错误
3.2 变量类型的检查

typescript最大的语法特性,就是将javascript变量类型,在声明的时候进行限定,如果改变变量的类型,将会报错,这样做让javascript更加不容易出错,例如

let isDone: boolean //指定变量为布尔类型
isDone = true //正确
isDone = 'hello' //错误,不能改变数据的类型

下面整理常见的数据类型的定义,如下

3.2.1 普通数据类型
let isDone: boolean //布尔值
let num: number //数字
let username: string //字符串
let unusable: void //空值(undefined或者null)
let numArr: number[] //数组,存储数字
let a: any  //任意值,任意类型
let b: string | number // 联合类型,指定多个类型之一
3.2.2 函数数据类型
  • 方式1,有名字的函数,指定形参类型返回值数据类型
function sum(x: number, y: number): number { 
    return x + y
}
  • 方式2,函数变量,指定形参类型返回值数据类型
let sum: (x: number, y: number) => number 
sum = function (x, y) {
    return x + y
}
3.2.3 对象数据类型
  • 方式1-1,通过接口interface定义对象类型,检查自变量
interface Person { //检查对象,是否包含username,age属性,say方法
    username: string
    age: number
    say: (message: string) => string
}
let tom: Person 
tom = { //正确
    username: 'Tom',
    age: 25,
    say: function(message){
        return message
    }
}
  • 方式1-2,通过接口interface定义对象类型,检查类实例对象
interface PersonInterface { //检查类实例对象,是否包含username,age属性,say方法
    username: string
    age: number
    say: (message: string) => string
}
class Person{ //定义类型
    constructor(username: string, age: number){
        this.username = username
        this.age = age
    }
    username: string
    age: number
    say(message){
        return message
    }
}

let tom:PersonInterface
tom = new Person('zs',25) //正确
  • 方式2-1,通过关键字type定义对象类型,检查自变量
type Person = { //检查对象,是否包含username,age属性,say方法
    username: string
    age: number
    say: (message: string) => string
}
let tom: Person 
tom = { //正确
    username: 'Tom',
    age: 25,
    say: function(message){
        return message
    }
}
  • 方式2-2,通过关键字type定义对象类型,检查类实例对象
type PersonInterface = { //检查类实例对象,是否包含username,age属性,say方法
    username: string
    age: number
    say: (message: string) => string
}
class Person{ //定义类型
    constructor(username: string, age: number){
        this.username = username
        this.age = age
    }
    username: string
    age: number
    say(message){
        return message
    }
}

let tom:PersonInterface
tom = new Person('zs',25) //正确
3.3 泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

  • 方式1-在数组中使用
let arr: Array<number> //指定数组存储的数据类型
arr = [1,2,3] //正确
  • 方式2-在方法中使用
function createArray<T>(length: number, value: T): Array<T> { //指定形参和返回值的数据类型
    let result: T[] = []
    for (let i = 0; i < length; i++) {
        result[i] = value
    }
    return result
}
createArray<string>(3, 'x') //动态设置泛型'T'为string,返回数据为['x', 'x', 'x']
createArray<number>(2, 0) //动态设置泛型'T'为number,[0, 0]
  • 方式3-在类定义中使用
class GenericNumber<T> { //指定类中变量和方法使用的类型
    zeroValue: T;
    add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();//设置泛型'T'为number
myGenericNumber.zeroValue = 0; //正确
myGenericNumber.add = function(x, y) { return x + y; } //正确

4.index.ts的源码解析

看了上面typescript语法后,我们再看index.ts中的代码,我们得出Component方法的结论有

  1. Component方法实现了重载,接受不同的和Vue相关类型
  2. Component方法内部根据传入参数的类型不同,做不同的处理
  3. Component方法返回经过componentFactory处理后的数据
// (1)`Component`方法实现了重载,接受不同的和`Vue`相关类型
// 形参为跟Vue配置属性类.返回值为一个方法,接收Vue的子类 
function Component <V extends Vue>(options: ComponentOptions<V> & ThisType<V>): <VC extends VueClass<V>>(target: VC) => VC
// 形参为Vue的子类.返回值为Vue的子类
function Component <VC extends VueClass<Vue>>(target: VC): VC
// 形参为Vue的配置属性类或者Vue的子类,返回值为任意值
function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any {
  //(2)`Component`方法内部根据传入参数的类型不同,做不同的处理
  if (typeof options === 'function') {
    //(3)`Component`方法返回经过`componentFactory`处理后的数据
    return componentFactory(options)
  }
  return function (Component: VueClass<Vue>) {
    //(3)`Component`方法返回经过`componentFactory`处理后的数据
    return componentFactory(Component, options)
  }
}

5.component.ts的源码预览

接下来,我们看下src/component.ts的源码,看下componentFactory方法的定义,弄明白这个函数做了什么

export const $internalHooks = [
  'data',
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeDestroy',
  'destroyed',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'render',
  'errorCaptured', // 2.5
  'serverPrefetch' // 2.6
]
export function componentFactory (
  Component: VueClass<Vue>,
  options: ComponentOptions<Vue> = {}
): VueClass<Vue> {
  options.name = options.name || (Component as any)._componentTag || (Component as any).name
  // prototype props.
  const proto = Component.prototype
  Object.getOwnPropertyNames(proto).forEach(function (key) {
    if (key === 'constructor') {
      return
    }

    // hooks
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
    if (descriptor.value !== void 0) {
      // methods
      if (typeof descriptor.value === 'function') {
        (options.methods || (options.methods = {}))[key] = descriptor.value
      } else {
        // typescript decorated data
        (options.mixins || (options.mixins = [])).push({
          data (this: Vue) {
            return { [key]: descriptor.value }
          }
        })
      }
    } else if (descriptor.get || descriptor.set) {
      // computed properties
      (options.computed || (options.computed = {}))[key] = {
        get: descriptor.get,
        set: descriptor.set
      }
    }
  })

  // add data hook to collect class properties as Vue instance's data
  ;(options.mixins || (options.mixins = [])).push({
    data (this: Vue) {
      return collectDataFromConstructor(this, Component)
    }
  })

  // decorate options
  const decorators = (Component as DecoratedClass).__decorators__
  if (decorators) {
    decorators.forEach(fn => fn(options))
    delete (Component as DecoratedClass).__decorators__
  }

  // find super
  const superProto = Object.getPrototypeOf(Component.prototype)
  const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
  const Extended = Super.extend(options)

  forwardStaticMembers(Extended, Component, Super)

  if (reflectionIsSupported()) {
    copyReflectionMetadata(Extended, Component)
  }

  return Extended
}

分析上面代码,我们也不需要弄明白所有代码的细节,需要抓住要点,看懂两点:

(1)componentFactory方法对传入的参数Component做了什么

(2)componentFactory方法返回什么样的数据

要弄懂上面语句,我们得明白上面component.ts当中一些es6的Object和vue当中的高级语法,下面对2者做讲解

6. ES6-Object语法

6.1 Object.getOwnPropertyDescriptor方法

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符.

其中自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性.

属性描述符是指对象属性的特征描述,包括4个特征

  • configurable:当且仅当指定对象的属性描述可以被改变或者属性可被删除时,为true。

  • enumerable: 当且仅当指定对象的属性可以被枚举出时,为 true。

  • value: 该属性的值(仅针对数据属性描述符有效)

  • writable: 当且仅当属性的值可以被改变时为true

如下面示例

var user = {
    username: 'zs'
}
const descriptor = Object.getOwnPropertyDescriptor(user, 'username')
/*
输入为一个对象,对象为
{
    configurable: true
    enumerable: true
    value: "zs"
    writable: true
}
*/
console.log(descriptor)

6.2 Object.getOwnPropertyNames方法

Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名组成的数组

如下面示例

var user = {
    username: 'zs',
    age: 20
}
var names = Object.getOwnPropertyNames(user)
console.log(names) //['username','age']

6.3 Object.getPrototypeOf方法

Object.getPrototypeOf() 方法返回指定对象的原型

如下面示例

class Person {
    constructor(username, age){
        this.username = username
        this.age = age
    }
    say(){

    }
}
var p = new Person('zs', 20)
/*
输出
{
	constructor:f, 
	say: f
}
*/
console.log(Object.getPrototypeOf(p))

7.Vue-extend方法

Vue.extend()方法使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象

如下面示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
</head>
<body>
    <div id="app">
        
    </div>
</body>
<script>
var App = Vue.extend({
  template: '<p>{{firstName}} {{lastName}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White'
    }
  }
})
// 创建 App 实例,并挂载到一个元素上。
new App().$mount('#app')
</script>
</html>

8.component.ts的源码解析

看了上面ObjectVue语法后,我们再看component.ts中的代码,我们得出componentFactory方法的结论有

  1. componentFactory方法,在遍历形参,即Vue组件的Component
  2. componentFactory方法,根据变量Component,生成组件的配置变量options
  3. componentFactory方法,通过Vue.extend方法和配置变量options生成Vue的子类,并且返回该类
//构造函数的名称列表
export const $internalHooks = [
  //...省略部分次要代码
  'created',
  'mounted',
  //...省略部分次要代码
]
// 
export function componentFactory (
  Component: VueClass<Vue>, //形参Component为Vue组件类的对象
  options: ComponentOptions<Vue> = {} //形参optionsVue为组件配置属性对象,默认为空对象
): VueClass<Vue> { //返回值为Vue对象
    // ...省略部分次要代码
    // 给组件配置添加name属性
    options.name = options.name || (Component as any)._componentTag || (Component as any).name
  const proto = Component.prototype
  // 要点1.在遍历形参Component的属性
  Object.getOwnPropertyNames(proto).forEach(function (key) {
	// 要点2.生成组件的配置变量`options`
    // 给组件配置添加钩子函数属性
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }  
    // 得到属性描述  
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
    if (descriptor.value !== void 0) {
        // 给组件配置添加methods属性
        if (typeof descriptor.value === 'function') {
        	(options.methods || (options.methods = {}))[key] = descriptor.value
      	}else if (descriptor.get || descriptor.set) {
            //给组件配置添加computed属性
            (options.computed || (options.computed = {}))[key] = {
                get: descriptor.get,
                set: descriptor.set
            }
        }
    }
    // ...省略部分次要代码
    // 得到父类即Vue类
    const superProto = Object.getPrototypeOf(Component.prototype)
    const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
    // 要点3.通过`Vue.extend`方法和配置变量`options`生成Vue的子类,并且返回该类
    // 调用父类的extend方法,即通过Vue.extend(options)生成Vue的子类
    const Extended = Super.extend(options)  
    // 返回处理生成的Vue对象
    return Extended
  })
}

9.自己写一个简单的vue-class-component

9.1 第一步,创建项目,安装依赖,写配置文件

  • 创建文件夹write-vue-class-component

  • 执行npm init -y生成package.json

  • 安装babel的依赖

    npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/node

    npm install --save @babel/polyfill

    npm install --save-dev @babel/plugin-proposal-decorators

    npm install --save-dev @babel/plugin-proposal-class-properties

  • 安装vue依赖

    npm install vue

  • 创建babel.config.js

const presets = [
    ["@babel/env",{
        targets:{
            edge:"17",
            firefox:"60",
            chrome:"67",
            safari:"11.1"
        }
    }]
]
const plugins = [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
]
module.exports = { presets, plugins }

9.2 创建装饰器component.js

import Vue from 'vue'
//构造函数的名称列表   
const $internalHooks = [ 
    'created',
    'mounted'
]
function componentFactory (Component, options = {}) { 
    const proto = Component.prototype
    // 遍历形参Component的属性
    Object.getOwnPropertyNames(proto).forEach(function (key) {
      // 给组件配置添加钩子函数属性
      if ($internalHooks.indexOf(key) > -1) {
        options[key] = proto[key]
        return
      }  
      // 得到属性描述 
      const descriptor = Object.getOwnPropertyDescriptor(proto, key)
      if (descriptor.value !== void 0) {
          // 给组件配置添加methods属性
          if (typeof descriptor.value === 'function') {
              (options.methods || (options.methods = {}))[key] = descriptor.value
          }else if (descriptor.get || descriptor.set) {
              //给组件配置添加computed属性
              (options.computed || (options.computed = {}))[key] = {
                  get: descriptor.get,
                  set: descriptor.set
           	  }
          }
        }      
        //通过Vue.extend(options)生成Vue的子类
        const Extended = Vue.extend(options)
        // 返回处理生成的Vue对象
        return Extended
    })
}

function Component (options) {
    if (typeof options === 'function') {
      return componentFactory(options)
    }
    return function (Component) {
      return componentFactory(Component, options)
    }
}
export default Component

9.3 创建测试代码index.js

import Vue from 'vue'
import Component from './component.js'    

@Component({
    filters: { //定义过滤器
        upperCase: function (value) {
            return value.toUpperCase()
        }
    }
})
class User extends Vue {
    firstName = ''//定义data变量
    lastName = ''  
    created(){ //定义生命周期函数
        this.firstName = 'li'
        this.lastName = 'lei'    
    }
    handleClick(){ //定义methods方法
        this.firstName = ''
        this.lastName = '' 
    }
    get fullName() { //定义计算属性
        return this.firstName + ' ' + this.lastName
    }
}

let u = new User()
console.log(u)

9.4 运行测试代码

npx babel-node index.js

运行成功,查看生成vue的对象

10.预告

弄清楚vue-class-component这个项目的核心代码和涉及到基础知识,和根据它的原理写了一个自己的vue-class-component后,下一节我们看下在项目当中如果使用vue-class-component和其中的注意事项

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue-typescript-import-dts 是一个用于为 Vue.js 项目TypeScript 文件生成类型声明文件的工具。在 Vue.js 项目使用 TypeScript 进行开发时,我们经常需要为一些第三方库或自定义组件编写类型声明文件,以提供更好的代码提示和类型检查。 使用 vue-typescript-import-dts 工具可以自动分析 TypeScript 文件的导入语句,并根据导入的模块生成对应的类型声明文件。这样,在使用该模块时,IDE 或编辑器就能提供准确的代码补全和类型检查。 例如,假设我们的项目使用了一个名为 axios 的第三方库进行网络请求,但是该库并没有提供类型声明文件。我们可以通过 vue-typescript-import-dts 工具,在我们的 TypeScript 文件导入 axios,并正确配置工具,它将自动为我们生成一个 axios.d.ts 类型声明文件。 具体使用 vue-typescript-import-dts 的步骤如下: 1. 在项目安装 vue-typescript-import-dts,可以使用 npm 或 yarn 命令来安装。 2. 在 TypeScript 文件,使用 import 语句导入需要生成类型声明文件的模块。 3. 在项目根目录下创建一个 .vue-typescript-import-dts.json 配置文件,用来配置生成类型声明文件的规则。可以指定生成的声明文件的输出路径、文件名等。 4. 运行 vue-typescript-import-dts 命令,它会自动扫描 TypeScript 文件的导入语句,并根据配置生成相应的类型声明文件。 这样,在我们编写代码时,IDE 或编辑器就可以准确地为我们提供代码补全和类型检查的功能。这对于提高开发效率和代码质量非常有帮助。 总之,vue-typescript-import-dts 是一个便捷的工具,可以自动为 Vue.js 项目使用的第三方库或自定义组件生成类型声明文件,提供更好的代码提示和类型检查功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值