前端 Typescript 入门

06-01 1391阅读

Ant design vue4.x 基于 vue3,示例默认是 TypeScript。比如table组件管理。

vue3 官网介绍也使用了 TypeScript,例如: 响应式 API:核心 

华为的鸿蒙OS(HarmonyOS)开发中也可以使用 TypeScript

本篇目的用于对 TS 进行扫盲

Tip:ts 路线图

ts 是什么

TS是TypeScript的缩写,由微软开发的一种开源的编程语言

以前官网说“ts 是 js 超级”,现在改为: TypeScript是具有类型语法的JavaScript。目前 TypeScript 5.4 已经发布(2024-03) ——ts 官网

Tip:ts缺点:开发更费麻烦,要多写东西了,看个人取舍。

环境

基于笔者博文《vue3 入门》,就像这样:

  
  


// ts



也可以直接在  ts在线运行环境 进行。

推导类型和显示注解类型

TS = 类

// 推导类型
let a = 1 // a 是数字
let b = 'hello' // b 是字符串
let c = [true, false]; // 布尔类型数组

型 + javascript

ts 编译过程:

  • TypeScript源码 -> TypeScript AST
  • 类型检查器检查AST
  • TypeScript AST -> JavaScript 源码

    显示注解类型,语法:value:type 告诉类型检查器,这个 value 类型是 type。请看示例:

    如果将 a 写成 let a: number = '3',vscode 中 a 就会出现红色波浪,移上去会看到提示:不能将类型“string”分配给类型“number”。

    前端 Typescript 入门
    (图片来源网络,侵删)

    如果想让 typescript 推到类型,就去掉注解,让 ts 自动推导。就像这样:

    // 推导类型
    let a = 1 // a 是数字
    let b = 'hello' // b 是字符串
    let c = [true, false]; // 布尔类型数组
    

    去掉注解后,类型并没有变。并且如果尝试修改 a 的类型,ts 也会报错。就像这样:

    前端 Typescript 入门
    (图片来源网络,侵删)
    let a = 1 // a 是数字
    // 尝试替换成字符串,vscode 会提示:不能将类型“boolean”分配给类型“number”。
    a = true
    

    Tip:有人说“最好让 ts 推导类型,少数情况才需要显示注解类型”。

    另外虽然大量错误 ts 在编译时无法捕获,例如堆栈溢出、网络断连,这些属于运行时异常。ts 能做的是将 js 运行时的报错提到编译时。比如以下代码:

    前端 Typescript 入门
    (图片来源网络,侵删)
    const obj = { width: 10, height: 15 };
    // 提示 heigth 属性写错了
    const area = obj.width * obj.heigth;
    let a = 1 + 2
    let b = a + 3
    // 鼠标以上 c,可以看到 c 对应的类型
    let c = {
      apple: a,
      banana: b
    }
    

    类型断言

    请看示例:

    let arr = [1, 2, 3]
    // r 为3
    const r = arr.find(item => item > 2)
    // “r”可能为“未定义”。ts(18048)
    // const r: number | undefined
    r * 5 // {1}
    

    行 {1} 处的 r 会错误提示,说 r 可能会是 undefined。

    要解决这个问题,可以使用类型断言:用来告诉编译器一个值的具体类型,当开发者比编译器更了解某个值的具体类型时,可以使用类型断言来告诉编译器应该将该值视为特定的类型。

    类型断言有两种形式,分别是尖括号语法和 as 语法。这里说一下 as 语法:value as type。请看示例:

    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    

    上述示例改成这样,r 就不会报错了。

    // 告诉编译器,r 一定是一个 number
    const r = arr.find(item => item > 2) as number
    r * 5
    

    Tip:由于需要人为干预,所以使用起来要谨慎

    基础类型

    js 基本类型大概有这些:

    let num1 = 10;
    let str1 = 'Hello';
    let isTrue = true;
    let undefinedVar;
    let nullVar = null;
    const symbol1 = Symbol('description');
    const bigIntNum = 9007199254740991n;
    let notANumber = NaN;
    let infinite = Infinity;
    console.log(typeof num1); // number
    console.log(typeof str1,); // string
    console.log(typeof isTrue); // boolean
    console.log(typeof undefinedVar); // undefined
    console.log(typeof nullVar); // object
    console.log(typeof symbol1); // symbol
    console.log(typeof bigIntNum); // bigint
    console.log(typeof notANumber); // number
    console.log(typeof infinite); // number
    

    ts 中基本类型有:

    // let v1: String = 'a' - 大写 String 也可以
    let v1: string = 'a'
    let v2: number = 1
    let v3: boolean = true
    let v4: null = null
    let v5: undefined = undefined
    // 字符串或者null
    let v6: string | null = null
    // 错误:不能将类型“5”分配给类型“1 | 2 | 3”
    let v7: 1 | 2 | 3 = 5
    // 正确
    let v8: 1 | 2 | 3 = 2
    

    联合类型

    数组

    ts 数组有两种方法,看个人喜好即可。请看示例:

    // 方式一
    // 定义一个由数字组成的数组
    let arr1: number[] = [2, 3, 4]
    // 报错:不能将类型“string”分配给类型“number”
    let arr2: number[] = [2, 3, 4, '']
    // 方式二
    let arr3: Array = ['a', 'b', 'c']
    // 报错:不能将类型“number”分配给类型“string”。
    let arr4: Array = ['a', 'b', 'c', 4]
    
    元组

    在 TypeScript 中,元组(Tuple)是一种特殊的数组类型,它允许您指定一个固定长度和对应类型的数组

    let arr5:[string, number, string] = ['a', 1, 'b']
    // 报错:不能将类型“[string, number]”分配给类型“[string, number, string]”。源具有 2 个元素,但目标需要 3 个。
    let arr6:[string, number, string] = ['a', 1]
    // 正确
    arr6[0] = 'a2'
    // 错误:不能将类型“number”分配给类型“string”。
    arr6[0] = 1
    // 第三个添加 ? 表明可选,这样只传入 2 个数也不会报错
    let arr7:[string, number, string?] = ['a', 1, 'b']
    
    枚举

    枚举需要使用关键字 enum。请看示例:

    // 就像定义对象,不过不需要 =
    enum TestEnum {
      a,
      b,
      c,
    }
    // 1
    console.log(TestEnum.b);
    // b
    console.log(TestEnum[1]);
    // string
    console.log(typeof TestEnum[1]);
    

    ts 可以自动为枚举类型中的各成员推导对应数字。上面示例推导结果:

    enum TestEnum {
      a = 0,
      b = 1,
      c = 2,
    }
    

    也可以自己手动设置:

    enum TestEnum2 {
      a = 3,
      b = 13,
      c = 23,
    }
    // 13
    console.log(TestEnum2.b);
    

    比如这个,c 就是 b 的下一个数字: 

    enum TestEnum3 {
      a,
      b = 13,
      c,
    }
    // 14
    console.log(TestEnum3.c);
    

    使用场景:比如你之前根据订单状态写了如下代码,可以用枚举来增加可读性。

    if(obj.state === 0){
    }else if(obj.state === 1){
    }else if(obj.state === 2){
    }else if(obj.state === 3){
    }
    
    // 优化后
    enum 订单状态{
      取消,
      上线,
      发送,
      退回,
      ...
    }
    if(obj.state === 订单状态.取消){
    }else if(obj.state === 订单状态.上线){
    }else if(obj.state === 订单状态.发送){
    }else if(obj.state === 订单状态.退回){
    }
    

    函数

    定义一个函数,参数报错:

    // 参数 a 和 b报错。例如:a - 参数“a”隐式具有“any”类型。
    function fn1(a, b){
      return a + b
    }
    

    定义参数类型:

    function fn2(a: number, b : number){
      return a + b
    }
    

    定义参数 b 可选,返回值是 number类型。请看示例:

    // b是可选。
    // 必选的放左侧,可选的放后侧
    function fn5(a: number, b?: number): number{
      return 10
    }
    // 应有 1-2 个参数,但获得 0 个。
    fn5()
    

    定义参数 a 的默认值,rest是一个字符串数组:

    // a 有一个默认值 10
    function fn7(a = 10, b?: number, ...rest:string[]): number{
      return 10
    }
    fn7(1,2, 'a', 'b')
    
    void

    通常用于函数,表示没有 return 的函数。

    function fn3(a: number, b : number):void{
      // 不能将类型“number”分配给类型“void”。
      return a + b
    }
    function fn4(a: number, b : number): void{
      
    }
    

    接口

    通常用于对象的定义。请看示例:

    interface Person{
      name: string,
      age: number
    }
    const p: Person = {
      name: 'peng',
      age: 18
    }
    // 报错:类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。ts(2741)
    const p2: Person = {
      name: 'peng',
    }
    

    类型别名

    比如定义了一个变量 v1,其类型可以是 number 或 string,但是好多地方都是这个类型:

    let v1: number | string = 3
    

    我们可以通过 type 定义一个别名。就像这样:

    // 定义别名 Message
    type Message = number | string
    let v2: Message = 'hello'
    // 报错:不能将类型“boolean”分配给类型“Message”
    let v3: Message = true
    

    泛型

    比如定义如下一个处理 number 的函数:

    function fn1(a: number, b:number): number[]{
      return [a, b]
    }
    

    假如以后想把这个函数作为一个通用函数,除了可以处理 number,还可以处理 string 等其他类型,比如:

    function fn1(a: string, b:string): string[]{
      return [a, b]
    }
    

    a: string | number 又交叉了。就像这样:

    function fn1(a: string | number, b:string | number): string[]{
      return [a, b]
    }
    

    这里可以使用泛型,请看示例:

    // 定义一个变量,比如 T
    function fn1(a: T, b:T): T[]{
      return [a, b]
    }
    fn1(11, 11)
    fn1('a', 'a')
    // 正确,ts 会自动推导
    fn1('a', 'a')
    

    再看一个泛型示例:

    // 参数 arr 是 T 类型的数组
    // 返回 T 类型或 undefined
    function firstElement(arr: T[]): T | undefined {
        return arr[0];
    }
    firstElement(['a', 'b'])
    

    函数重载

    java 中函数重载是定义多个方法,调用时根据参数类型和数量的不同执行不同的方法。例如下面定义两个 add:

    // 方法重载示例:两个参数的相加
    public int add(int a, int b) {
        return a + b;
    }
    // 方法重载示例:三个参数的相加
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    

    ts 这里重载和 java 中的有些不同,可以称之为函数重载申明。

    比如首先我们写了一个数字相加或字符串相加的方法:

    // 数字相加
    // 字符串相加
    function combine(x: number | string, y: number | string): number | string {
      if (typeof x === 'number' && typeof y === 'number') {
        return x + y;
      } else if (typeof x === 'string' && typeof y === 'string') {
        return x + y;
      }
      // 处理其他情况
      return 'Invalid input';
    }
    console.log(combine(1, 2)); // 输出:3
    console.log(combine('hello', 'world')); // 输出:helloworld
    

    这里有两个问题:

    // 问题一:鼠标移动到 combine 显示:
    // function combine(x: number | string, y: number | string): number | string
    console.log(combine(1, 2));
    console.log(combine('hello', 'world'));
    // 问题二:传入 number和 string 不合法,但不报错。鼠标移动到 combine 显示:
    // function combine(x: number | string, y: number | string): number | string
    console.log(combine(1, 'two')); // 输出:Invalid input
    

    现在加上函数重载申明,就能解决上述两个问题。请看示例:

    // 函数重载
    function combine(x: number, y: number): number;
    // 变量名可以不是x、y
    function combine(x2: string, y2: string): string;
    function combine(x: number | string, y: number | string): number | string {
      // 不变
    }
    // function combine(x: number, y: number): number (+1 overload)
    console.log(combine(1, 2));
    // function combine(x: string, y: string): string (+1 overload)
    console.log(combine('hello', 'world'));
    // 报错:没有与此调用匹配的重载。
    //   第 1 个重载(共 2 个),“(x: number, y: number): number”,出现以下错误。
    //   第 2 个重载(共 2 个),“(x: string, y: string): string”,出现以下错误。ts(2769)
    console.log(combine(1, 'two'))
    

    接口继承

    直接看示例:

    interface Person{
      name: string,
      age: number
    }
    // Student 继承 Person
    interface Student extends Person{
      school: string
    }
    // 提示p缺少3个属性
    // 类型“{}”缺少类型“Student”中的以下属性: school, name, agets(2739)
    const p: Student = {
    }
    

    Student 继承 Person,有了3个属性。

    类的修饰符

    类的修饰符有:public、private、protected、static、readonly...。用法请看下文:

    比如有这样一段正常的js代码:

    class People{
        constructor(name){
            this.name =name;
        }
        // 不需要逗号
        sayName(){
            console.log(this.name)
        }
    }
    let people = new People('aaron')
    people.sayName() // aaron
    

    放在 ts(比如  ts在线运行环境) 中会报错如下:

    Parameter 'name' implicitly has an 'any' type.
    Property 'name' does not exist on type 'People'.
    Property 'name' does not exist on type 'People'.
    

    需要修改如下两处即可消除所有错误:

     class People{
    -    constructor(name){
    +    // 消除ts报错:类型“People”上不存在属性“name”
    +    name: string
    +    constructor(name: string){       
             this.name =name;
         }
         // 不需要逗号
    

    其中 name: string 的作用:声明 People 类有个必填属性。实例化 People 类的时候,必须传入一个 string 类型的 name 属性。

    接着加一个可选属性 age:

      // 通过?将 age 改成可选。解决:属性“age”没有初始化表达式,且未在构造函数中明确赋值。
      age?: number
    

    可以设置默认值:

      // 根据默认值推断类型,而且是必选属性
      money = 100
    

    Tip:稍后我们会看到对应的 js 是什么样子。

    属性默认是 public,自身可以用,继承的子类中也可以使用。public 还可以这么写,效果和上例等价:

        constructor(name){
    -    name: string
    -    constructor(name: string){       
    +    constructor(public name: string){       
             this.name =name;
         }
    

    另外还有 private 表明只能在类中使用。protected 只能在类和子类中使用。请看示例:

    class People{
        ...
        // 属性默认是 public,自身可以用、继承也能用
        public money2 = 200
        private money3 = 300
        protected money4 = 400
        constructor(name: string){
            this.name =name;
        }
        sayName(){
            console.log(this.name)
        }
    }
    let people = new People('aaron')
    console.log(people.money);
    // 属性“money3”为私有属性,只能在类“People”中访问。ts(2341)
    console.log('people.money3: ', people.money3); // 300
    // 属性“money4”受保护,只能在类“People”及其子类中访问。ts(2445)
    console.log('people.money4: ', people.money4); // 400
    

    注:虽然 vscode 报错,但浏览器控制台还是输出了。或许 ts 只是静态编译,对应的js 没有做特殊处理,比如 private 声明 money4,实际上并没有实现。请看ts在线运行环境 ts 对应的 js:

    // ts
    class People{
        name: string
        age?: number
        money = 100
        public money2 = 200
        private money3 = 300
        protected money4 = 400
        constructor(name: string){
            this.name =name;
        }
        sayName(){
            console.log(this.name)
        }
    }
    let people = new People('aaron')
    console.log('people.money3: ', people.money3);
    console.log('people.money4: ', people.money4);
    
    // 对应的js
    "use strict";
    class People {
        constructor(name) {
            this.money = 100;
            this.money2 = 200;
            this.money3 = 300;
            this.money4 = 400;
            this.name = name;
        }
        sayName() {
            console.log(this.name);
        }
    }
    let people = new People('aaron');
    console.log('people.money3: ', people.money3);
    console.log('people.money4: ', people.money4);
    

    js 中静态属性使用如下:

        protected money4 = 400
        // 静态属性
    +   static flag = 110
    console.log('People.flag: ', People.flag);
    

    例如将静态属性设置成私有,只能在类中使用。请看示例:

      // 静态属性
      private static flag = 110
    // 报错:属性“flag”为私有属性,只能在类“People”中访问。
    console.log('People.flag: ', People.flag);
    

    多个修饰符可以一起使用,但有时候需要注意顺序,vscode 也会给出提示。就像这样:

    // “static”修饰符必须位于“readonly”修饰符之前。ts(1029)
    readonly static flag = 110
    

    比如定义一个静态只读属性:

      static readonly flag2 = 110
    // 报错:无法为“flag2”赋值,因为它是只读属性。ts(2540)
    People.flag2 = 111
    

    类的存取器

    感觉就是 js 的 get 和 set。比如下面就是一个 js 的get、set示例:

    class People {
        constructor(name) {
            this.name = name;
        }
        get name() {
            return 'apple';
        }
        set name(v) {
            console.log('set', v);
        }
    }
    let people = new People('aaron') // set aaron
    people.name = 'jia' // set jia
    console.log(people.name); // apple
    

    对应 ts 中的存取器就是这样:

    class People{
        constructor(name: string){
            this.name = name;
        }
        get name(){
          return 'apple'
        }
        set name(v){
          console.log('set', v)
        }
    }
    let people = new People('aaron') // set aaron
    people.name = 'jia' // set jia
    console.log(people.name); // apple
    

    注:这个例子很可能会栈溢出,就像这样:

    class People{
        constructor(name: string){
            this.name = name;
        }
        get name(){
          return 'apple'
        }
        set name(v){
          console.log('v: ', v);
          // 栈溢出
          // 报错:VM47:10 Uncaught RangeError: Maximum call stack size exceeded
          this.name = v
        }
    }
    let people = new People('aaron')
    

    所以可以这么写:

    class People {
        private _name: string = ''
      
        get name(): string{
          return 'peng'
        }
      
        set name(val: string){
          this._name = val
        }
      }
      let people = new People()
      
      people.name
      // 报错:属性“_name”为私有属性,只能在类“People”中访问。ts(2341)
      people._name
    

    不写类型,ts 也会自动推导,比如去除类型后也可以。就像这样:

    // 自动推导类型
    class People {
        private _name = 'peng'
      
        get name(){
          return 'peng'
        }
      
        set name(val){
          this._name = val
        }
      }
    let people = new People()
    

    抽象类

    抽象类(abstract),不允许被实例化,抽象属性和抽象方法必须被子类实现。更像一个规范。请看示例

    abstract class People {
      // 可以有抽象属性和方法
      abstract name: string
      abstract eat(): void
      // 也可以有普通属性和方法
      say() {
        console.log('hello: ' + this.name)
      }
    }
    // 如果不实现 name 和 eat 方法则报错
    class Student extends People{
      name: string = '学生'
      // 既然没报错 - 抽象类中返回是 void,这里返回string
      eat(){
        return 'eat apple'
      }
    }
    const s1 = new Student()
    s1.say()
    console.log(s1.eat()); // eat apple
    

    抽象类定义了一个抽象属性、一个抽象方法,一个具体方法。子类必须实现抽象属性和抽象方法,子类实例可以直接访问抽象类中具体的方法。请看对应的 js 代码,你就能很明白。

    class People {
        say() {
            console.log('hello: ' + this.name);
        }
    }
    class Student extends People {
        constructor() {
            super(...arguments);
            this.name = '学生';
        }
        eat() {
            return 'eat apple';
        }
    }
    const s1 = new Student();
    s1.say();
    console.log(s1.eat());
    

    类实现接口

    前面我们用接口定义了一个类型:

    interface Person{
      name: string,
      age: number
    }
    const p: Person = {
      name: 'peng',
      age: 18
    }
    

    抽象类如果只写抽象方法和属性,那么就和接口很相同了。另外接口用 interface 关键字定义,子类可以实现 implements(注意这个单词是复数) 多个接口(不能同时继承多个)。请看示例:

    interface People {
      name: string
      eat(): void
    }
    interface A{
      age: number
    }
    // 实现两个接口,所有属性和方法都需要实现
    class Student implements People, A{
      name: string = '学生'
      age = 100
      // 既然没报错
      eat(){
        return 'eat apple'
      }
    }
    const s1 = new Student()
    console.log(s1.eat()); // eat apple
    

    泛型类

    使用类时,除了可以使用接口来规范行为,还可以将类和泛型结合,称为泛型类。

    比如现在 deal 是处理 string 的方法:

    class People {
        value: string;
        constructor(value: string) {
            this.value = value;
        }
        deal(): string {
            return this.value;
        }
    }
    const p1 = new People('peng')
    p1.deal()
    

    后面我需要 deal 又能处理 number,这样就可以使用泛型。就像这样:

    class People {
        value: T;
        constructor(value: T) {
            this.value = value;
        }
        deal(): T {
            return this.value;
        }
    }
    const p1 = new People('peng')
    p1.deal()
    const p2 = new People(18)
    p2.deal()
    

    多个泛型写法如下:

    class Pair {
        private first: T;
        private second: U;
        constructor(first: T, second: U) {
            this.first = first;
            this.second = second;
        }
        public getFirst(): T {
            return this.first;
        }
        public getSecond(): U {
            return this.second;
        }
    }
    // 使用带有多个泛型类型参数的泛型类
    let pair1 = new Pair(1, "apple");
    console.log(pair1.getFirst()); // 1
    console.log(pair1.getSecond()); // apple
    let pair2 = new Pair("banana", true);
    console.log(pair2.getFirst()); // banana
    console.log(pair2.getSecond()); // true
    

    其他

    Error Lens:提供了一种更直观的方式来展示代码中的问题,如错误、警告和建议,以帮助开发者更快速地识别和解决问题。

    vscode 直接安装后,会将红色错误提示直接显示出来,无需将鼠标移到红色波浪线才能看到错误提示。

    原文链接:https://www.cnblogs.com/pengjiali/p/18104072

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码