重学vue(2, 3)及其生态+TypeScript 之 TypeScript(下)

简介: 重学vue(2, 3)及其生态+TypeScript 之 TypeScript


有关js中类的知识就不介绍了。只介绍一些typescript中对类的扩展。


类的成员修饰符


在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected


  • public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的。


  • private 修饰的是仅在同一类中可见、私有的属性或方法。


  • protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。


只读属性


如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly。但是只读属性可以在constructor中对其赋值。属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改。


class Person {
      // 1.只读属性是可以在构造器中赋值, 赋值之后就不可以修改
      // 2.属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改
      readonly name: string
      age?: number
      readonly friend?: Person
      constructor(name: string, friend?: Person) {
        this.name = name
        this.friend = friend
      }
    }
    const p = new Person("llm", new Person("jcl"))
    console.log(p.name)
    console.log(p.friend)
    // 不可以直接修改friend
    // p.friend = new Person("hcy")
    if (p.friend) {
      // name也是不能修改的,因为name是只读的。
      p.friend.name = "zh"
      // 但是age是可以修改的
      p.friend.age = 30
    } 
    // 不能修改
    // p.name = "123"


存取器(getter, setter)


在前面一些私有(private)属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程,这个时候我们可以使用存取器。


class Person {
      // 定义私有属性
      private _name: string
      constructor(name: string) {
        this._name = name
      }
      // 访问器setter/getter
      // setter
      set name(newName) {
        this._name = newName
      }
      // getter
      get name() {
        return this._name
      }
    }
    const p = new Person('zh')
    p.name = 'llm'
    console.log(p.name)


抽象类


我们知道,继承是多态使用的前提。所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。


但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法


什么是 抽象方法?


在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法。抽象方法,必须存在于抽象类中。抽象类是使用abstract声明的类。


抽象类有如下的特点:


  • 抽象类是不能被实例化的(也就是不能通过new创建)。


  • 抽象方法必须被子类实现,否则该类必须是一个抽象类。


// 这里不能传入Shape类实例化的对象。因为Shape是抽象类
    function makeArea(shape: Shape) {
      return shape.getArea()
    }
    abstract class Shape {
      abstract getArea(): number
    }
    class Rectangle extends Shape {
      private width: number
      private height: number
      constructor(width: number, height: number) {
        super()
        this.width = width
        this.height = height
      }
      getArea() {
        return this.width * this.height
      }
    }
    class Circle extends Shape {
      private r: number
      constructor(r: number) {
        super()
        this.r = r
      }
      getArea() {
        return this.r * this.r * 3.14
      }
    }
    const rectangle = new Rectangle(20, 30)
    const circle = new Circle(10)
    console.log(makeArea(rectangle))
    console.log(makeArea(circle))


类作为类型


类也可以作为一个类型使用


class Person {
      name: string = "zh"
      eating() {
      }
    }
    const p = new Person()
    // 用类来约束p1
    const p1: Person = {
      name: "llm",
      eating() {
      }
    }
    function printPerson(p: Person) {
      console.log(p.name)
    }
    printPerson(new Person())
    printPerson({name: "llm", eating: function() {}})


接口


接口定义对象类型(只读, 可选)


可以声明对象类型, 并且可以指定可读属性readonly和可选属性?


interface IInfoType {
      readonly name: string
      age: number,
      fn: () => void
    }
    const info: IInfoType = {
      name: "zh",
      age: 20,
      fn() {
        console.log(name, age)
      }
    }


定义索引类型


我们可以定义相同类型的键值和键名。


// 通过interface来定义索引类型
    interface IndexLanguage {
      [index: number]: string
    }
    const frontLanguage: IndexLanguage = {
      0: "HTML",
      1: "CSS",
      2: "JavaScript",
      3: "Vue"
    }
    interface ILanguageYear {
      [name: string]: number
    }
    const languageYear: ILanguageYear = {
      "C": 1972,
      "Java": 1995,
      "JavaScript": 1996,
      "TypeScript": 2014
    }


接口定义函数类型


我们可以通过interface来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型。没有方法体,只有方法签名。但是还是通过类型别名来定义函数类型较好。


// type CalcFn = (n1: number, n2: number) => number
    // 可调用的接口
    interface CalcFn {
      (n1: number, n2: number): number
    }
    function calc(num1: number, num2: number, calcFn: CalcFn) {
      return calcFn(num1, num2)
    }
    const add: CalcFn = (num1, num2) => {
      return num1 + num2
    }
    calc(20, 30, add)


接口的继承


接口和类一样是可以进行继承的,也是使用extends关键字。并且可以支持多继承。


interface ISwim {
      swimming: () => void
    }
    interface IFly {
      flying: () => void
    }
    interface IAction extends ISwim, IFly {}
    const action: IAction = {
      swimming() {},
      flying() {},
    }


类可以实现接口


接口定义后,也是可以被类实现的。如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入。这就是面向接口开发。


我们可以通过implements来实现接口。


interface ISwim {
      swimming: () => void
    }
    interface IEat {
      eating: () => void
    }
    // 类实现接口
    class Animal {}
    // 继承: 只能实现单继承
    // 实现: 实现接口, 类可以实现多个接口
    class Fish extends Animal implements ISwim, IEat {
      swimming() {
        console.log('Fish Swmming')
      }
      eating() {
        console.log('Fish Eating')
      }
    }
    class Person implements ISwim {
      swimming() {
        console.log('Person Swimming')
      }
    }
    // 编写一些公共的API: 面向接口编程
    function swimAction(swimable: ISwim) {
      swimable.swimming()
    }
    // 1.所有实现了接口的类对应的对象, 都是可以传入
    swimAction(new Fish())
    swimAction(new Person())
    swimAction({ swimming: function () {} })


interface和type区别


  • 如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function。


  • 如果是定义对象类型,那么他们是有区别的:


  • interface 可以重复的对某个接口来定义属性和方法。ts会合并同名的接口,我们在实现的时候,需要实现合并到接口。


interface IFoo {
      name: string
    }
    interface IFoo {
      age: number
    }
    // 这里需要实现上面的
    const foo: IFoo = {
      name: 'zh',
      age: 18,
    }


  • type定义的是别名,别名是不能重复的。


// 重复定义会报错
    type IBar = {
      name: string
      age: number
    }
    type IBar = {
    }


引用赋值


当我们引用赋值对象给不同接口的时候,ts内部会处理不同接口类型的差异,不会报错。如果直接将不同接口的对象赋值其他接口,将会报错。


interface IPerson {
      name: string
      age: number
    }
    const info = {
      name: 'zh',
      age: 20,
      address: '信阳市',
    }
    // freshness擦除, 不会报错
    const p: IPerson = info
    // 这样会报错,因为赋值的对象类型和IPerson类型不符
    const p1: IPerson = {
      name: 'zh',
      age: 20,
      address: '信阳市'
    }


枚举类型


通过enum来定义枚举类型。其实他也可以通过联合类型来代替。


  • 枚举类型的值默认是从0开始的递增。


  • 枚举类型(只针对数字枚举)还可以反向取值,利用下标,取出对应的枚举值。


enum Direction {
      LEFT,
      RIGHT,
      TOP,
      BOTTOM,
    }
    console.log(Direction[2]) // TOP


  • 枚举类型的属性值,可以是数字,也可以是字符串。


  • 不会为字符串枚举成员生成反向映射。


泛型


软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性: 比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作。


泛型在函数中的使用


以前我们都是在函数定义的时候,给函数参数和返回值做类型约束,但是现在我们可以通过泛型来对其做类型约束。我们在定义的时候给定参数泛型变量,等我们调用函数的时候传入泛型变量类型即可。使参数的类型是也可以参数化。 一般在我们传入函数参数的时候,ts可以自动推断出泛型变量的类型,所以可以不显示的传入泛型类型。


function sum<Type>(num: Type): Type {
      return num
    }
    // 1.调用方式一: 明确的传入类型
    sum<number>(20)
    sum<{name: string}>({name: "zh"})
    sum<any[]>(["abc"])
    // 2.调用方式二: 类型推到
    sum(50)
    sum("abc")


并且,我们可以定义多个泛型。注意:在传入类型的时候要么都传入要么都不传入。


function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {}
    // 这里要么多传入类型,要么都不传
    foo<number, string, boolean>(10, 'abc', true)


如果在函数中给泛型指定默认类型,我们必须全部指定,要不然会报错。(这是错误的,请忽略)


// 这里的泛型类型只要指定了其中一个,其他得也得指定。
    function foo<T = string, E = string, O = boolean>(
      arg1: T,
      arg2: E,
      arg3?: O,
      ...args: T[]
    ) {}
    foo<number, string, boolean>(10, 'abc', true)


网络异常,图片无法展示
|


平时在开发中我们可能会看到一些常用的泛型名称:


  • T:Type的缩写,类型


  • K、V:key和value的缩写,键值对


  • E:Element的缩写,元素


  • O:Object的缩写,对象


泛型在接口中的使用


我们可以在接口中使用泛型,来让外界自己传入指定的类型来约束内部的属性或方法。


并且他不会自动推断泛型的类型。


interface IPerson<T1, T2> {
      name: T1
      age: T2
    }
    const p: IPerson<string, number> = {
      name: "zh",
      age: 20
    }


我们也可以指定泛型的默认类型,并且可以部分指定。


interface IPerson<T1, T2 = number> {
      name: T1
      age: T2
    }
    // 只传入第一个类型,第二个用指定的类型
    const p: IPerson<string> = {
      name: 'zh',
      age: 20,
    }


泛型在类中的使用


我们可以在调用类后面传入类型,也可以在指定实例化对象类型的时候传入类型。并且,如果不传入类型,ts也可以进行类型推断。并且也可以指定默认的类型。


class Point<T = number> {
      x: T
      y: T
      z: T
      constructor(x: T, y: T, z: T) {
        this.x = x
        this.y = y
        this.z = y
      }
    }
    const p1 = new Point('1.33.2', '2.22.3', '4.22.1')
    const p2 = new Point<string>('1.33.2', '2.22.3', '4.22.1')
    const p3: Point<string> = new Point('1.33.2', '2.22.3', '4.22.1')


泛型约束


有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中。这时候我们可以让泛型继承extends自这个类型。


比如string和array都是有length的,或者某些对象也是会有length属性的。那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?


interface ILength {
      length: number
    }
    function getLength<T extends ILength>(arg: T) {
      return arg.length
    }
    getLength("abc")
    getLength(["abc", "cba"])
    getLength({length: 100})


模块解析


类型查找


之前我们所有的typescript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型:


const image = document.getElementById('image') as HTMLImageElement


大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?


其实这里就涉及到typescript对类型的管理和查找规则了。我们这里先给大家介绍另外的一种typescript文件:.d.ts文件


我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方。还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型。


那么typescript会在哪里查找我们的类型声明呢?


  • 内置类型声明。


  • 内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件。


  • 包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等。


  • 外部定义类型声明。通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。


  • 有时候安装第三方包,他自己内部就定义了.d.ts文件。例如axios库。


网络异常,图片无法展示
|


  • 大部分情况下,第三方库都没有提供内置.d.ts文件,我们可以通过该链接查找对应库的.d.ts来进行安装。


  • 自己定义类型声明。 下面我们就来介绍自定义类型声明。


声明模块


什么情况下需要自己来定义声明文件呢?


  • 我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件。 可以通过声明模块的语法: declare module '模块名' {}


  • 在声明模块的内部,我们可以通过 export 导出对应库的类、函数等。


declare module 'lodash' {
      export function join(arr: any[]): void
    }


  • 我们给自己的代码中声明一些类型,方便在其他地方直接进行使用。就是将声明变量/函数/类等的类型声明和实现分开写。


// 声明变量/函数/类
    declare let name: string
    declare let age: number
    // 声明函数
    declare function foo(): void
    // 声明类
    declare class Person {
      name: string
      age: number
      constructor(name: string, age: number)
    }


  • 声明文件,typescript也不识别导入的图片文件等,所以我们也需要声明。


// 声明文件 。当我们在ts文件中引入图片等文件时,会报错。
    declare module '*.jpg'
    declare module '*.jpeg'
    declare module '*.png'
    declare module '*.svg'
    declare module '*.gif'


  • 声明命名空间


// 声明命名空间
    declare namespace $ {
      export function ajax(settings: any): any
    }


上面类型的具体实现


<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
  "></script>


let name = 'zh'
    let age = 20
    function foo() {
      console.log('foo')
    }
    class Person {
      name
      age
      constructor(name, age) {
        this.name = name
        this.age = age
      }
    }
    console.log(name)
    console.log(age)
    foo()
    const p = new Person('llm', 20)
    console.log(p)
    $.ajax({})


相关文章
|
3月前
|
JavaScript 前端开发 安全
【技术革新】Vue.js + TypeScript:如何让前端开发既高效又安心?
【8月更文挑战第30天】在使用Vue.js构建前端应用时,结合TypeScript能显著提升代码质量和开发效率。TypeScript作为JavaScript的超集,通过添加静态类型检查帮助早期发现错误,减少运行时问题。本文通过具体案例展示如何在Vue.js项目中集成TypeScript,并利用其类型系统提升代码质量。首先,使用Vue CLI创建支持TypeScript的新项目,然后构建一个简单的待办事项应用,通过定义接口描述数据结构并在组件中使用类型注解,确保代码符合预期并提供更好的编辑器支持。
84 0
|
3月前
|
JavaScript 前端开发 安全
立等可取的 Vue + Typescript 函数式组件实战
立等可取的 Vue + Typescript 函数式组件实战
|
4月前
|
JavaScript 前端开发
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
49 0
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
|
5月前
|
JavaScript 安全 前端开发
Vue 3 中的 TypeScript
【6月更文挑战第15天】
83 6
|
6月前
|
JavaScript 前端开发 开发者
类型检查:结合TypeScript和Vue进行开发
【4月更文挑战第24天】TypeScript是JavaScript超集,提供类型注解等特性,提升代码质量和可维护性。Vue.js是一款高效前端框架,两者结合优化开发体验。本文指导如何配置和使用TypeScript与Vue:安装TypeScript和Vue CLI,创建Vue项目时选择TypeScript支持,配置`tsconfig.json`,编写`.tsx`组件,最后运行和构建项目。这种结合有助于错误检查和提升开发效率。
56 2
|
6月前
|
JavaScript 前端开发 开发者
Vue工具和生态系统: Vue.js和TypeScript可以一起使用吗?
【4月更文挑战第18天】Vue.js与TypeScript兼容,官方文档支持在Vue项目中集成TypeScript。TypeScript作为JavaScript超集,提供静态类型检查和面向对象编程,增强代码准确性和健壮性。使用TypeScript能提前发现潜在错误,提升代码可读性,支持接口和泛型,使数据结构和函数更灵活。然而,不是所有Vue插件都兼容TypeScript,可能需额外配置。推荐尝试在Vue项目中使用TypeScript以提升项目质量。
113 0
|
6月前
|
JavaScript 前端开发
在Vue中使用TypeScript的常见问题有哪些?
在Vue中使用TypeScript的常见问题有哪些?
98 2
|
8天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。