最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入理解Vue中的Typescript(二)-vue_component源码分析和Typescript语法

    正文概述 掘金(黄金林)   2020-11-29   551

    0.目录

    深入理解Vue中的Typescript(一)-es语法的类属性和装饰器
    深入理解Vue中的Typescript(二)-vue_component源码分析和Typescript语法
    深入理解Vue中的Typescript(三)-vue2项目当中使用Typescript

    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的依赖

    • 安装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和其中的注意事项


    起源地下载网 » 深入理解Vue中的Typescript(二)-vue_component源码分析和Typescript语法

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元