深入理解Vue中的Typescript(二)-vue

it2023-10-01  70

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方法的结论有

Component方法实现了重载,接受不同的和Vue相关类型Component方法内部根据传入参数的类型不同,做不同的处理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的源码解析

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

componentFactory方法,在遍历形参,即Vue组件的ComponentcomponentFactory方法,根据变量Component,生成组件的配置变量optionscomponentFactory方法,通过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和其中的注意事项

最新回复(0)