TypeScript学习记录-function,对象,类

78 阅读9分钟

function类型

方法作为参数

  • 使用函数类型表达式:(a: string) => void
  • 使用 type:type GreetFunction = (a: string) =>void;

调用签名

// 声明一个方法的 type
type DescribableFunction = {
  description: string;
  // 注意:在参数列表和返回类型之间使用 : 而不是 =>
  (someArg: number): boolean;
};

// 定义 doSomething,接收一个方法作为参数,类型是上面定义的 type
function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

// 如果想要传入 doSomething 中,入参和返回值类型必须与上面定义的 type 对应上
function myFunc(someArg: number) {
  return someArg > 3;
}

myFunc.description = "default description";
 
doSomething(myFunc);

构造签名

type SomeConstructor = {
  new (s: string): SomeObject;
};

function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

// 组合方法
interface CallOrConstruct {
  (n?: number): string;
  new (s: string): Date;
}

泛型函数

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);

推断

使用多个

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}
 
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

约束条件

function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}
 
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// 报错! Numbers don't have a 'length' property
const notOK = longest(10, 100);

使用约束值

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
}

错误的,因为如果传入一个数组和一个数字,特殊情况下会有问题,比如:

// 'arr'方法 返回 { length: 6 }
const arr = minimumLength([1, 2, 3], 6);

// 报错,因为 对象 { length: 6 } 没有 slice 方法
console.log(arr.slice(0));

指定类型参数

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

// 如果这么使用就会出错
const arr = combine([1, 2, 3], ["hello"]);

// 修改:手动指定 type
const arr = combine<string | number>([1, 2, 3], ["hello"]);

可选参数(?)

function f(x?: number) {
  // ...
}

f(); // OK
f(10); // OK

注意:尽管参数被指定为 number 类型,但 x 参数实际上将具有 number | undefined 类型

在函数中声明this

const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();

// 注意,需要使用 function 而不是箭头函数来获得此行为
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

其他类型

void

不返回值的函数的返回值

object

  • 指的是任何非基础值(string、number、bigint、boolean、symbol、null 或 undefined)
  • 这与空对象类型 { } 不同
  • 也与全局类型 Object 不同

unknown

  • 代表任何值
  • 这类似于 any 类型,但更安全,因为使用 unknown 值做任何事情都是不合法的
  • 可以描述接受任何值而不在函数体中包含 any 值的函数
function safeParse(s: string): unknown {
  return JSON.parse(s);
}
 
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);

never

  • 从未观察到的值
  • 在返回类型中,这意味着函数抛出异常或终止程序的执行

Function

全局类型 Function 描述了 bind、call、apply 等属性,以及 JavaScript 中所有函数值上的其他属性。它还具有 Function 类型的值始终可以被调用的特殊属性;这些调用返回 any:

function doSomething(f: Function) {
  return f(1, 2, 3);
}

如果需要接受任意函数但不打算调用它,则类型 () => void 通常更安全

剩余形参和实参

剩余形参

给出的任何类型注释必须采用 Array<T>T[] 形式,或者元组类型

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

剩余实参

const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

对象类型

属性修饰符

可选属性(?)

interface PaintOptions {
  shape: Shape;
  // 可有可无
  xPos?: number;
  yPos?: number;
}

readonly 属性

只读,不能改

interface SomeType {
  readonly a: string;
}

注意:只是针对这个属性的,如果这个属性映射一个对象,对象里面的内容还是可以修改的

索引签名

不知道类型属性的所有名称,但确实知道值的类型的情况下,使用索引签名来描述可能值的类型

interface StringArray {
  [index: number]: string;
}
 
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];

索引签名属性只允许使用某些类型:stringnumbersymbol、模板字符串模式以及仅由这些组成的联合类型

interface NumberOrStringDictionary {
  // 索引:返回值的类型
  // 强制所有属性与其返回类型匹配
  [index: string]: number | string;
  length: number; // ok, length is a number
  name: string; // ok, name is a string
}

// 防止修改
interface ReadonlyStringArray {
  readonly [index: number]: string;
}

扩展类型

interface Colorful {
  color: string;
}
 
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

交叉类型(&)

组合作用,必须同时拥有属性

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
 
type ColorfulCircle = Colorful & Circle;

类型操作

泛型

function identity<Type>(arg: Type): Type {
  return arg;
}

let output = identity<string>("myString"); // 显示声明

let output = identity("myString"); // 类型推断

使用泛型类型变量

想使用 length 获取入参的长度

function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length);
  return arg;
}
// 或者
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
  console.log(arg.length); // Array has a .length, so no more error
  return arg;
}

泛型类型

function identity<T>(arg: T): T {
  return arg;
}
 
let myIdentity: <T>(arg: T) => T = identity;
// 或者写成这样
let myIdentity: { <T>(arg: T): T } = identity;


// 实际上等于 js 中的
function identity(arg) {
    return arg;
}
let myIdentity = identity;

抽取转成接口:

interface GenericIdentityFn {
  <Type>(arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn = identity;


// 优化
interface GenericIdentityFn<Type> {
  (arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}
 
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
  return x + y;
};

泛型约束

通过接口改写前面想要获取长度的例子

interface Lengthwise {
  length: number;
}
 
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length);
  return arg;
}

// 报错,没有传入长度
loggingIdentity(3);

loggingIdentity({ length: 10, value: 3 });

泛型约束中使用类型参数

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
 
let x = { a: 1, b: 2, c: 3, d: 4 };
 
getProperty(x, "a");
getProperty(x, "m");	// 报错,x上不存在属性 m 

在泛型中使用类类型

function create<Type>(c: { new (): Type }): Type {
  return new c();
}

泛型参数默认值

declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(
  element?: T,
  children?: U
): Container<T, U>;
 
const div = create(); 
// const div: Container<HTMLDivElement, HTMLDivElement[]>
 
const p = create(new HTMLParagraphElement()); 
// const p: Container<HTMLParagraphElement, HTMLParagraphElement[]>

keyof类型运算符

keyof 操作符接受一个对象类型作为参数,并将其键生成一个字符串或数字字面量并集

type Point = { x: number; y: number };
type P = keyof Point;  // "x" | "y"

// 如果该类型具有 string 或 number 索引签名,则 keyof 将返回这些类型:
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // number

type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // string | number

typeof类型运算符

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
// type P = {
//     x: number;
//     y: number;
// }

索引访问类型

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // type Age = number

type I1 = Person["age" | "name"]; // type I1 = string | number     
type I2 = Person[keyof Person]; // type I2 = string | number | boolean


type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];	// type I3 = string | boolean

const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];
 
type Person = typeof MyArray[number];    
// type Person = {
//     name: string;
//     age: number;
// }


type Age = typeof MyArray[number]["age"];  // type Age = number

// Or
type Age2 = Person["age"];  // type Age2 = number


// 重构
type key = "age";
type Age = Person[key];

条件类型

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string; // type Example1 = number      
type Example2 = RegExp extends Animal ? number : string;  // type Example2 = string    

映射类型

映射类型建立在索引签名的语法之上,用于声明未提前声明的属性类型:

type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};
 
const conforms: OnlyBoolsAndHorses = {
  del: true,
  rodney: false,
};

映射类型是一种泛型类型,它使用 PropertyKey 的联合来迭代键来创建类型:

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

type Features = {
  darkMode: () => void;
  newUserProfile: () => void;
};
 
type FeatureOptions = OptionsFlags<Features>;
// type FeatureOptions = {
//     darkMode: boolean;
//     newUserProfile: boolean;
// }

映射修饰符

  • 在映射期间可以应用两个额外的修饰符:readonly? 分别影响可变性和可选性
  • 可以通过添加前缀 -+ 来移除或添加这些修饰符。如果不添加前缀,则假定为 +
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
  readonly id: string;
  readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;
// type UnlockedAccount = {
//     id: string;
//     name: string;
// }


type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};
 
type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};
 
type User = Concrete<MaybeUser>;
// type User = {
//     id: string;
//     name: string;
//     age: number;
// }

通过 as 重新映射键

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

// 利用模版字符串
type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;
// type LazyPerson = {
//     getName: () => string;
//     getAge: () => number;
//     getLocation: () => string;
// }

// 通过条件类型生成 never 来过滤掉键:
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type KindlessCircle = RemoveKindField<Circle>;
// type KindlessCircle = {
//     radius: number;
// }

// 映射任意联合,不仅是 string | number | symbol 的联合,还可以映射任何类型的联合:
type EventConfig<Events extends { kind: string }> = {
    [E in Events as E["kind"]]: (event: E) => void;
}
 
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
 
type Config = EventConfig<SquareEvent | CircleEvent>
// type Config = {
//     square: (event: SquareEvent) => void;
//     circle: (event: CircleEvent) => void;
// }

成员

字段

// 空
class Point {}

// 字段
class Point {
  x: number;
  y: number;
}

// 初始化
class Point {
  x = 0;
  y = 0;
}

// 打算在构造函数外初始化一个字段
class OKGreeter {
  name!: string;
}

--strictPropertyInitialization:设置控制类字段是否需要在构造函数中初始化

readonly

防止修改

class Greeter {
  readonly name: string = "world";
 
  constructor(otherName?: string) {
    if (otherName !== undefined) {
      this.name = otherName;
    }
  }
 
  err() {
    // 报错
    this.name = "not ok";
  }
}

构造器

class Point {
  x: number;
  y: number;
 
  // Normal signature with defaults
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

class Point {
  // Overloads
  constructor(x: number, y: string);
  constructor(s: string);
  constructor(xs: any, y?: any) {
    // TBD
  }
}

// 超类调用
class Base {
  k = 4;
}
 
class Derived extends Base {
  constructor() {
    // Prints a wrong value in ES5; throws exception in ES6
    console.log(this.k); // 报错
    super();
  }
}

getter/setter

class C {
  _length = 0;
  get length() {
    return this._length;
  }
  set length(value) {
    this._length = value;
  }
}

// 从 TypeScript 4.3 开始,可以使用不同类型的访问器来获取和设置
class Thing {
  _size = 0;
 
  get size(): number {
    return this._size;
  }
 
  set size(value: string | number | boolean) {
    let num = Number(value);
 
    // Don't allow NaN, Infinity, etc
 
    if (!Number.isFinite(num)) {
      this._size = 0;
      return;
    }
 
    this._size = num;
  }
}

推断规则:

  • 如果 get 存在但没有 set,则属性自动为 readonly
  • 如果不指定 setter 参数的类型,则从 getter 的返回类型推断
  • gettersetter 必须有相同的 成员可见性

索引签名

class MyClass {
  [s: string]: boolean | ((s: string) => boolean);
 
  check(s: string) {
    return this[s] as boolean;
  }
}

继承

  • implements:是否满足接口
  • extends:继承父类

成员可见性

  • public:在任何地方访问
  • protected:类的子类可见
  • private 类似于 protected,但不允许从子类访问成员

静态成员

class MyClass {
  static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
console.log(MyClass.x);
MyClass.printX();

// 使用相同的 public、protected 和 private 可见性修饰符
class MyClass {
  private static x = 0;
}
console.log(MyClass.x); // 报错

// 继承
class Base {
  static getGreeting() {
    return "Hello world";
  }
}
class Derived extends Base {
  myGreeting = Derived.getGreeting();
}

// static 类中的块
class Foo {
    static #count = 0;
 
    get count() {
        return Foo.#count;
    }
 
    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.#count += lastInstances.length;
        }
        catch {}
    }
}

为什么没有静态类

  • TypeScript(和 JavaScript)没有一个名为 static class 的构造,就像 C# 一样
  • 这些构造之所以存在,是因为这些语言强制所有数据和函数都在一个类中;因为 TypeScript 中不存在该限制,所以不需要它们。只有一个实例的类通常只表示为 JavaScript/TypeScript 中的普通对象
  • 例如,不需要 TypeScript 中的 “静态类” 语法,因为常规对象(甚至顶层函数)也可以完成这项工作:
// Unnecessary "static" class
class MyStaticClass {
  static doSomething() {}
}
 
// Preferred (alternative 1)
function doSomething() {}
 
// Preferred (alternative 2)
const MyHelperObject = {
  dosomething() {},
};

泛型类

class Box<Type> {
  contents: Type;
  constructor(value: Type) {
    this.contents = value;
  }
}
 
const b = new Box("hello!"); // const b: Box<string>