美文网首页
TypeScript基础超全示例详解

TypeScript基础超全示例详解

作者: Dr_喜禾 | 来源:发表于2022-02-23 17:07 被阅读0次
    // 安装ts
    npm install -g typescript
    // 编译
    tsc hello.ts
    // 插件 这个插件可以让ts文件 编译后直接执行
    npm install -g ts-node 
    ts-node hello.ts
    

    1、基本数据类型

    // 基本数据类型
    let str: string = 'hello'
    let num: number = 12
    let b: boolean = true
    let u: undefined = undefined
    let n: null = null
    let anyTing: any = '123'
    
    // 函数没有返回值时使用 void
    function test(): void {
        console.log("test");
    }
    // 声明一个void类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null
    let unusable: void = undefined;
    
    // 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量
    // 这样不会报错
    let num: number = undefined;
    // 这样也不会报错
    let u: undefined;
    let num: number = u;
    
    // void 类型的变量不能赋值给 number 类型的变量
    // 这样会报错
    let u: void;
    let num: number = u;
    // Type 'void' is not assignable to type 'number'.
    
    
    
    // never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
    
    // never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。
    
    // 返回never的函数必须存在无法达到的终点
    function error(message: string): never {
        throw new Error(message);
    }
    
    // 推断的返回值类型为never
    function fail() {
        return error("Something failed");
    }
    
    // 返回never的函数必须存在无法达到的终点
    function infiniteLoop(): never {
        while (true) {
        }
    }
    

    2、数组类型

    let arr: number[] = [1, 2, 3]
    let arr2: string[] = ['a', 'b', 'c']
    // 使用数组泛型,Array<元素类型>
    let list: Array<number> = [1, 2, 3];
    
    let a: number[] = [1, 2, 3, 4];
    let ro: ReadonlyArray<number> = a;
    ro[0] = 12; // error!
    ro.push(5); // error!
    ro.length = 100; // error!
    
    // 就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:
    a = ro; // error!
    a = ro as number[];
    
    function aTest(){
       // 类数组
        console.log(arguments) // IArguments
    }
    

    3、元组Tuple

    let tom: [string, number] = ['tom', 24]
    
    // 赋值长度必须等于设置长度
    let x: [string, number];
    x = ['hello', 10]; // OK
    x = [10, 'hello']; // Error
    
    // 当访问一个已知索引的元素,会得到正确的类型:
    console.log(x[0].substr(1)); // OK
    console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
    
    // 当访问一个越界的元素,会使用联合类型替代:
    x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
    console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString
    
    x[6] = true; // Error, 布尔不是(string | number)类型
    

    4、类型接口

    interface Person {
      readonly id: number;
      name: string;
      age: number;
      gender ?: string; // 可选属性
    
      // 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
      [propName: string]: any; // ⚠️注意: 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
      // 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
      [propName: string]: string | number;
    }
    let tom: Person {
      id: 1,
      name: 'tom',
      age: 18
    }
    // 定义的变量比接口少或者多一些属性是不允许的, 可选属性除外
    

    5、函数类型

    function getSum (x: number, y:number, z?:number ): number {
      if(z){
        return x + y + z
      }else {
        return x + y
      }
    }
    let s1 = getSum(1, 9)
    console.log(s1) // 10
    // 注意,输入多余的(或者少于要求的)参数,是不被允许的:
    
    
    let mySum2 = function(x: number, y: number): number {
        return x + y;
    };
    // 上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum2,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum2 添加类型,则应该是这样:
    let mySum2:(x: number, y: number)=> number = function(x: number, y: number): number {
        return x + y;
    };
    
    let mySum = (x: number, y: number): number => {
      return x + y;
    };
    let mySum1: (x: number, y: number) => number = mySum
    
    // 用接口定义函数的形状
    interface ISum {
        (x: number, y: number): number
    }
    let mySum3: ISum = mySum
    
    
    // 用接口定义函数的形状
    interface SearchFunc {
        (source: string, subString: string): boolean;
    }
    
    let mySearch: SearchFunc;
    mySearch = function(source, subString) {
        return source.search(subString) !== -1;
    }
    let res = mySearch('xdes', 'a')
    console.log(res) // false
    
    
    // 默认参数值
    // 此时就不受「可选参数必须接在必需参数后面」的限制了:
    function buildName(firstName: string = 'Tom', lastName: string) {
        return firstName + ' ' + lastName;
    }
    let tomcat = buildName('Tom', 'Cat');
    let cat = buildName(undefined, 'Cat');
    
    // 剩余参数
    // 注意,...rest 参数只能是最后一个参数
    function push(array: any[], ...items: any[]) {
        items.forEach(function(item) {
            array.push(item);
        });
    }
    
    let a = [];
    push(a, 1, 2, 3);
    
    
    // 重载
    // 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
    
    // 比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
    
    // 利用联合类型,我们可以这么实现:
    
    function reverse(x: number | string): number | string | void {
        if (typeof x === 'number') {
            return Number(x.toString().split('').reverse().join(''));
        } else if (typeof x === 'string') {
            return x.split('').reverse().join('');
        }
    }
    // 然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
    
    // 这时,我们可以使用重载定义多个 reverse 的函数类型:
    
    function reverse(x: number): number;
    function reverse(x: string): string;
    function reverse(x: number | string): number | string | void {
        if (typeof x === 'number') {
            return Number(x.toString().split('').reverse().join(''));
        } else if (typeof x === 'string') {
            return x.split('').reverse().join('');
        }
    }
    // 上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
    
    // 注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
    

    6、枚举enum

    // 枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
    
    // 常数项
    // 定义一些常量 比如:周一到周天,三原色红黄蓝,方向上下左右 等等
    const enum direction { // const enum 变成常量枚举, 可以提升性能
      Up,
      Down,
      Left,
      Right
    }
    console.log( direction[0] ) // Up
    console.log( direction['Up'] ) // 0
    
    enum direction { // 常量枚举可以提升性能
        Up = 3, // 默认从0开始,可以手动修改
        Down,
        Left,
        Right
    }
    console.log(direction[3]); // Up
    console.log(direction["Up"], direction["Down"]); // 3, 4
    
    
    // 计算所得项
    enum Color {Red, Green, Blue = "blueaaa".length};
    console.log(Color.Red) // 0
    console.log(Color.Blue) // 7
    

    7、高级类型

    联合类型
    let strORnum: string | number
    strORnum = 'seven'
    strORnum = 7
    
    // 联合类型表示一个值可以是几种类型之一
    // 联合类型只能访问类型都有的方法 取不同类型的 方法的 交集
    function getString(something: string | number): string {
      return something.toString(); // 不会报错
    }
    function getString(something: string | number): number {
       return something.length // 会报错 ,number没有length属性
    }
    
    交叉类型
    // 交叉类型 是将多个类型合并为一个类型
    // 交叉类型取不同类型方法的 并集
    type IPerson = { name: string } & { age: number }
    let person:IPerson = { name: 'xiaoming', age: 12 }
    
    
    类型别名
    // 类型别名 用type给类型起个别名, 类型别名常用于联合类型交叉类型
    // 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名
    // 例如:传入的参数 要么是string 要么是一个函数。如果是string直接返回; 如果是函数返回其执行结果
    type NameResolver = () => string;
    type NameOrResolver = string | NameResolver;
    function getName(n: NameOrResolver): string {
        if (typeof n === 'string') {
            return n;
        } else {
            return n();
        }
    }
    
    字面量类型
    // 字面量类型
    type Direction = 'up' | 'down' | 'left' | 'right'
    let toWhere: Direction = 'up' // toWhere只能是 'up','down','left','right' 这四个值
    
    // ⚠️注意,类型别名与字符串字面量类型都是使用 type 进行定义。
    
    
    可辨识联合
    // 可辨识联合
    
    // 可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式
    // 具有3个要素:
    // 具有普通的单例类型属性 — 可辨识的特征。 单例类型 多数是指枚举成员类型和数字/字符串字面量类型
    // 一个类型别名包含了那些类型的联合 — 联合。
    // 此属性上的类型保护。
    
    interface Square {
        kind: "square";
        size: number;
    }
    interface Rectangle {
        kind: "rectangle";
        width: number;
        height: number;
    }
    interface Circle {
        kind: "circle";
        radius: number;
    }
    // 把它们联合到一起
    type Shape = Square | Rectangle | Circle;
    
    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
    }
    
    // 当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了 Triangle到 Shape,我们同时还需要更新 area:
    interface Triangle {
        kind: "triangle";
        side: number;
        height: number;
    }
    
    type Shape = Square | Rectangle | Circle | Triangle;
    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
        // should error here - we didn't handle case "triangle"
    }
    
    // 有两种方式可以实现。 首先是启用 --strictNullChecks并且指定一个返回值类型:
    function area(s: Shape): number { // error: returns number | undefined
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
    }
    
    // 第二种方法使用 never类型,编译器用它来进行完整性检查:
    function assertNever(x: never): never {
        throw new Error("Unexpected object: " + x);
    }
    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
            default: return assertNever(s); // error here if there are missing cases
            // 这里, assertNever检查 s是否为 never类型—即为除去所有可能情况后剩下的类型
        }
    }
    
    let t: Triangle = {
        kind: "triangle",
        side: 3,
        height: 2
    };
    let s11 = area(t);
    console.log(s11);
    
    

    8、类型断言

    // 通常这会发生在 你清楚地知道一个实体具有比它现有类型更确切的类型
    
    // 类型断言有两种形式。 其一是“尖括号”语法:
    let someValue: any = "this is a string";
    let strLength: number = (<string>someValue).length;
    
    // 另一个为as语法: 当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。
    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    
    
    
    // 将一个联合类型断言为其中一个类型
    // 之前提到过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:
    // 而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如:
    
    function getLen(input: string | number): number {
      const str = input as string
       if(str.length){
         return str.length
       }else {
         const num = input as number
         return num.toString().length
       }
    }
    
    // 或者使用 typeof
    function getLen1(input: string | number): number {
       if(typeof input === 'string'){
         return input.length
       }else {
         return input.toString().length
       }
    }
    
    
    // 例2:
    interface Cat {
        name: string;
        run(): void;
    }
    interface Fish {
        name: string;
        swim(): void;
    }
    
    function isFish(animal: Cat | Fish) {
        if (typeof (animal as Fish).swim === 'function') {
            return true;
        }
        return false;
    }
    

    9、类

    // public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
    // private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
    // protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的,不允许实例访问
    // static 不需要实例化 , 可以通过类直接调用
    
    class Animal {
      name string;
      constructor(name: string) {
        this.name = name 
      }
      get name() {
        return 'Jack'
      }
      set name(value){
        console.log('setter: ' + value)
      }
      static isAnimal(a) {
        return a instanceof Animal;
      }
      sayHi(): string {
        console.log(`${this.name} Hi`)
      }
    }
    let a = new Animal('mimi') // setter: mimi
    a.name = 'tom' // // setter: tom
    
    
    let b = new Animal('mimi') // setter: mimi
    b.name = 'tom' // // setter: tom
    console.log(b.name) // Jack
    
    // 继承
    class Dog extends Animal {
       constructor(name){
         super(name)
       }
      sayHi() {
        console.log(`wang, ${this.name} Hi`)
      }
    }
    
    let d = new Dog('Focus')
    d.sayHi() // wang, Focus Hi
    
    // static 不需要实例化 , 可以通过类直接调用
    let a = new Animal('Jack');
    Animal.isAnimal(a); // true
    a.isAnimal(a); // TypeError: a.isAnimal is not a function
    
    
    // 当成员被标记成 private时,它就不能在声明它的类的外部访问
    class Animal {
        private name: string;
        constructor(theName: string) { this.name = theName; }
    }
    
    new Animal("Cat").name; // 错误: 'name' 是私有的.
    
    

    10、类和接口

    // 没有公共父类的类,但是拥有相同的功能,这部分功能就可以提取一个接口 用implements来实现
    // 比如 车和手机 没有公共的父类  但是都有打开音乐的按钮
    // 再比如,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:
    
    // 提取公共报警接口 Alarm
    interface Alarm {
        alert(): void;
    }
    
    interface Battery {
        checkBatteryStaus(): void;
    }
    
    class Door {}
    class SecurityDoor extends Door implements Alarm {
        alert() {
            console.log("SecurityDoor alert");
        }
    }
    
    // 继承接口可以用implements关键字, 多个的话用逗号隔开
    class Car implements Alarm, Battery {
        alert() {
            console.log("car alert");
        }
        checkBatteryStaus() {
            console.log(`检查电池`)
        }
    }
    
    // 接口可以继承接口
    interface AlarmAndBattery extends Alarm {
      checkBatteryStaus(): void;
    }
    class Car implements AlarmAndBattery {
        alert() {
            console.log("car alert");
        }
        checkBatteryStaus() {
            console.log(`检查电池`)
        }
    }
    
    
    // 接口也可以继承类
    class Point {
        x: number;
        y: number;
    }
    
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {x: 1, y: 2, z: 3};
    

    11、泛型

    // 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
    // 类似一个占位符,在执行的时候才知道是什么类型
    
    // // 实现一个函数:传入什么,输出什么;传入的类型与返回的类型应该是相同的
    // 下面这个例子中:泛型和any的一个区别是: 泛型的输入输出类型是确定的 
    // 使用any类型会导致:如果我们传入一个数字,任何类型的值都有可能被返回,any没有准确的定义返回值的类型。
    
    function getResult<T> (a: T): T {
      return a
    }
    let g1 = getResult('a')
    let g2 = getResult(1)
    let g3 = getResult([1])
    
    // 实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
    function createArray<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    let c1 = createArray(4, 'aa')
    let c2 = createArray(5, 6)
    console.log(c1) //  ['aa', 'aa', 'aa', 'aa']
    console.log(c2) //  [6, 6, 6, 6, 6]
    
    
    // 多个类型参数
    functing change<T, U>(tuple: [T, U]): [U, T]{
      return [tuple[1], tuple[0]]
    }
    change(['seven', 7]) // [7, 'seven']
    

    12、泛型约束

    // 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
    function loggingIdentity<T>(arg: T): T {
        console.log(arg.length); // index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
        return arg;
    }
    
    interface Lengthwise {
        length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);
        return arg;
    }
    
    let l1 = loggingIdentity('a')
    // let l2 = loggingIdentity(9)
    let l3 = loggingIdentity(['a','b'])
    let l4 = loggingIdentity({length: 10, name: 'a'})
    
    

    13、泛型与类

    // 泛型和类
    // 定义一个 数字队列类 ,先进先出
    class Queen {
      private data = [];
      push(item: number) {
        return this.data.push(item)
      }
      pop(): number {
        return this.data.shift()
      }
    }
    const q = new Queen()
    q.push(1)
    console.log(q.pop().toFixed())
    
    // 现在我又有一个字符串的队列 在写一个类显然不是最优解
    // 可以用泛型来解决
    class Queen<T> {
      private data = [];
      push(item: T) {
        return this.data.push(item)
      }
      pop(): T {
        return this.data.shift()
      }
    }
    
    const q1 = new Queen<number>()
    q1.push(1)
    q1.push(2)
    console.log(q1.pop().toFixed())
    console.log(q1.pop().toFixed())
    
    const q2 = new Queen<string>()
    q2.push('tom')
    q2.push('jack')
    console.log(q2.pop().length)
    console.log(q2.pop().length)
    
    
    

    14、泛型和接口

    // 泛型和接口
    interface KeyPair <T, U> {
      key: T;
      value: U
    }
    
    const a: KeyPair<number, string> = { key: 1, value: 'hello'}
    const b: KeyPair<string, number> = { key: 'hi', value: 21 }
    
    let arr: number[] = [1, 2, 3]
    let arr2: Array<number> = [1, 2, 3]
    let arr3: Array<string> = ['1', '2', '3']
    

    15、泛型和函数

    // 泛型描述一个函数
    interface IPlus<T> {
      (a: T, b: T) : T
    }
    
    function plus(a: number, b: number): number {
      return a + b
    }
    
    function connect(a: string, b:string ) : string {
      return a + b
    }
    
    const a: IPlus<number> = plus
    const b: IPlus<string> = connect
    

    16、内置类型

    // Omit 以一个类型为基础支持剔除某些属性,然后返回一个新类型。
    type Person = {
        name: string;
        age: number;
        location: string;
    };
    
    type PersonWithoutLocation = Omit<Person, 'location'>;
    
    let p: PersonWithoutLocation = {
      name: 'focus',
      age: 2
    }
    
    // Partial 把某个接口类型中定义的属性变成可选的
    
    // const Jerry: Person = {
    //     age: 10,
    //     name: "Jerry"
    // };
     
    type AnonymousPeople = Partial<Person>;
    
    const tom: AnonymousPeople = {
        name: "Tom"
    };
    

    17、声明文件

    // 什么是声明语句§、
    // 假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。
    
    // 我们通常这样获取一个 id 是 foo 的元素:
    
    $('#foo');
    // or
    jQuery('#foo');
    // 但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西:
    
    jQuery('#foo');
    // ERROR: Cannot find name 'jQuery'.
    // 这时,我们需要使用 declare var 来定义它的类型2:
    
    declare var jQuery: (selector: string) => any;
    
    jQuery('#foo');
    // 上例中,declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
    
    jQuery('#foo');
    
    
    
    // 通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件
    // src/jQuery.d.ts
    
    declare var jQuery: (selector: string) => any;
    // src/index.ts
    
    jQuery('#foo');
    // 声明文件必需以 .d.ts 为后缀。
    
    // 一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。
    /**
    /path/to/project
    ├── src
    |  ├── index.ts
    |  └── jQuery.d.ts
    └── tsconfig.json
    */
    
    // 假如仍然无法解析,那么可以检查下 tsconfig.json 中的 files、include 和 exclude 配置,确保其包含了 jQuery.d.ts 文件。
    
    
    // jquery
    npm install @types/jquery --save-dev
    
    // node
    npm install @types/node --save-dev
    
    
    
    

    相关文章

      网友评论

          本文标题:TypeScript基础超全示例详解

          本文链接:https://www.haomeiwen.com/subject/tinolrtx.html