你所不知道的typescript

638 阅读6分钟

基础

1. 基础类型

  • 常用:boolean、number、string、array、enum、any、void
  • 不常用:tuple、null、undefine、never
数组
const numberArr = [1, 2, 3];

const arr: number[] = [1,2,3];
// 除此之外,还可以使用数组泛型
const _arr: Array<number> = [1, 2, 3];

const stringArr: string[] = ['a', 'b', 'c'];
// 如果这个数组里面既存数字又存字符串,如何写
const arr1: (number | string)[] = [1, '2' ,3];

// 除了基本类型的数组,对象类型的数组怎么写
const objectArr: {name: string, age: number}[] = [{name:'a', age:16}]
// 将上面的写法简化下,利用 type alias 类型别名
type User = {name: string, age: number}
const objectArr1: User[] = [{name:'a', age:16}]

2. interface 和 type

参考:juejin.cn/post/684490…

  • interface

    1. 对象interface
      1. 设置需要存在的普通属性
      2. 设置可选属性
      3. 设置只读属性
    interface Person {
        name: string
        bool?: boolean
        readonly timestamp: number
        readonly arr: ReadonlyArray<number> // 此外还有 ReadonlyMap/ReadonlySet
    }
    
    let p1: Person = {
        name: 'oliver',
        bool: true, // ✔️️ 可以设置可选属性 并非必要的 可写可不写
        timestamp: + new Date(), // ✔️ 设置只读属性
        arr: [1, 2, 3] // ✔️ 设置只读数组
    }
    
    let p: Person = {
        age: 'oliver', // ❌ 多出来的属性
        name: 123 // ❌ 类型错误
    }
    
    p1.timestamp = 123 // ❌ 只读属性不可修改
    p1.arr.pop() // ❌ 只读属性不可修改
    
    1. 函数 Interface

    Interface 还可以用来规范函数的形状。Interface 里面需要列出参数列表返回值类型的函数定义。

    interface Func {
    // ✔️ 定于这个函数接收两个必选参数都是 number 类型,以及一个可选的字符串参数 desc,这个函数不返回任何值
    (x: number, y: number, desc?: string): void
    }
    
    const sum: Func = function (x, y, desc = '') {
        // const sum: Func = function (x: number, y: number, desc: string): void {
        // ts类型系统默认推论可以不必书写上述类型定义
        console.log(desc, x + y)
    }
    
    sum(32, 22)
    
  • type

    type User = {
        name: string
        age: number
    };
    
    type SetUser = (name: string, age: number) => void;
    
    function getName(user: User) {
        return user.name
    }
    
相同点

1. 都允许拓展

// interface extends interface
interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

// type extends type
type Name = { 
  name: string; 
}
type User = Name & { age: number  };

// interface extends type
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

type extends interface
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}
不同点
  1. type 可以声明基本类型别名
type s = string
  1. type 语句中还可以使用 typeof 获取实例的 类型进行赋值
type div = document.createElement('div');
  1. interface可以合并声明
interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

3. enum枚举

enum d1 {
    a,
    b,
    c,
}
enum d1 {
    a,
    b = 'two',
    c = 'three',
}

// 赋值了非数字的,下一个一定要赋值,除非它是最后一个,例如下面这种情况会报错
enum d1 {
    a,
    b = 'two',
    c,
}

4. 泛型 T(Type)

简单说就是:泛指的类型,不确定的类型,可以理解为一个占位符(使用T只是习惯,使用任何字母都行)

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。
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'); // ['x', 'x', 'x']

// 第一个T相当于一个变量,调用函数时createArray<string>...相当于给T赋值string类型

5. 类

class Gen {
    age: number 变量的类型限制的声明;
    constructor(age: number) {
        this.age = age;
    }

    getAge() {
        return this.age;
    }
}

const gen = new Gen(18);

// 与es6相比,多了age: number 变量的类型限制的声明
继承
class Animal {
  age: number
  constructor(age: number) {
    this.age = age
  }
  getAge() {
    console.log(this.age)
  }
}

class Cat extends Animal {
  say() {
    console.log('miao~')
  }
}

const cat = new Cat(18)

cat.getAge()  // 
cat.say()  // miao~

复杂一点的继承

class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

// 派生类:重写constructor
// 在派生类中,必须调用super
// super相当于指向父类,如果在constructor直接使用super(),想当与调用父类的constructor
公共,私有与受保护的修饰符
  • 在typescript中,默认public
  • 当成员被标记成private时,它就不能在声明它的类的外部访问,比如这样会报错 image.png
  • protected修饰符与private修饰符的行为很相似,但protected成员在派生类中仍然可以访问
存取器 get、set
class A {
  private _name: string = 'world';

  get name(): string {
    return this._name;
  }

  set name(name: string) {
    this._name = 'hello' + name
  }
}

const a = new A()
console.log(a.name)

a.name = 'Johnny'
console.log(a.name)

// 只是使用a.name时,就会执行get name
// 当给a.name赋值时,相当于调用set name,因此set需要一个参数
// 如果只有get,那么相当于该变量的属性为readonly 

6. 函数

函数的返回值
  • 有返回值:number、string、boolean
    function a(x: number): number {
      return x
    }
    
    function b(x: number): boolean {
      return !!x
    }
    
  • 无返回值:void
类型推断
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };

// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

相当于

type Adder = (a: number, b: number) => number;

let myAdd: Adder = function(x: number, y: number) {
  return x + y
}
可选参数和默认参数

参数后面加“?”

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

7. 类型推论

最佳通用类型

当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。例如下面相当于const arr: number[] = [1],因为赋值的数组中只有number这个类型,所以当push一个不是number的值时就会报错

image.png

8. 类型兼容性

  • TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。比如:

    interface Named {
        name: string;
    }
    
    let x: Named;
    // y's inferred type is { name: string; location: string; }
    let y = { name: 'Alice', location: 'Seattle' };
    x = y;
    
    // 虽然可以这样写,但只能获取x.name无法获取x.location
    

    但不可以

    interface Named {
        name: string;
        age: number;
    }
    
    let x: Named;
    // y's inferred type is { name: string; location: string; }
    let y = { name: 'Alice', location: 'Seattle' };
    x = y; // error
    
    // x必须接受一个string属性的name和一个number的age
    
  • 比较两个函数

    let x = (a: number) => 0;
    let y = (b: number, s: string) => 0;
    
    y = x; // OK
    x = y; // Error
    
    // 若a给b赋值,则a中的每一个参数都能在b中找到对应的参数 (一定是对应的参数,参数名可以随意)
    

    个人理解:上面的赋值相当于

    let x: (a: number) => number = (b: number, s: string) => 0  // error
    
    let y: (b: number, s: string) => number = (a: number) => 0  // ok
    
  • 类:只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内 image.png

9. 高级类型

交叉类型(&)

交叉类型说简单点就是将多个类型合并成一个类型

T & U
interface a {
  name: string;
  age: number;
}

interface b {
  tall: number
}

const people: a & b = {
  name: 'wong',
  age: 18,
  tall: 176
}
联合类型(|)

联合类型的语法规则和逻辑 “或” 的符号一致,表示其类型为连接的多个类型中的任意一个

T | U
type InnerType = 'default' | 'primary' | 'danger'
类型别名
type InnerType = 'default' | 'primary' | 'danger'

interface Button {
  type: InnerType
  text: string
}
类型索引 keyof
interface Button {
  type: string
  text: string
  age: number
}

type ButtonKeys = keyof Button

// keyof相当于Object.keys
// type ButtonKeys = 'type' | 'text' | 'age'

interface a {
  type: ButtonKeys;
}

let b: a = {
  type: 'age'
}
类型约束(extends)
type BaseType = string | number | boolean

// 这里表示 copy 的参数
// 只能是字符串、数字、布尔这几种基础类型
function copy<T extends BaseType>(arg: T): T {
  return arg
}
类型映射(in)
interface a {
  text: string;
  name: string;
}

type onlyRead<T> = {
  readonly [P in keyof T]: T[P];
}

const c: onlyRead<a> = {
  text: 'tt',
  name: 'nn'
}

理解:

// keyof T得到 'text' | 'name'
// p通过in去遍历'text' | 'name'
// 首次遍历p就是'text', 所以T['text']就是string,所以就是text: string --> readonly text: string

// 最终得到的就是

interface _a {
  readonly text: string;
  readonly name: string;
}