想查看更多的文章请关注公众号:IT巡游屋
接着上篇文章,我们在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
首先看下这个项目的入口文件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语法.下面对这两部分的语法进行讲解
首先方法的重载的含义是指,可以定义同名的方法,在调用方法的时候,根据传参不同,调用不同的方法.但是在原生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) //错误typescript最大的语法特性,就是将javascript变量类型,在声明的时候进行限定,如果改变变量的类型,将会报错,这样做让javascript更加不容易出错,例如
let isDone: boolean //指定变量为布尔类型 isDone = true //正确 isDone = 'hello' //错误,不能改变数据的类型下面整理常见的数据类型的定义,如下
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
方式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; } //正确看了上面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) } }接下来,我们看下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者做讲解
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)Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名组成的数组
如下面示例
var user = { username: 'zs', age: 20 } var names = Object.getOwnPropertyNames(user) console.log(names) //['username','age']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))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>看了上面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 }) }创建文件夹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 }运行成功,查看生成vue的对象
弄清楚vue-class-component这个项目的核心代码和涉及到基础知识,和根据它的原理写了一个自己的vue-class-component后,下一节我们看下在项目当中如果使用vue-class-component和其中的注意事项