发布于

Typescript的个人学习理解

Authors
  • avatar
    Name
    田中原
    Twitter

Typescript的个人学习理解

目录

包装类型与字面量类型

TypeScript 对五种原始类型分别提供了大写和小写两种类型。

  • Boolean 和 boolean
  • String 和 string
  • Number 和 number
  • BigInt 和 bigint
  • Symbol 和 symbol

大写类型同时包含包装对象和字面量两种情况

小写类型只包含字面量,不包含包装对象。

const s1: String = 'hello' // 正确
const s2: String = new String('hello') // 正确

const s3: string = 'hello' // 正确
const s4: string = new String('hello') // 报错

建议只使用小写类型,不使用大写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。而且,TypeScript 把很多内置方法的参数,定义成小写类型,使用大写类型会报错

泛型

泛型的意义

泛型都是关于将两个或多个值与相同类型相关联!

联合类型

/**
 * 联合类型 = 类型A | 类型 B
 */
type NumOrStr = number | string
let num: NumOrStr = 1
let str: NumOrStr = 1

函数

重载签名、实现签名

实现签名必须包含所有的重载签名

实现签名不能被直接调用。实现签名在外部不可见

function makeDate(timestamp: number): Date
function makeDate(m: number, d: number, y: number): Date
/**
 * 函数实现时的签名需包含(兼容)重载签名
 * 实现签名在实际使用时不能被直接调用
 */
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d)
  } else {
    return new Date(mOrTimestamp)
  }
}
const d1 = makeDate(12345678)
const d2 = makeDate(5, 5, 5)

/**
 * 实现签名不能被调用
 * No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
 */
const d3 = makeDate(1, 3)

构造签名

/**
 * 构造签名
 * js通过new操作符调用构造函数式通常是创建对象
 * 通过new 关键字可以编写构造签名
 */
class Animal {
  numLegs: number = 4
}

// AnimalConstructor是一个构造签名
type AnimalConstructor = new () => Animal

// create函数接受一个构造签名,返回一个Animal实例
function create(c: AnimalConstructor): Animal {
  return new c()
}

const a = create(Animal)

直接调用构造函数

/**
 * 直接调用构造函数
 * 某些对象,如 JavaScript 的 Date 对象,可以使用或不使用 new 进行调用。
 * 可以同时使用调用签名和构造签名表达
 * NOTE: es6/7 Class语法实例化时必须使用new
 * 故此场景仅适用函数构造函数
 */
interface CallOrConstruct {
  new (s: string): Date
  (n?: number): number
}

参数

extends用于约束参数类型

参数与返回共用一个泛型

参数与返回结构相同,但不是同一个对象,使用

/**
 * 使用[泛型]约束时,如果返回值也使用同一个泛型
 * 意味着参数与返回值必须是同一个对象,而不能仅仅符合结构
 */
function minimumLength<T extends Type>(obj: T, minimum: number): T {
  if (obj.length >= minimum) {
    return obj
  } else {
    /**
     * Type '{ length: number; }' is not assignable to type 'Type'.
     * '{ length: number; }' is assignable to the constraint of type 'Type',
     * but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
     */
    return obj
  }
}

指定函数参数的类型

/**
 * 指定函数参数的类型
 */
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2)
}

let arr = combine<string | number>([1, 2, 3], ['hello'])
// ✅ 给泛型指定了联合类型

arr = combine([1, 2, 3], ['hello'])
// ❌Type 'string' is not assignable to type 'number'.

可选参数

/**
 * 可选参数
 */
declare fn(x?: number): void

Rest Parameters 剩余形参

  • 通过 any[]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)

Rest Arguments 剩余实参

不限参数数量

// push接收不限量的参数
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2)

限制参数数量/元组

// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
const args = [8, 5];

// (method) Math.atan2(y: number, x: number):number
// Math.atan2 的参数数量实际有限、
const angle = Math.atan2(...args);
// 报错信息:
A spread argument must either have a tuple type or be passed to a rest parameter.

解决方案:as const const上下文(TODO:理解as const

// Inferred as 2-length tuple
// as const 推断转为长度为2的元组
const args = [8, 5] as const
// OK
const angle = Math.atan2(...args)

形参解构

function sum({ a, b, c }) {
  console.log(a + b + c)
}
sum({ a: 10, b: 3, c: 9 })

function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c)
}

// 形参过多时另外命名类型更好
type ABC = { a: number; b: number; c: number }
function sum({ a, b, c }: ABC) {
  console.log(a + b + c)
}

this

ts默认行为可以自动推导出this指向。

如果有手动指定this类型的需要,通过以下方式处理

this参数类型

js规范要求不能有参数名为this,ts利用此规则在函数内指定this类型

通常出现在回调式api的情况下

TODO: 此处需加深理解。此处给出的例子也不完整不利于理解。补充更易理解的例子

目前理解:通过设置一个this参数类型,指定了函数体内部this的值

const user = {
  id: 123,

  admin: false,
  becomeAdmin: function () {
    this.admin = true
  },
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[]
}

const db = getDB()

// 不能使用箭头函数,猜测:否则会导致this绑定至当前代码块或全局
const admins = db.filterUsers(function (this: User) {
  return this.admin
})

函数相关的其他类型

void

void 表示不返回值的函数的返回值。只要函数没有任何返回语句,或者不从这些返回语句返回任何显式值,它就是推断类型。

js函数不返回值时,实际获取为undefined。ts的函数在不指定返回值类型时默认推导为void。但实际void不能看做等于undefined

void和undefined区别

返回类型为 void 的上下文类型不会强制函数不返回某些内容。

返回类型为 void 的函数,在实现时可以返回任何其他值,但会被忽略。

type voidFunc = () => void

const f1: voidFunc = () => {
  return true
}

const f2: voidFunc = () => true

const f3: voidFunc = function () {
  return true
}

但是在将函数返回值赋值给其他变量是,变量会被推断为void类型

// v1,v2,v3类型都是void
const v1 = f1()
const v2 = f2()
const v3 = f3()

object

  • 可以指代所有非原始类型
  • 基本上不会被使用到
  • object ≠ Object (全局类型)
  • 函数可以视为object类型,instanceof Object

unknown

  • 类似于any,但无法做任何操作
  • 比any类型更安全
  • 使用场景待补充
function f1(a: any) {
  a.b() // OK
}

function f2(a: unknown) {
  a.b()
  // Object is of type 'unknown'.
  // 类型安全:无法在函数体内调用a.b
}

function safeParse(s: string): unknown {
  return JSON.parse(s)
}

// Need to be careful with 'obj'!
// 增加类型安全,防止使用obj.xx?
const obj = safeParse(someRandomString)

never

  • never 类型表示从未观察到的值。在返回类型中,这意味着函数抛出异常或终止程序的执行。
  • 收窄联合类型时,排除掉所有可能类型后,变量的类型会被推断为never
function fnUnknown(x: string | number) {
  if (typeof x === 'string') {
    x // 此时类型为 sting
  } else if (typeof x === 'number') {
    x // 此时类型为 number
  } else {
    x // 此时类型已无其他可能,has type 'never'!
  }
}
React的useRef 导致类型“never”上不存在属性“xxx”
const textInputRef = useRef();
  useEffect(() => {
    if (textInputRef && textInputRef.current) {
      textInputRef.current?.focus(); // property 'focus' does not exist on type 'never'
    }
  }, []);

  return (
      <input ref={textInputRef} />
  )

useRef 没有指定元素的类型,应该给useRef泛型指定类型才可以:如const textInputRef = useRef<HTMLInputElement>(null);

Function

  • 全局类型 Function 描述了 JavaScript 中所有函数值上的bind、call、apply和其他属性。
  • 类型为Function时,可以被当函数调用,函数调用返回值为any
  • 类型不安全,尽量避免使用
function doSomething(f: Function) {
  return f(1, 2, 3)
}

类型运算符

keyof

keyof 是一个单目运算符,接受一个对象类型作为参数,返回该对象的所有键名组成的联合类型。

type MyObj = {
  foo: number
  bar: string
}

type Keys = keyof MyObj // 'foo'|'bar'

由于 JavaScript 对象的键名只有三种类型,所以对于任意对象的键名的联合类型就是string|number|symbol

对于没有自定义键名的类型使用 keyof 运算符,返回never类型,表示不可能有这样类型的键名。

type KeyT = keyof any // string | number | symbol

type KeyT = keyof object // never

extends

extends继承与扩展

/**
 * extends关键字表示继承
 * SubType extends SuperType {
 *  ...SubType新成员
 * }
 */

interface Vector1D {
  x: number
}

// Vector2D 为 { x: number, y: number }
interface Vector2D extends Vector1D {
  y: number
}
// vector3D 为 { x: number, y: number, z: number }
interface Vector3D extends Vector2D {
  z: number
}

extends用于逻辑判断

/**
 * extends结合三元表达式,实现条件判断
 * extends关键字用于条件判断
 * ts使用的是结构化类型,只要结构满足则将子类视继承父类
 */

interface Vector1D {
  x: number
}
interface Vector2D {
  y: number
  x: number
}

// 通过extends和三元判断T是否为U的子类型
type SubTypeOf<T, U> = T extends U ? true : false

type A = SubTypeOf<Vector2D, Vector1D> // true

type B = SubTypeOf<Vector1D, Vector1D> // true

type C = SubTypeOf<Vector2D, Vector1D> // false

extends用于约束参数类型

/**
 * 通过泛型、extends,约束函数参数类型
 * longest的参数被约束为具有length属性的对象
 */
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')
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100)