ts相关

Posted by Qz on May 18, 2020

“Yeah It’s on. ”

正文

深入理解 TypeScript

进阶 TypeScript

https://mkosir.github.io/typescript-style-guide/

https://mkosir.github.io/typescript-style-guide/#introduction

interface

在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

任意属性

https://ts.xcatliu.com/basics/type-of-object-interfaces

有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

使用 [propName: string] 定义了任意属性取 string 类型的值。

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。

另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },这是联合类型和接口的结合。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

类 Interface

Interface 也可以用来定义一个类的形状。需要注意的是类 Interface 只会检查实例的属性,静态属性是需要额外定义一个 Interface

// ? PersonConstructor 是用来检查静态部分的
interface PersonConstructor {
    new (name: string, age: number) // ✔️ 这个是用来检查 constructor 的
    typename: string // ✔️ 这个是用来检查静态属性 typename 的
    logname(): void // ✔️ 这个用来检查静态方法 logname 的
}
// ? PersonInterface 则是用来检查实例部分的
interface PersonInterface {
    // new (name: string, age: number) // ❌ 静态方法的检查也不能写在这里 这样写是错误的
    log(): void // : 这里定义了实例方法 log
}

// class Person implements PersonInterface, PersonInterface { ❌ 这样写是错误的
const Person: PersonConstructor = class Person implements PersonInterface {
    name: string
    age: number
    static typename = 'Person type' // 这里定义了一个名为 typename 的静态属性
    static logname() { // 这里定义了一个名为 logname 的静态方法
        console.log(this.typename)
    }
    constructor(name: string, age: number) { // constructor 也是静态方法
        this.name = name
        this.age = age
    }
    log() { // log 是实例方法
        console.log(this.name, this.age)
    }
}

一定要记住静态属性和方法的检查、实例属性和方法的检查是不同的 Interface

interface直接定义函数体

一个完整的.d.ts类型声明

export type Color = string | RgbArray | RGB

export interface InvertColor {
  (color: Color, bw?: boolean | BlackWhite): string // interface 可以直接定义函数体
  asRGB(color: Color, bw?: boolean | BlackWhite): RGB
  asRgbArray(color: Color, bw?: boolean | BlackWhite): RgbArray
}

export const invert: InvertColor;

interface vs type

https://juejin.cn/post/6844903749501059085

结论:能用 interface 实现,就用 interface

相同点:

都可以描述一个对象或者函数

都允许拓展(extends)

不同点:

  • type 可以声明基本类型别名,联合类型,元组等类型

  • interface 能够声明合并

获取interface所有值的类型

使用

type ValueOf<T> = T[keyof T]

举个例子:

enum RtmMessageType {
  UserInfo = "UserInfo",
  BeHost = "BeHost",
}

interface RtmMessageData {
  [RtmMessageType.UserInfo]?: {
    userName: string
    userId: string
    type: RtmMessageType.UserInfo
  }
  [RtmMessageType.BeHost]?: {
    userId: string
    type: RtmMessageType.BeHost
  }
}

用法:

type Test = ValueOf<RtmMessageData>

type

import/export type

导入/导出

import type 是TypeScript 和Flow 中特有的语法,它允许你导入类型而不导入运行时的值。 这通常用于导入类型定义,例如接口、类型别名或类类型。 这种导入方式不会影响生成的JavaScript 代码,因为类型信息在编译时会被移除。与此相似,export type 仅仅提供一个用于类型的导出,在 JavaScript 输出文件中,它也将会被删除。

运算符

! 运算符

x! 将从 x 值域中排除 null 和 undefined 。

function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'. 
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

调用函数时忽略 undefined 类型

type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // Object is possibly 'undefined'.(2532)
  // Cannot invoke an object which is possibly 'undefined'.(2722)
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); //OK
}

确定赋值断言

在 TypeScript 2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:

let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error

function initialize() {
  x = 10;
}

很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

& 运算符

This looks like it’s from the Intersection Types portion of the Language Specification. Specifically, the & appears to be an intersection type literal. As for what it does:

Intersection types represent values that simultaneously have multiple types. A value of an intersection type A & B is a value that is both of type A and type B. Intersection types are written using intersection type literals (section 3.8.7).

交集类型表示同时具有多种类型的值。交集类型A和B的值是同时具有A和B类型的值。

举一例子:

interface A { a: number }  
interface B { b: number }

var ab: A & B = { a: 1, b: 1 };  
var a: A = ab;  // A & B assignable to A  
var b: B = ab;  // A & B assignable to B

Because ab is both of type A and of type B, we can assign it to a and/or b. If ab were only of type B, we could only assign it to b.

& string

Above, a keyof T & string intersection is required because keyof T could contain symbol types that cannot be transformed using template string types.

type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`

这里会自动把联合类型展开并分配,${'cart'}/${'add' | 'remove'} 会被推断成 'cart/add' | 'cart/remove',不过由于我们传入的是 keyof GetMutations<Module> 它还有可能是 symbol | number 类型,所以用 Keys & string 来取其中的 string 类型

| 运算符

联合类型(Union Types)表示取值可以为多种类型中的⼀种,联合类型使⽤ 分隔每个类型。联合类型通常与 null 或 undefined ⼀起使⽤
const fn = (info: strong | null | undefined) => {}

?. 运算符

?.用来判断左侧的表达式是否是 null undefined,如果是则会停止表达式运行,可以减少我们大量的&&运算。

比如我们写出a?.b时,编译器会自动生成如下代码

a === null || a === void 0 ? void 0 : a.b;

这里涉及到一个小知识点:undefined这个值在非严格模式下会被重新赋值,使用void 0必定返回真正的 undefined。

?? 运算符

??与   的功能是相似的,区别在于 ??在左侧表达式结果为 null 或者 undefined 时,才会返回右侧表达式

比如我们书写了let b = a ?? 10,生成的代码如下:

let b = a !== null && a !== void 0 ? a : 10;
  表达式,大家知道的,则对 false、’‘、NaN、0 等逻辑空值也会生效,不适于我们做对参数的合并。

操作符

asserts

https://blog.logrocket.com/assertion-functions-typescript/

asserts 关键字用于定义自定义的类型断言函数,用于告诉编译器某个表达式的类型,并在编译时进行类型检查。

在使用 asserts 关键字进行类型断言时,编译器会进行类型检查,如果类型不匹配,将会抛出一个编译时错误。

function isString(value: unknown): asserts value is string {
  if (typeof value !== "string") throw new Error("Not a string")
}

If we invoke the function above with a given parameter, and it returns correctly, TypeScript knows that value has type string. Hence, it will narrow down its type to string:

const aValue: string|number = "Hello"
isString(aValue)
// The type of aValue is narrowed to string here

举个例子:

export function assertError(value: unknown): asserts value is Error {
	if (value instanceof Error) return
	throw value
}
export function assertNonNullable<T>(
	value: T
): asserts value is NonNullable<T> {
	if (value === null) throw new Error(`Value was null`)
	if (value === undefined) throw new Error(`Value was undefined`)
}

keyof

键值获取 keyof

keyof 可以获取一个类型所有键值,返回一个联合类型,如下:

type Person = {
  name: string;
  age: number;
}
type PersonKey = keyof Person;  // PersonKey得到的类型为 'name' | 'age'
function prop<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

---------------------------------------------------------------

function prop2<T>(obj: T, key: keyof T) {
    return obj[key];
}



let o = {
    p1: 0,
    p2: ''
}

let v = prop(o, 'p1') // is number, K is of type 'p1'
let v2 = prop2(o, 'p1') // is number | string, no extra info is captured

The difference is that in the first case the return type will be T[K] while in the second case it will be T[keyof T].

K can at it’s widest be keyof T but it can be a specific string literal type representing a key. This means if K is a specific property the return value will be of the same type as the property:

keyof any

// Keys 类型为 string | number | symbol 组成的联合类型
type Keys = keyof any

typeof

在 TypeScript 中,typeof 操作符可以用来获取一个变量或对象的类型。

注意typeof操作的是一个变量或对象

注意typeof操作的是一个变量或对象

注意typeof操作的是一个变量或对象

interface Person {
  name: string;
  age: number;
}

const sem: Person = { name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person

注意下面这种写法是错误的:
const Sem = typeof sem;

此外,typeof 操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型,比如:

function toArray(x: number): Array<number> {
  return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]

keyof 结合 typeof 一起使用

https://stackoverflow.com/questions/57086672/element-implicitly-has-an-any-type-because-expression-of-type-string-cant-b

const cats = {
  "Coding Cat": "https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif",
  "Compiling Cat": "https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif",
  "Testing Cat": "https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif"
};

function f(a: keyof typeof cats) {
  console.log(a);
}

// f("x"); // error
f("Coding Cat"); // ok

在遍历中尤其有用

const state: Record<string, string> = {}
for (const key in attr) {
   const value = attr[key as keyof typeof attr]
   state[key] = isString(value) ? value : JSON.stringify(value)
}

enum

枚举是组织收集有关联变量的一种方式

const enum

获得性能提升的一个小技巧是使用常量枚举:

const enum Tristate {
  False,
  True,
  Unknown
}

const lie = Tristate.False;

将会被编译成:

let lie = 0;

编译器将会:

  • 内联枚举的任何用法(0 而不是 Tristate.False);
  • 不会为枚举类型编译成任何 JavaScript(在这个例子中,运行时没有 Tristate 变量),因为它使用内联语法。

void

声明一个 void 类型的变量

声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefinednull

let unusable: void = undefined;

void和never的区别

一旦有人告诉你,never 表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 void,然而实际上,void 表示没有任何类型,never 表示永远不存在的值的类型。

当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never 类型。void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。

enum

enums compile on *.d.ts

https://stackoverflow.com/questions/38553097/how-to-import-an-enum

TypeScript中如何使用自己在d.ts中定义的enum?

结论:.d.ts 中定义的东西不会进入到运行时,你应该只在 .ts 中定义一遍

Never

https://www.zhihu.com/question/354601204/answer/888668879

https://mp.weixin.qq.com/s/PpumNz-3lhJZ4zIUZzbnNg

never 是值集为空的集合。因为集合里面没有值,所以 never 类型就不能被赋值,包括 any 类型的值(这听起来很绕)。也就是说 never 类型代表永远不会发生的类型,或者话句话说是一个底层类型的概念。

never的主要作用就是充当Typescript类型系统里的Bottom Type

top type => unknown

即是top也是bottom => any

never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

  1. 不相交类型的inteserction结果为never:
type result = 1 & 2 // 结果为never
  1. 是任何类型的subtype
type Check<T> = never extends T ? true : false
type result = check<xxx> // 结果始终为true
  1. 除了never,没有其他类型是never的subtype
type Check = never extends never ? true : false // true 类型
let aaa:Check = true
type Check<T> = never extends never ? false : T extends never ? true : false
type result = check<xxx> // 结果始终为false
  1. 布尔运算

union运算的幺元,intersection运算的零元

T | never // 结果为T
T & never // 结果为never

never 实现全面性检查

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}

注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:

type Foo = string | number | boolean;

然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保

controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

为什么需要 never

实现switch中的穷尽枚举

作者:尤雨溪 链接:https://www.zhihu.com/question/354601204/answer/888551021 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

举个具体点的例子,当你有一个 union type:

interface Foo {
  type: 'foo'
}

interface Bar {
  type: 'bar'
}

type All = Foo | Bar

在 switch 当中判断 type,TS 是可以收窄类型的 (discriminated union):

function handleValue(val: All) {
  switch (val.type) {
    case 'foo':
      // 这里 val 被收窄为 Foo
      break
    case 'bar':
      // val 在这里是 Bar
      break
    default:
      // val 在这里是 never
      const exhaustiveCheck: never = val
      break
  }
}

注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:

type All = Foo | Bar | Baz

然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never,产生一个编译错误。所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。

extends

https://juejin.cn/post/6998736350841143326

分配条件类型

  type A1 = 'x' extends 'x' ? string : number; // string
  type A2 = 'x' | 'y' extends 'x' ? string : number; // number
  
  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'> // ?

 // A3的类型是 string | number

这个反直觉结果的原因就是所谓的分配条件类型(Distributive Conditional Types)

对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。

该例中,extends的前参为T,T是一个泛型参数。在A3的定义中,给T传入的是’x’和’y’的联合类型'x' | 'y',满足分配律,于是’x’和’y’被拆开,分别代入P<T>

P<'x' | 'y'> => P<'x'> | P<'y'>

防止条件判断中的分配

  type P<T> = [T] extends ['x'] ? string : number;
  type A1 = P<'x' | 'y'> // number
  type A2 = P<never> // string

在条件判断类型的定义中,将泛型参数使用[]括起来,即可阻断条件判断类型的分配,此时,传入参数T的类型将被当做一个整体,不再分配。

PropertyKey

TypeScript 内置属性

https://www.totaltypescript.com/concepts/propertykey-type

// string | number | symbol
type Example = PropertyKey;

static

  • 访问static成员,用类(class)而不是实例化的对象。
  • static方法只能访问static属性
  • static成员可以保留其值(因为开辟了他自己内部的空间)。

private static与 public static

https://blog.csdn.net/chuigu0767/article/details/100671458

private static 和 public static 都是静态变量,在类加载时就定义,不需要创建对象

private static 是私有的,不能在外部访问,只能通过静态方法调用,这样可以防止对变量的修改

class Test {
    private static aaa = 'aaa'
    public static bbb = 'bb'

    static logA(){
       console.log(Test.aaa) 
    }
}

// Property 'aaa' is private and only accessible within class 'Test'.ts(2341)
console.log(Test.aaa) // error
console.log(Test.bbb) // ok

reference

.d.ts文件中,<reference>指令用于指定对其他类型声明文件的依赖关系。它告诉编译器在构建过程中需要引入其他的类型声明文件。

https://www.tslang.cn/docs/handbook/triple-slash-directives.html

使用<reference>指令可以确保在编译时,相关的类型声明文件能够正确地被引入。这对于使用外部库或框架的项目特别有用,因为类型声明文件能够提供有关这些库或框架的类型信息。

types 和 path 的区别

<reference>指令通常出现在.d.ts文件的顶部,其语法如下:

/// <reference path="..." />  对指定路径的文件引入
/// <reference types="..." /> 指定声明了对某个包的依赖

对这些包的名字的解析与在 import语句里对模块名的解析类似。 可以简单地把三斜线类型引用指令当做 import声明的包。

例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。

仅当在你需要写一个d.ts文件时才使用这个指令。

unknown

unknown 类型只能被赋值给 any 类型和 unknown 类型本身

unknown which is the type-safe counterpart of any

unknown 类型要安全得多,因为它迫使我们执行额外的类型检查来对变量执行操作。 (类型安全版本的any)

let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。

let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

unknown & any

https://stackoverflow.com/questions/51439843/unknown-vs-any

unknown is the parent type of all other types. it’s a regular type in the type system.

any means “turn off the type check”. it’s a compiler directive and kind of meta programming.

unknown 类型要安全得多,因为它迫使我们执行额外的类型检查来对变量执行操作。

any 和 unknown 的最大区别是, unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) ,这导致 any 基本上就是放弃了任何类型检查.

TypeScript 强制要求在使用unknown之前必须确定其具体类型,而any不需要。

举个例子:

class BoardUIStore {
  aaa: string
  constructor() {
    this.aaa = "aaa"
  }
}

const boardUIStore = new BoardUIStore()
const a = boardUIStore.aaa as unknown
const b = boardUIStore.aaa as any

(a as string).toString()   // unknown 必须要设置类型
b.toString()  // any 相当于不进行任何校验

结论:

使用unknown 可以保证类型安全,使用 any 则彻底放弃了类型检查 , 在很多情况下, 我们可以使用 unknow 来替代 any , 既灵活, 又可以继续保证类型安全

泛型

把明确类型的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型,简单点来讲我们可以将泛型理解成为把类型当作参数一样去传递

举个例子:

// 定义一个泛型接口 IPerson表示一个类,它返回的实例对象取决于使用接口时传入的泛型T
interface IPerson<T> {  
  // 因为我们还没有讲到unknown 所以暂时这里使用any 代替  
  new(...args: unknown[]): T;
}

function getInstance<T>(Clazz: IPerson<T>) { 
  return new Clazz();
}

// use it
class Person {}

// TS推断出函数返回值是person实例类型
const person = getInstance(Person);

约束泛型

interface IHasLength { 
  length: number;
}

// 利用 extends 关键字在声明泛型时约束泛型需要满足的条件
function getLength<T extends IHasLength>(arg: T) { 
  // throw error: arr上不存在length属性 
  return arg.length;
}

getLength([1, 2, 3]); // correct
getLength('123'); // correct
getLength({ name: '19Qingfeng', length: 100 }); // correct
// error 当传入true时,TS会进行自动类型推导 相当于 getLength<boolean>(true)
// 显然 boolean 类型上并不存在拥有 length 属性的约束,所以TS会提示语法错误getLength(true); 

工具类型

Partial

Partial<T> 的作用就是将某个类型里的属性全部变为可选项 ?

定义

/**
 * node_modules/typescript/lib/lib.es5.d.ts
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。

例子:

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "Learn TS",
  description: "Learn TypeScript",
};

const todo2 = updateTodo(todo1, {
  description: "Learn TypeScript Enum",
});

Required

既然可以快速地把某个接口中定义的属性全部声明为可选,那能不能把所有的可选的属性变成必选的呢?答案是可以的,针对这个需求,我们可以使用 Required<T> 工具类型,具体的使用方式如下:

interface PullDownRefreshConfig {
  threshold: number;
  stop: number;
}

type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

/**
 * type PullDownRefresh = {
 *   threshold: number;
 *   stop: number;
 * }
 */
type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

同样,我们来看一下 Required<T> 工具类型是如何实现的:

/**
 * Make all properties in T required
 */
type Required<T> = {
  [P in keyof T]-?: T[P];
};

原来在 Required<T> 工具类型内部,通过 -? 移除了可选属性中的 ?,使得属性从可选变为必选的。

指定某个类型为非可选的键

也就说我们将类型中的部分key由可选变为必选

type RequireKeys<T, TNames extends keyof T> = T &
  { [P in keyof T]-?: P extends TNames ? T[P] : never };

举个例子:

type ButtonProps2 = RequireKeys<ButtonProps, "onClick">;  // onClick 变为必须的key

const Button2 = (props: ButtonProps2) => {
  const handleClick: React.MouseEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  > = e => {
    props.onClick(e); // works
  };
};

Pick

何为Pick?

就是从一个复合类型中,取出几个想要的类型的组合,例如:

// 原始类型
interface TState {
	name: string;
	age: number;
	like: string[];
}
// 如果我只想要name和age怎么办,最粗暴的就是直接再定义一个(我之前就是这么搞得)
// 这样的弊端是什么?就是在Tstate发生改变的时候,TSingleState并不会跟着一起改变,所以应该这么写
interface TSingleState {
	name: string;
	age: number;
}

interface TSingleState extends Pick<TState, "name" | "age"> {};

如何实现Pick?

type Pick<T, K extends keyof T> = {
	[key in k]: T[key]
}

Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

举个例子 在WechatMiniprogram中的data

type DataOption = Record<string, any>


interface Data<D extends DataOption> {
    
}

Map 和 Record

While the idea that “TypeScript supports Map natively” is still true, since version 2.1 TypeScript supports something called Record.

TypeScript原生支持Map

但我们也可以使用Record来实现Map

type MyMapLikeType = Record<string, IPerson>;
const peopleA: MyMapLikeType = {
    "a": { name: "joe" },
    "b": { name: "bart" },
};

Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。

利用了分发的原理

定义

// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉。

例子:

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

Extract

Extract<T, U> 的作用是从 T 中提取出 U 。

定义:

// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

如果 T 能赋值给 U 类型的话,那么就会返回 T 类型,否则返回 never 类型。

示例:

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () => void

Omit

https://segmentfault.com/a/1190000022429482

Omit<T, K> 类型让我们可以从另一个对象类型中剔除某些属性,并创建一个新的对象类型:

例子:

type User = {
id: string;
name: string;
email: string;
};

type UserWithoutEmail = Omit<User, "email">;

// 等价于:
type UserWithoutEmail = {
id: string;
name: string;
};

export interface InputProps extends Omit<InputHTMLAttributes<HTMLElement>,'size'>{
  // 是否禁用
  disabled?:boolean
  size?:InputSize
  icon?:IconProp,
  // 前缀 
  prepend?: string | ReactElement
  // 后缀
  append?:string | ReactElement
  onChange?: (e:ChangeEvent<HTMLInputElement>) => void 
}

推导:

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;


type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

difference-between-omit-and-exclude

https://iamshadmirza.com/difference-between-omit-and-exclude-in-typescript

  • Omit utility type works on object type or interfaces to omit one of the key-value pair.
  • Exclude only works on union literal to exclude one of the property.
  • Omit uses Pick and Exclude under the hook.
  • Omit<T, K>:用于去除对象类型 T 中的属性 K。 (用于interface)
  • Exclude<T, U>:用于从联合类型 T 中排除类型 U。 (用于联合类型)

Parameters

拿到函数参数的类型

declare function f1(arg: { a: number; b: string }): void;
 
type T0 = Parameters<() => string>;   
type T0 = []

type T1 = Parameters<(s: string) => void>;
type T1 = [s: string]

type T2 = Parameters<<T>(arg: T) => T>;
type T2 = [arg: unknown]

type T3 = Parameters<typeof f1>;
type T3 = [arg: {
    a: number;
    b: string;
}]

原理:

type Parameters<T extends (...args: any) => any> = T extends ( 
  ...args: infer P 
  ) => any 
    ? P 
    : never 

InstanceType

https://www.typescriptlang.org/docs/handbook/utility-types.html#instancetypetype

定义:

例子:

class C {
  x = 0;
  y = 0;
}

type T0 = InstanceType<typeof C>;
//    ^ = type T0 = C
type T1 = InstanceType<any>;
//    ^ = type T1 = any
type T2 = InstanceType<never>;
//    ^ = type T2 = never
type T3 = InstanceType<string>;
Type 'string' does not satisfy the constraint 'new (...args: any) => any'.
//    ^ = type T3 = any
type T4 = InstanceType<Function>;
Type 'Function' does not satisfy the constraint 'new (...args: any) => any'.
Type 'Function' provides no match for the signature 'new (...args: any): any'.
//    ^ = type T4 = any

ReturnType

ReturnType 的作用是用于获取函数 T 的返回类型。

定义

// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

// 这是一个三元表达式
T extends (...args: any) => infer R ? R : any;
// 返回 R 或者 any

例子:

type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error

function f1(s: string) {
  return { a: 1, b: s };
}


type T14 = ReturnType<typeof f1>;  // { a: number, b: string }

应用redux中的action

// actionCreator.ts

import * as Types from './actionTypes';

type AddTodoAction = {
    type: typeof Types.ADD_TODO;
    payload: string;
}

type RemoveTodoAction = {
    type: typeof Types.REMOVE_TODO,
    payload: number;
}

export type Actions = AddTodoAction | RemoveTodoAction;

export const createAddTodo = (text: string): AddTodoAction => ({
    type: Types.ADD_TODO,
    payload: text,
});

export const createRemoveTodo = (id: number): RemoveTodoAction => ({
    type: Types.REMOVE_TODO,
    payload: id,
});

当我们完成了 actionCreator 和 reducer 的连接!这一点非常重要,只有这样,我们才能借助 ts 的类型保证 actionCreator 返回的值一定和 reducer 内部的 action 类型是一致的。

因为 actionCreator 返回的是 action 对象,为什么我们不使用 ReturnType 直接拿到函数的返回值类型,作为对应的 Action 类型呢,这样就自动完成了关联操作。

import * as Types from './actionTypes';

type AddTodoAction = ReturnType<typeof createAddTodo>;
type RemoveTodoAction = ReturnType<typeof createRemoveTodo>;

export type Actions = AddTodoAction | RemoveTodoAction;

export const createAddTodo = (text: string) => ({
    type: Types.ADD_TODO,
    payload: text,
});

export const createRemoveTodo = (id: number) => ({
    type: Types.REMOVE_TODO,
    payload: id,
});

让我们来看看此时 reducer 内部 action 的类型

推断失败

这个时候就要使用as const

https://blog.csdn.net/yunfeihe233/article/details/108027882

拿到泛型函数的返回类型

https://segmentfault.com/q/1010000015557807

infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

它一定是出现在条件类型中的

infer 代表待推断类型,它的必须和 extends 条件约束类型一起使用。

举个例子:

type ParamType<T> = T extends (param: infer P) => any ? P : T;

在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T

interface User {
  name: string;
  age: number;
}

type Func = (user: User) => void
type Param = ParamType<Func>;   // Param = User
type AA = ParamType<string>;    // string

Awaitable

https://github.com/microsoft/TypeScript/issues/31394

type Awaitable<T> = T | PromiseLike<T>
async function logAnswer(getAnswer: () => Awaitable<number>): Promise<void> {
  const answer = await getAnswer();
  console.log(answer);
}

logAnswer(() => 42);
logAnswer(() => Promise.resolve(42));

NonNullable

https://javascript.plainenglish.io/how-the-typescript-nonnullable-type-works-ccfd68972f02

type myType = string | number | null | undefined
type noNulls = NonNullable<myType>   // string | number

Function

CallableFunction

它表示一个具有函数签名的类型,该函数可以被调用。

在 CallableFunction 类型中,函数签名包含了函数的参数类型和返回类型。

泛型

反向推导

例子:

function create<T>(val: T): T {
  return val;
}
let num: number;
// 泛型没有传入 也能成功推导
const c = create(num);

泛型约束

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

分发

type GetSomeType<T extends string | number> = T extends string ? 'a' : 'b';
let someTypeThree: GetSomeType<string | number>; // what ? 

// 'a' | 'b' 组成的联合类型
  • 分发一定是需要产生在 extends 产生的类型条件判断中,并且是前置类型。
比如T extends string | number ? 'a' : 'b'; 那么此时,产生分发效果的也只有 extends 关键字前的 T 类型,string number 仅仅代表一种条件判断。
  • 其次,分发一定是要满足联合类型,只有联合类型才会产生分发(其他类型无法产生分发的效果,比如 & 交集中等等)。
  • 最后,分发一定要满足所谓的裸类型中才会产生效果。

逆变

逆变的效果函数的参数只允许“从少的赋值给多的”

let fn1!: (a: string, b: number) => void;
let fn2!: (a: string, b: number, c: boolean) => void;

fn2 = fn1; // 正确,被允许  fn1 赋值给 fn2

参数少(父)的可以赋给参数多(子)的那一个。看起来和类型兼容性(多的可以赋给少的)相反,但是通过调用的角度来考虑的话恰恰满足多的可以赋给少的兼容性原则。

上述这种函数之间互相赋值,他们的参数类型兼容性是典型的逆变

协变

let fn1!: (a: string, b: number) => string;
let fn2!: (a: string, b: number) => string | number | boolean;

fn2 = fn1; // correct 
fn1 = fn2 // error: 不可以将 string|number|boolean 赋给 string 类型

这里,函数类型赋值兼容时函数的返回值就是典型的协变场景

断言

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

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

类型守卫与类型区分

let pet = getSmallPet();

// 每一个成员访问都会报错
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}

为了让这段代码工作,我们要使用类型断言:

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}

is 用户自定义的类型守卫

类型守卫就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

在这个例子里,pet is Fish就是类型谓词。 谓词为parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。

每当使用一些变量调用isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

// 'swim' 和 'fly' 调用都没有问题了

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

注意TypeScript不仅知道在if分支里petFish类型; 它还清楚在else分支里,一定_不是_Fish类型,一定是Bird类型。

使用in操作符

in操作符可以作为类型细化表达式来使用。

对于n in x表达式,其中n是字符串字面量或字符串字面量类型且x是个联合类型,那么true分支的类型细化为有一个可选的或必须的属性nfalse分支的类型细化为有一个可选的或不存在属性n

function move(pet: Fish | Bird) {
    if ("swim" in pet) {
        return pet.swim();
    }
    return pet.fly();
}

类型断言

由于可以为null的类型是通过联合类型实现,那么你需要使用类型守卫来去除null。 幸运地是这与在JavaScript里写的代码一致:

function f(sn: string | null): string {
    if (sn == null) {
        return "default";
    }
    else {
        return sn;
    }
}

这里很明显地去除了null,你也可以使用短路运算符:

function f(sn: string | null): string {
    return sn || "default";
}

如果编译器不能够去除nullundefined,你可以使用类型断言手动去除。 语法是添加!后缀:identifier!identifier的类型里去除了nullundefined

错误的情况:

function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // error, 'name' is possibly null
  }
  name = name || "Bob";
  return postfix("great");
}

正确的写法:

function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}

自定义类型保护的类型谓词

function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

推断类型

由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。例如:

let zoo = [new Rhino(), new Elephant(), new Snake()];

这里,我们想让zoo被推断为Animal[]类型,但是这个数组里没有对象是Animal类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型:

let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型,(Rhino | Elephant | Snake)[]

in 关键字

interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}

namespace

https://www.tslang.cn/docs/handbook/namespaces.html

快速编写第三方包 .d.ts 类型声明指南

Validation.ts
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

declare

https://juejin.im/entry/5907f5020ce46300617bfb44

https://ts.xcatliu.com/basics/declaration-files.html

// 作为全局变量使用:
declare namespace UUU{
    let a:number
}
 
// 作为模块加载使用:
declare module "UUU" {
    export =UUU
}

Overload

https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads

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);
const d3 = makeDate(1, 3);

No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.

class

https://www.sitepen.com/blog/advanced-typescript-concepts-classes-and-types

When using the class keyword in TypeScript, you are actually creating two things with the same identifier:

  • A TypeScript interface containing all the instance methods and properties of the class; and
  • A JavaScript variable with a different (anonymous) constructor function type

In other words, the example class above is effectively just shorthand for this code:

// our TypeScript `Point` type
interface Point {
  x: number;
  y: number;
  toString(): string;
}

// our JavaScript `Point` variable, with a constructor type
let Point: {
  new (x: number, y: number): Point;
  prototype: Point;

  // static class properties and methods are actually part
  // of the constructor type!
  fromOtherPoint(point: Point): Point;
};

// `Function` does not fulfill the defined type so
// it needs to be cast to 
Point =  function (this: Point, x: number, y: number): void {
  // ...
};

// static properties/methods go on the JavaScript variable...
Point.fromOtherPoint = function (point: Point): Point {
  // ...
};

// instance properties/methods go on the prototype
Point.prototype.toString = function (): string {
  // ...
};

构造函数

私有构造函数

在类的构造函数中设置为私有访问修饰符(private)的作用是禁止在外部直接实例化类的对象。这样做的目的通常是为了实现类的单例模式或者实现类的对象唯一性。

举个例子:

export abstract class AgoraWidgetBase {
  private _instanceId = ""

  constructor(
    private _widgetController: AgoraWidgetController,
    private _classroomStore: EduClassroomStore,
  ) {}
}

public private 和 protected

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

使用 private 修饰的属性或方法,在子类中也是不允许访问的:

class Animal {
  private name;
  public constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
    console.log(this.name);
  }
}

// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

而如果是用 protected 修饰,则允许在子类中访问:

class Animal {
  protected name;
  public constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
    console.log(this.name);
  }
}

abstract 抽象类

https://ts.xcatliu.com/advanced/class.html

abstract 用于定义抽象类和其中的抽象方法。

  1. 定义其他类的基类(父类)。
  2. 提供一些共同的实现细节。
  3. 强制子类实现指定的抽象方法或遵循某些约定。
  4. 提高代码的重用性和可维护性。

首先,抽象类是不允许被实例化的:

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

let a = new Animal('Jack');

// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.

抽象方法必须在子类中实现,否则将发生编译时错误。

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class Cat extends Animal {
  public eat() {
    console.log(`${this.name} is eating.`);
  }
}

let cat = new Cat('Tom');

// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.

需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类,上面的代码的编译结果是:

var __extends =
  (this && this.__extends) ||
  function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() {
      this.constructor = d;
    }
    d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
  };
var Animal = (function () {
  function Animal(name) {
    this.name = name;
  }
  return Animal;
})();
var Cat = (function (_super) {
  __extends(Cat, _super);
  function Cat() {
    _super.apply(this, arguments);
  }
  Cat.prototype.sayHi = function () {
    console.log('Meow, My name is ' + this.name);
  };
  return Cat;
})(Animal);
var cat = new Cat('Tom');

readonly

readonly关键字将属性设置为只读的,只读属性必须在声明时或者构造函数里被初始化。

type A = number[];
type T = [number, number];

// error 
// 'readonly' type modifier is only permitted on array and tuple literal types.
type RA = readonly A;
type RT = readonly T;

// success
type RA = Readonly<A>;
type RT = Readonly<T>;

readonly vs const

最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const,若做为属性则使用readonly

as

as用作重新赋值

出现报错:

Property 'url' does not exist on type 'DataSourceObject'.
  const renderOption = (item:DataSourceType) => {
    item = item as  DataSourceType<GithubUserProps>
    return (
      <>
        <h2>Name: {item.value}</h2>
         <p>url: {item.url}</p>
      </>
    )
  }

解决方案:

  • 将as的结果赋值给一个新的变量
 interface GithubUserProps {
  login:string,
  url:string,
  avatar_url:string 
}


const renderOption = (item:DataSourceType) => {
    const itemWithGithub = item as  DataSourceType<GithubUserProps>
    return (
      <>
        <h2>Name: {itemWithGithub.value}</h2>
         <p>url: {itemWithGithub.url}</p>
      </>
    )
  }

as const

https://blog.csdn.net/yunfeihe233/article/details/108027882

what-does-the-as-const-mean

告诉 ts 在进行类型推断时告诉 ts,我是一个字面量类型,不要把我简化归约!

TypeScript 3.4 introduces a new construct for literal values called const assertions. Its syntax is a type assertion with const in place of the type name (e.g. 123 as const). When we construct new literal expressions with const assertions, we can signal to the language that

  • no literal types in that expression should be widened (e.g. no going from "hello" to string)
  • object literals get readonly properties
  • array literals become readonly tuples

翻译

  • 表达式中的任何字面量类型都不应该被扩展;
  • 对象字面量的属性,将使用 readonly 修饰;
  • 数组字面量将变成 readonly 元组。

例子;

// Type '"hello"'
let x = "hello" as const;

// Type 'readonly [10, 20]'
let y = [10, 20] as const;

// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;

从官方的介绍中,对我们最有用的是

no literal types in that expression should be widened (e.g. no going from "hello" to string)

对于字面量类型 “hello” 来讲,在类型推导中会被扩展为 string 类型,对于字面量量类型 9999 来说,在推导中会被扩展为 number 类型。

通过 as const 被声明的字面量类型,在类型推导中不会被扩展成为 “父类型”,

在redux中应用

利用这个特性,我们可以在定义 actionType 时候这样写

export const ADD_TODO = 'ADD_TODO' as const;

export const REMOVE_TODO = 'REMOVE_TODO' as const;

这时候,我们再去看看 ts 对 actionCreator 函数返回值类型的推断

转换联合类型

const Values = ["lazy", "eager", undefined] as const;
type LoadingValue = (typeof Values)[number];   // "lazy" | "eager" | undefined

类型编程

获取所有的 actionCreator 函数类型

在实际编写类型的过程中,其实可以注意到,我们进行类型标注的行为,使用 ReturnType 类型函数,多么像在编程,我们传进去一个类型,得到一个新类型。那么是否,我们可以利用这种类型编程的能力,做到自动类型推断呢?

我们只要编写 actionType 和 actionCreator ,reducer 内部会自动的得到 action 类型,不需要我们手动去指定。

先看看我们现在已经有了什么,在我们通过 as const 定义过 actionType 后,我们可以通过 ReturnType 拿到对应 actionCreator 函数返回值的类型,而这个类型,正好是我们需要的 Action 的类型,最后我们标注到 reducer 的参数即可。

所以我们现在做的应该是,怎么获取 actionCreators.ts 文件里的所有 actionCreator 函数类型,并且循环的将所有他们的返回值类型抽出来,并组合成一个可辨识联合类型。

简单需要妥协的做法

我们先解决,如何获取所有的 actionCreator 函数类型?最简单的做法是将所有 actionCreator 定义到一个对象上,我们导出这个对象即可。

export const actionCreators = {
    createAddTodo: (text: string) => ({
        type: Types.ADD_TODO,
        payload: text,
        time: new Date(),
    }) as const,
    createRemoveTodo: (id: number) => ({
        type: Types.REMOVE_TODO,
        payload: id,
    }) as const,
}

偏要勉强的做法

现在我们已经获取到需要的 actionCreator 函数的类型了,只是我们需要将所有的 actionCreator 函数定义到一个对象上。那如果我偏不,偏要勉强,偏要在不改变编写习惯的基础上,达到同样的目标呢。

我们需要想办法把除了 ActionCreator 之外的类型过滤掉。既然要过滤,那我们就要先确定 什么是一个 ActionCreator ?

这里我们对 ActionCreator 下一个定义,一个接受不定参数,返回一个 Action 对象的函数,就是 ActionCreator

什么又是 Action 呢?一个带有 .type 属性的对象就是 Action 对象。

type Action = {
    type: string;
    [otherKey: string]: unknown;
};

type ActionCreator = (...args: unknown[]) => Action;

自动过滤

type ExtractActionCreaotrKey<T> = {
    [K in keyof T]: T[K] extends ActionCreator ? K : never;
}[keyof T]

tsc

TypeScript编译器(The TypeScript Compiler)

TSC是TypeScript编译器的缩写。它是一个TypeScript语言的官方编译器,用于将TypeScript代码转换为可在浏览器或现代JavaScript环境中执行的JavaScript代码。

TSC编译时指定生成d.ts的目录 并解决无法导入package.json和alias别名的问题

// ts config 
{
  "compilerOptions": {
    "resolveJsonModule": true,
    "noEmit": false,
    "rootDir": "./src",
    "outDir": "./dist",
    "emitDeclarationOnly": true,
    "declaration": true,
    "declarationDir": "./dist/types"
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

无法导入package.json

https://chengpeiquan.com/article/tsc-compiles-dts.html

尝试关闭 tsconfig 里的 resolveJsonModule 选项

合并文件

https://github.com/SitePen/dts-generator

这样构建出来的 DTS 文件只有一个,比如 dist/types/index.d.ts

处理别名

如果用了 alias 别名,会发现生成的 DTS 文件并不支持 alias (比如源码里通过 @foo/bar 来代替 src/foo/bar 或者 ../../foo/bar )。

这个也是要借助外部插件来实现转换,这里测试了几款外部工具,最有效的是 tscpaths

需要明确的是,它本身不支持编译生成 DTS 文件,而是在 TSC 编译时,根据 tsconfig.jsonpaths 配置,将 TypeScript 编译出来的 DTS 文件里的绝对路径替换为相对路径。

tsc && tscpaths -p tsconfig.json -s ./src -o ./dist/types

编译单文件

tsc --out final.js class.ts
  • final.js 最终生成文件
  • class.ts 要转化的ts文件

–project,-p

  • 此命令接受一个string

Compile a project given a valid configuration file. The argument can be a file path to a valid JSON configuration file, or a directory path to a directory containing a tsconfig.json file. See tsconfig.json documentation for more details.

script:{
   "test:types": "tsc -p types/test",
}

处理没有默认导出

错误:

import inquirer from "inquirer"
inquirer.prompt()
// tsc 转换后
const inquirer = require("inquirer");
inquirer.default.prompt()

正确:

import * as inquirer from "inquirer"
inquirer.prompt()
// tsc 转换后
const inquirer = require("inquirer");
inquirer.prompt()

使用swc将模块转换时,如果模块中没有默认导出(default export),则需要使用以下语法来导入该模块:

import * as name from 'module';

tsconfig

https://www.tslang.cn/docs/handbook/tsconfig-json.html

配置的项解释

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    // 支持装饰器    
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    // 将每个文件作为单独的模块
    "isolatedModules": true,
    // 在默认情况下,TypeScript 不允许导入时使用 .ts、.tsx、.d.ts 文件的扩展名。
    // 启用 allowImportingTsExtensions 选项后,TypeScript 将允许你在导入时使用这些扩展名。
    "allowImportingTsExtensions":true
    "noEmit": true,
    "jsx": "react"
  },
  "include": [
    "src"
  ]
}

target

  • 使用目标版本 –target ES2016 会告诉编译器不要对 ES2016 的特性进行转换, 比如 ** 运算符.
  • 相似的, –target ES2017 会告诉编译器不要转换 ES2017 的特性, 比如 async/await.
  • –target ESNext 则对应最新的 ES 提案特性的支持.

jsx

https://www.typescriptlang.org/docs/handbook/jsx.html

TypeScript ships with three JSX modes: preserve, react, and react-native

https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html

老版本:

// 转换前
import React from 'react';
function App() {
  return <h1>Hello World</h1>;
}
//转换后
import React from 'react';
function App() {
  return React.createElement('h1', null, 'Hello world');
}

因此老版本需要在jsx文件中显式的引入React,不然语法检测会通不过,再来看新版

// 转换前
function App() {
  return <h1>Hello World</h1>;
}
//转换后
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
  return _jsx('h1', { children: 'Hello world' });
}

isolatedModules

https://www.typescriptlang.org/tsconfig#isolatedModules

While you can use TypeScript to produce JavaScript code from TypeScript code, it’s also common to use other transpilers such as Babel to do this. However, other transpilers only operate on a single file at a time, which means they can’t apply code transforms that depend on understanding the full type system. This restriction also applies to TypeScript’s ts.transpileModule API which is used by some build tools.

These limitations can cause runtime problems with some TypeScript features like const enums and namespaces. Setting the isolatedModules flag tells TypeScript to warn you if you write certain code that can’t be correctly interpreted by a single-file transpilation process.

skipLibCheck

https://www.typescriptlang.org/tsconfig#skipLibCheck

Skip type checking of declaration files.

This can save time during compilation at the expense of type-system accuracy. For example, two libraries could define two copies of the same type in an inconsistent way. Rather than doing a full check of all d.ts files, TypeScript will type check the code you specifically refer to in your app’s source code.

Another possibility is when you are migrating between TypeScript releases and the changes cause breakages in node_modules and the JS standard libraries which you do not want to deal with during the TypeScript update.

noImplicitAny

https://segmentfault.com/a/1190000019768261

noImplicitAny编译器选项所做的,基本上是将TypeScript从可选类型语言转换为强制类型检验语言。这使得TypeScript离JavaScript的超集稍微远了一些,因为简单的:

function logMe(x) {
  console.log(x);
}
// error TS7006: Parameter 'x' implicitly has an 'any' type.

也将报错——你必须明确声明x的类型为any:

function logMe(x: any) {
  console.log(x);
}
 // OK

这意味着,如果你要把现有的JS代码库迁移到TS,那除了更改文件扩展名,你还得做一些较复杂的东西。这还意味着,在编写代码时,您需要更多地关注类型,如果不指定类型,编译器就总是会「抱怨」。由于在实际情况中显式地声明any被认为是不好的实践,所以在开发过程的早期,您就需要分配正确的类型。如果没有显式的声明,这可能意味着「我太懒了,没有正确地注释这里的类型」。

noImplicitReturns

https://www.typescriptlang.org/tsconfig#noImplicitReturns

When enabled, TypeScript will check all code paths in a function to ensure they return a value.

声明文件(.d.ts)的使用

  1. 在ts文件中对引用的外部库做类型判断;
  2. 制作npm包时,书写自己的声明文件,需要在package.json的typing/types字段注册声明文件的路径;
  3. 不使用ts时,也可以添加声明文件与(自己的)的模块存放在同一目录下,简单做一下数据结构体,对IDE参数声明也有用哦;

.d.ts文件中声明的变量或者模块,在其他文件中不需要使用import导入,可以直接使用。

–lib选项

有时,你想要解耦编译目标(即生成的 JavaScript 版本)和环境库支持之间的关系。例如对于 Promise,你的编译目标是 --target es5,但是你仍然想使用它,这时,你可以使用 lib 对它进行控制。

  • JavaScript 功能
    • es5
    • es6
    • es2015
    • es7
    • es2016
    • es2017
    • esnext
  • 运行环境
    • dom
    • dom.iterable
    • webworker
    • scripthost
  • ESNext 功能选项
    • es2015.core
    • es2015.collection
    • es2015.generator
    • es2015.iterable
    • es2015.promise
    • es2015.proxy
    • es2015.reflect
    • es2015.symbol
    • es2015.symbol.wellknown
    • es2016.array.include
    • es2017.object
    • es2017.sharedmemory
    • esnext.asynciterable

@types,typeRoots和types

默认所有可见的@types“包会在编译过程中被包含进来。 node_modules/@types文件夹下以及它们子文件夹下的所有包都是可见的; 也就是说, ./node_modules/@types/../node_modules/@types/../../node_modules/@types/等等。

如果指定了typeRoots只有typeRoots下面的包才会被包含进来。 比如:

{
   "compilerOptions": {
       "typeRoots" : ["./typings"]
   }
}

这个配置文件会包含所有./typings下面的包,而不包含./node_modules/@types里面的包。

如果指定了types,只有被列出来的包才会被包含进来。 比如:

{
   "compilerOptions": {
        "types" : ["node", "lodash", "express"]
   }
}

这个tsconfig.json文件将仅会包含 ./node_modules/@types/node./node_modules/@types/lodash./node_modules/@types/express

注意:

node_modules/@types/*里面的其它包不会被引入进来

useDefineForClassFields

https://zhuanlan.zhihu.com/p/258906525

useDefineForClassFields 是 TypeScript 3.7.0 中新增的一个编译选项(详见 PR),启用后的作用是将 class 声明中的字段语义从 [[Set]] 变更到 [[Define]]

class C {
  foo = 100;
  bar: string;
}

这是长期以来很常见的一种 TS 字段声明方式,默认情况下它的编译结果如下:

class C {
  constructor() {
    this.foo = 100;
  }
}

当启用了 useDefineForClassFields 编译选项后它的编译结果如下:

class C {
  constructor() {
    Object.defineProperty(this, 'foo', {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 100
    });
    Object.defineProperty(this, 'bar', {
      enumerable: true,
      configurable: true,
      writable: true,
      value: void 0
    });
  }
}
  1. 字段声明的方式从 = 赋值的方式变更成了 Object.defineProperty
  2. 所有的字段声明都会生效,即使它没有指定默认值

experimentalDecorators

https://www.tslang.cn/docs/handbook/decorators.html

启用实验性的装饰器特性

esModuleInterop

esModuleInterop 到底做了什么

改成 true 之后,TS 对于 import 的转译规则会发生一些变化(export 的规则不会变):

 // before
 import React from 'react';
 console.log(React);
 // after 代码经过简化
 var react = __importDefault(require('react'));
 console.log(react['default']);


 // before
 import {Component} from 'react';
 console.log(Component);
 // after 代码经过简化
 var react = require('react');
 console.log(react.Component);
 
 
 // before
 import * as React from 'react';
 console.log(React);
 // after 代码经过简化
 var react = _importStar(require('react'));
 console.log(react);

可以看到,对于默认导入和 namespace(*)导入,TS 使用了两个 helper 函数来帮忙

// 代码经过简化
var __importDefault = function (mod) {
  return mod && mod.__esModule ? mod : { default: mod };
};

var __importStar = function (mod) {
  if (mod && mod.__esModule) {
    return mod;
  }

  var result = {};
  for (var k in mod) {
    if (k !== "default" && mod.hasOwnProperty(k)) {
      result[k] = mod[k]
    }
  }
  result["default"] = mod;

  return result;
};

首先看__importDefault

如果目标模块是 esm,就直接返回目标模块;否则将目标模块挂在一个对象的 defalut 上,返回该对象。

再看 __importStar

  1. 如果目标模块是 esm,就直接返回目标模块。否则
  2. 将目标模块上所有的除了 default 以外的属性挪到 result 上
  3. 将目标模块自己挂到 result.default 上

esm 导入 cjs 的问题:

兼容问题的产生是因为 esm 有 default 这个概念,而 cjs 没有。任何导出的变量在 cjs 看来都是 module.exports 这个对象上的属性,esm 的 default 导出也只是 cjs 上的 module.exports.default 属性而已

目前很多常用的包是基于 cjs / UMD 开发的,而写前端代码一般是写 esm,所以常见的场景是 esm 导入 cjs 的库。但是由于 esm 和 cjs 存在概念上的差异,最大的差异点在于 esm 有 default 的概念而 cjs 没有,所以在 default 上会出问题。

TS babel webpack 都有自己的一套处理机制来处理这个兼容问题,核心思想基本都是通过 default 属性的增添和读取

references

https://www.tslang.cn/docs/handbook/project-references.html

tsconfig.json增加了一个新的顶层属性references。它是一个对象的数组,指明要引用的工程:

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

每个引用的path属性都可以指向到包含tsconfig.json文件的目录,或者直接指向到配置文件本身(名字是任意的)。

  • 导入引用工程中的模块实际加载的是它输出的声明文件(.d.ts)。
  • 如果引用的工程生成一个outFile,那么这个输出文件的.d.ts文件里的声明对于当前工程是可见的。
  • 构建模式(后文)会根据需要自动地构建引用的工程。

emitDecoratorMetadata

emitDecoratorMetadata 是 tsconfig.json 文件中的一个编译选项,用于告诉 TypeScript 编译器在生成 JavaScript 代码时是否在装饰器的元数据中包含有关类型信息。当 emitDecoratorMetadata 设置为 true 时,TypeScript 编译器会在装饰器生成的元数据中添加类型信息。这意味着在运行时,可以使用反射来获取装饰器中被装饰的类、属性或方法的类型信息。

使用 emitDecoratorMetadata 可以在某些情况下非常有用,特别是当你想使用注入容器(如 Angular 中的依赖注入)来解析和创建类实例时。通过使用装饰器的元数据和反射,注入容器可以在运行时推断它需要实例化的类,并正确地解析其依赖关系。

strict

“Strict Mode” TypeScript config options

Before we begin, replace this in the config:

  "strict": true,

with these guys:

  "noImplicitAny": true,
  "noImplicitThis": true,
  "alwaysStrict": true,
  "strictBindCallApply": true,
  "strictNullChecks": true,
  "strictFunctionTypes": true,
  "strictPropertyInitialization": true,

noEmit

不生成输出文件。 (通常用于 ts check)

// vite

"build-types-check": "tsc --project tsconfig.check.json",

resolveJsonModule

tsconfig.json 中的 resolveJsonModule 设置为 true 时,可以在 TypeScript 中直接引入 .json 文件,而无需进行特殊处理。这个选项的作用是让 TypeScript 支持解析 JSON 文件。当这个选项被启用时,TypeScript 就可以像处理 .ts/.tsx 文件一样处理 .json 文件。因此我们可以在 TypeScript 中直接使用 import 导入 .json 文件,而不需要自己手动读取和解析。这个选项通常在处理配置文件或者国际化数据时会用到。

moduleResolution

指定模块解析的策略

  • 如果设置为”node”,TypeScript编译器将使用Node.js的模块解析算法来解析模块路径。这意味着当引入一个模块时,将首先尝试添加”.ts”、”.tsx”、”.d.ts”扩展名,然后尝试添加文件夹中的”index.ts”、”index.tsx”、”index.d.ts”文件进行解析。这通常用于Node.js项目或在使用模块加载器时。
  • 如果设置为”classic”(默认值),TypeScript编译器将使用经典的模块解析算法来解析模块路径。这意味着当引入一个模块时,编译器将首先尝试在相同目录下查找该模块,然后再尝试在父目录中逐级向上查找该模块。这种解析算法适用于非模块加载器的项目。

“bundler”选项是在TypeScript 3.2版本中添加的一个实验性选项。当设置为”bundler”时,TypeScript编译器将通过使用bundler的路径映射来解析模块路径。这种模块解析策略用于支持基于bundler(如Webpack、Rollup)的项目,以便在构建过程中将模块打包到单个文件中。

allowImportingTsExtensions

在 TypeScript 中,allowImportingTsExtensions 是在 TypeScript 4.7 版本中引入的一个编译选项。它的作用是允许在导入 TypeScript 文件时,使用 .ts.tsx.d.ts 文件的扩展名。

作用

在使用 TypeScript 时,通常在导入模块时不需要指定扩展名,例如:

import { foo } from './module';

但是在某些情况下,特别是当你需要兼容某些模块解析策略时,指定扩展名是必要的。例如:

import { foo } from './module.ts';

在默认情况下,TypeScript 不允许导入时使用 .ts.tsx.d.ts 文件的扩展名。启用 allowImportingTsExtensions 选项后,TypeScript 将允许你在导入时使用这些扩展名。

用法

tsconfig.json 文件中启用 allowImportingTsExtensions 选项:

{
  "compilerOptions": {
    "allowImportingTsExtensions": true
  }
}

启用后,你可以在导入 TypeScript 文件时使用扩展名:

import { foo } from './module.ts';
import { bar } from './component.tsx';
import { types } from './types.d.ts';

ts结合react

style

style: React.CSSProperties;

quicktype

https://quicktype.io/

Convert JSON into gorgeous, typesafe code in any language.

补充

高级用法

把对象里的所有值类型展开

type Values<Modules> = {
  [K in keyof Modules]: Modules[K]
}[keyof Modules]

使用:

type Obj = {
  a: 'foo'
  b: 'bar'
}

type T = Values<Obj> // 'foo' | 'bar'

获取类型里面子值的类型

cart: {
   mutations: {
      add() { },
      remove() { }
   }
},

例子:我们获取cart里面mutations的类型

type GetMutations<Module> = Module extends { mutations: infer M } ? M : never

Record & Dictionary & Many

这几个语法糖是从 lodash 的 types 源码中学到的,平时工作中的使用频率还挺高。

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

interface Dictionary<T> {
  [index: string]: T;
};

interface NumericDictionary<T> {
  [index: number]: T;
};

const data:Dictionary<number> = {
  a: 3,
  b: 4
}

object 和 Object

object:在 TypeScript 中,object是指非原始类型(即除了number、string、boolean、symbol、null和undefined之外的类型)的值。它包括对象、数组、函数和其他复杂类型。在类型注解或泛型中,可以使用object来表示任意非原始类型。

ObjectObject是 TypeScript 内置的基本类型之一,它是所有对象类型的父类型。在 JavaScript 中,所有引用类型都是继承自Object的,包括对象、数组、函数等。在类型注解或泛型中,可以使用Object来表示任意对象类型。

在平常的开发过程中,推荐使用object来表示一般的对象类型,而不使用Object

当你想用到object 的时候,可能你需要的是 Record<string, any>

当你想用到object 的时候,可能你需要的是 Record<string, any>

当你想用到object 的时候,可能你需要的是 Record<string, any>

implements与extends

implements 实现,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能。

可以实现多个

extends 继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法。

只能继承一个

数组类型遍历

type Keys<F> = {
  [key in keyof F]: F[key]
}

type aaa = Keys<[1,2,3]>

索引类型是聚合多个元素的类型,所以对象、数组、class 都是。

(准确来说叫元组,元组是元素个数固定的数组)

区分的联合类型

type Shape = {
  kind: 'circle' | 'rect';
  radius?: number;
  width?: number;
  height?: number;
}

因为 kind 与其他字段之间没有建立关系。相反,区分联合是一个更好的解决方案:

type Circle = { kind: 'circle'; radius: number };
type Rect = { kind: 'rect'; width: number; height: number };
type Shape = Circle | Rect;

类型谓词来避免类型断言

如果你正确使用 TypeScript,你应该很少会发现自己使用显式类型断言(例如 value as SomeType

type Circle = { kind: 'circle'; radius: number };
type Rect = { kind: 'rect'; width: number; height: number };
type Shape = Circle | Rect;

function isCircle(shape: Shape) {
  return shape.kind === 'circle';
}

function isRect(shape: Shape) {
  return shape.kind === 'rect';
}

const myShapes: Shape[] = getShapes();
// 错误,因为typescript不知道过滤的方式
const circles: Circle[] = myShapes.filter(isCircle);

// 你可能倾向于添加一个断言
// const circles = myShapes.filter(isCircle) as Circle[];

一个更优雅的解决方案是将isCircleisRect改为返回类型谓词,这样它们可以帮助Typescript在调用 filter 后进一步缩小类型。

function isCircle(shape: Shape): shape is Circle {
    return shape.kind === 'circle';
}

function isRect(shape: Shape): shape is Rect {
    return shape.kind === 'rect';
}

...
// now you get Circle[] type inferred correctly
const circles = myShapes.filter(isCircle);

函数重载

不要把一般的重载放在精确的重载前面:

/* 错误 */
declare function fn(x: any): any;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: any, wat?

应该排序重载令精确的排在一般的之前:

/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)

为什么:TypeScript会选择第一个匹配到的重载当解析函数调用的时候。 当前面的重载比后面的“普通”,那么后面的被隐藏了不会被调用。

另外加上 as const 会推导出 readonly

另外加上 as const 会推导出 readonly

另外加上 as const 会推导出 readonly

限制class实例

interface Crazy {
  new (): {
    hello: number;
  };
}
class CrazyClass implements Crazy {
  constructor() {
    return { hello: 123 };
  }
}

// Because
const crazy = new CrazyClass(); // crazy would be { hello:123 }

尽量使用使用联合类型

不要为仅在某个位置上的参数类型不同的情况下定义重载:

/* WRONG */
interface Moment {
    utcOffset(): number;
    utcOffset(b: number): Moment;
    utcOffset(b: string): Moment;
}

应该尽可能地使用联合类型:

/* OK */
interface Moment {
    utcOffset(): number;
    utcOffset(b: number|string): Moment;
}

注意我们没有让b成为可选的,因为签名的返回值类型不同。

实现 vue3 的Ref 类型

https://juejin.cn/post/6844904126283776014

主要是要支持嵌套:

const count = ref(ref(ref(ref(2))))

定义一定长度数组

https://juejin.cn/post/6844903781633622023

使用元组来定义类型

type Position = [number, number, number]
let p: Position = [1, 2, 3];

假如我们想定义一个长度限制在50的数组呢?难不成要写50遍 number?

Typescript 3.0 中引入一个新的特性叫元组展开

type Tuple50<TItem> = [TItem, ...TItem[]] & { length: 50 };
let iMArray: Tuple50<number> = [0,...,50];

注解

https://github.com/QinZhen001/injector-demo

TS从装饰器到注解到元编程

https://github.com/rbuckton/reflect-metadata

https://www.typescriptlang.org/docs/handbook/decorators.html#metadata

  • 装饰器(Decorator) 仅提供定义劫持,能够对类及其方法、方法入参、属性的定义并没有提供任何附加元数据的功能。
  • 注解(Annotation) 仅提供附加元数据支持,并不能实现任何操作。需要另外的 Scanner 根据元数据执行相应操作。

之所以要给注解添加引号,是因为注解的概念是要进行元数据的修改,而这里仅仅是动态改变原型上的属性。要进行元数据的修改,我们需要利用反射Reflect。 ES6提供的Refelct并不满足修改元数据,我们要额外引入一个库reflect-metadata

import 'reflect-metadata'

@modifyClass('param')
class A {

}

function modifyClass(param) {
  return target => {
      Reflect.defineMetadata(Symbol.for('META_PARAM'), param, target.prototype)
  }
}

这个时候就是真正的注解了,我们通过装饰器和Reflect对要修饰的类注入了元数据,注意我们这里是注入到target.prototype,类的实例上。因为不同的实例是获得的不同的数据,因此不能注入到target上。

Reflect.defineMetadata`方法第一个入参可以是string 类型或者 symbol 类型。建议使用symbol类型,这样避免被覆盖掉。而这里的param可以是任意类型。我们不仅能在类上定义元数据,也可以在类的属性上定义
 `Relect.defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey)

接下来我们就可以在任意的地方通过Reflect.getMetadata(metadataKey, target)获取元数据了。

反射给了我们在类及其属性、方法、入参上存储读取数据的能力

额外的属性检查

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

注意传入createSquare的参数拼写为colour而不是color。 在 JavaScript 里,这会默默地失败。

你可能会争辩这个程序已经正确地类型化了,因为width属性是兼容的,不存在color属性,而且额外的colour属性是无意义的。

然而,TypeScript 会认为这段代码可能存在 bug。 对象字面量会被特殊对待而且会经过_额外属性检查_,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。

// error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
let mySquare = createSquare({ colour: "red", width: 100 });

绕开这些检查非常简单。 最简便的方法是使用类型断言:

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果SquareConfig带有上面定义的类型的colorwidth属性,并且_还会_带有任意数量的其它属性,那么我们可以这样定义它:

interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;
}

我们稍后会讲到索引签名,但在这我们要表示的是SquareConfig可以有任意数量的属性,并且只要它们不是colorwidth,那么就无所谓它们的类型是什么。


还有最后一种跳过这些检查的方式,这可能会让你感到惊讶,它就是将这个对象赋值给一个另一个变量: 因为squareOptions不会经过额外属性检查,所以编译器不会报错。

let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

索引签名

当你声明一个索引签名时,所有明确的成员都必须符合索引签名

interface Foo {
  [key: string]: number;
  x: number;
  y: string; // ERROR 必须是number
}


interface Foo {
  [key: number]: string;
  2: number
}

const foo: Foo = ['1', '2', '3']; // OK

webpack打包ts

webpack打包ts的两种方案对比

TS 编译工具!从 ts-loader 到 Babel

目前大家常用的webpack打包ts主要为两种方案:

  1. ts-loader:将ts转为js,再使用babel将js转为低版本js;
  2. @babel/preset-typescript:它是直接移除TypeScript,转为JS,这使得它的编译速度飞快,并且只需要管理Babel一个编译器就行了。

我们通常采取第二种方案,它比第一种快得多

TS 编译工具!从 ts-loader 到 Babel

ts-loader 在内部是调用了 TypeScript 的官方编译器 – tsc。所以,ts-loadertsc 是共享 tsconfig.json

awesome-typescript-loader

https://blog.csdn.net/iamxuqianqian/article/details/116067093

awesome-typescript-loader 的创建主要是为了加快项目中的编译速度。

ts-loader的主要区别:

  • 更适合与 Babel 集成,使用 Babel 的转义和缓存。
  • 不需要安装独立的插件,就可以把类型检查放在独立进程中。

ts 4.1 新特性:字符串模板类型

https://mp.weixin.qq.com/s/MeGj7bD8m3VvXDMrMXNUHw

Babel 7 处理 ts

https://juejin.cn/post/6954304242093932557

@babel/preset-typescript

  • Babel 7 之前,是不支持 TS 的

    编译流程是这样的:TS > TS 编译器 > JS > Babel > JS (再次)

  • Babel 7

    实现了“只有一个 Javascript 编译器” 的梦想!通过允许 Babel 作为唯一的编译器来工作,就再也没必要利用一些复杂的 Webpack 魔法来管理、配置或者合并两个编译器。

它移除了 TypeScript

是的,它将 TypeScript 全部转换为常规 JavaScript,然后再一如既往的操作。

Babel 为什么在编译过程中剥离 TypeScript

  1. 基于 Babel 的优秀的缓存和单文件散发架构,Babel + TypeScript 的组合套餐会提供了更快的编译。
  2. 而 类型检查 呢? 那只在当你准备好的时候进行。

如果项目中有 Babel,安装 @babel/preset-typescript,配合 tsc 做类型检查。(这个是我们首选的)

动态设置请求返回值类型

export function request<T>(): Promise<T>

// 如 const result = request<{a: string}>(); result.a

class 实例

class A {}

// 这里表示参数是 class A 这个类,如果没有 `typeof` 则参数是 class A 的实例
export const callClassA(classA: typeof A)

export const callClassA(a: A)

有value属性的对象(同时支持泛型)

需要一个有value属性的对象,这个ts应该怎么写?

interface DataSourceObject {
  value:string 
}
export type DataSourceType<T = {}> = T & DataSourceObject

// DataSourceType 一定含有value

is 来判定值的类型

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    let code = 'BAD_REQUEST'
    if (err.isAxiosError) {
      code = `Axios-${err.code}`
    } else if (err instanceof Sequelize.BaseError) {

    }
    ctx.body = {
      code
    }
  }
})

err.code 处,会编译出错,提示 Property 'code' does not exist on type 'Error'.ts(2339)

此时可以使用 as AxiosError 或者 as any 来避免报错,不过强制类型转换也不够友好

if ((err as AxiosError).isAxiosError) {
  code = `Axios-${(err as AxiosError).code}`
}

此时可以使用 is 来判定值的类型

function isAxiosError (error: any): error is AxiosError {
  return error.isAxiosError
}

if (isAxiosError(err)) {
  code = `Axios-${err.code}`
}

lib.d.ts

https://jkchao.github.io/typescript-book-chinese/typings/lib.html#%E4%BD%BF%E7%94%A8%E4%BE%8B%E5%AD%90

当你安装 TypeScript 时,会顺带安装一个 lib.d.ts 声明文件。这个文件包含 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。

  • 它自动包含在 TypeScript 项目的编译上下文中;
  • 它能让你快速开始书写经过类型检查的 JavaScript 代码。

@types

https://jkchao.github.io/typescript-book-chinese/typings/types.html

https://zhuanlan.zhihu.com/p/194196536

tslib

https://github.com/microsoft/tslib

This is a runtime library for TypeScript that contains all of the TypeScript helper functions.

This library is primarily used by the --importHelpers flag in TypeScript.

使用tslib后

https://mizchi.dev/202008081732-effect-by-tslib

target/importHelpers

  • es2019/true: 289B
  • es2019/false: 289B
  • es5/true: 4.7K
  • es5/false: 6.9K

ES2019没有效果。

如果在ES5 / ES2015中使用异步/等待,则会有效。

类型检查

https://www.typescriptlang.org/tsconfig#noEmit

// package.json
  "scripts": {
    "types:check": "tsc --noEmit",
  },

Do not emit compiler output files like JavaScript source code, source-maps or declarations.

不要发出编译器输出文件,如JavaScript源代码、源映射或声明

字典工厂函数

使用 TypeScript 定义业务字典

https://gist.github.com/jsonleex/123396dc33b41dc1c332653213ac03f9

我们可以实现一个工具函数,将一份定义转换成多种格式的字典。

首先考虑入参的格式。显然作为原始数据,入参必须能够包含完整的字典信息,包括键,值,所有扩展字段,甚至列表场景中的展示顺序。

const { KV, VK, LIST, MAP_BY_KEY, MAP_BY_VALUE } = defineConstants([
  {
    key: 'POP',
    value: 1,
    name: '流行音乐',
  },
  {
    key: 'ROCK',
    value: 2,
    name: '摇滚音乐',
  },
  // ...
]);

KV; // { POP: 1, ROCK: 2, ... }
VK; // { 1: 'POP', 2: 'ROCK', ... }
LIST; // [{ key: 'POP', value: 1, name: '流行音乐' }, { key: 'ROCK', value: 2, name: '摇滚音乐' }, ...]
MAP_BY_KEY; // { POP: { key: 'POP', value: 1, name: '流行音乐' }, ROCK: { key: 'ROCK', value: 2, name: '摇滚音乐' }, ... }
MAP_BY_VALUE; // { 1: { key: 'POP', value: 1, name: '流行音乐' }, 2: { key: 'ROCK', value: 2, name: '摇滚音乐' }, ... }

这给开发者在两个层面带来了不便

一是在定义字典时需要对工具函数的使用和实现有一定了解,这样才能正确传入参数和解构返回值。

二是在使用字典时无法获得类型提示,使用字典的开发者需要回来查看定义了哪些字段和值,同时还需要了解工具函数的使用方式。

ts对应的错误码

https://www.tslang.cn/docs/handbook/error.html

PropertyKey 限制类型

在 TypeScript 中,PropertyKey 是一个联合类型,它表示一个对象的键,可以是 stringnumbersymbol。有时候,你可能希望限制对象的键类型,以确保键符合某些特定的类型约束。以下是一些常见的情况,以及如何在 TypeScript 中限制键的类型。

限制对象的键类型为特定类型

假设你有一个对象类型,你希望对象的键类型限制为特定的类型,比如 stringnumber。你可以使用 TypeScript 的映射类型和 keyof 操作符来实现。

示例

假设你希望对象的键类型为 string,可以这样定义:

type StringKeyedObject<T> = {
  [K in string]: T;
};

const obj: StringKeyedObject<number> = {
  key1: 1,
  key2: 2,
  // key3: "string" // 这会导致类型错误
};

console.log(obj.key1); // 1
console.log(obj.key2); // 2

限制对象的键类型为联合类型

假设你希望对象的键类型限制为某些特定的字符串,可以使用字符串字面量类型和映射类型来实现。

示例

type SpecificKeys = "key1" | "key2" | "key3";

type SpecificKeyedObject<T> = {
  [K in SpecificKeys]: T;
};

const obj: SpecificKeyedObject<number> = {
  key1: 1,
  key2: 2,
  key3: 3,
  // key4: 4 // 这会导致类型错误
};

console.log(obj.key1); // 1
console.log(obj.key2); // 2
console.log(obj.key3); // 3

在 TypeScript 中,可以使用映射类型和 keyof 操作符来限制对象的键类型。你可以限制键类型为特定的 stringnumbersymbol,或者结合 PropertyKey 来实现更灵活的键类型限制。希望这些示例能够帮助你在 TypeScript 中更好地限制对象的键类型。

遇到的问题

常见的 error

Don’t use Function type

https://www.totaltypescript.com/dont-use-function-keyword-in-typescript

Sometimes, you’ll want to express ‘any function’ in TypeScript.

(...args: any) => any

object keys expression string

typescript-element-implicitly-has-any-type-expression

const str = 'name' as string;

const obj = {
  name: 'Bobby Hadz',
  country: 'Chile',
};

// ⛔️ Error: Element implicitly has an 'any' type
// because expression of type 'string' can't be used
// to index type '{ name: string; }'.
// No index signature with a parameter of type 'string'
// was found on type '{ name: string; }'.ts(7053)
obj[str];

Object.keys(obj).map((key) => {
  console.log(obj[key]); 
})

// 这里 str 和 key 被推断成了 string 而不是 obj 中的 key 导致了问题

解决:

obj[str as keyof typeof obj]

Object.keys(obj).map((key) => {
  console.log(obj[key as keyof typeof obj]); 
})

public static and private static

public static and private static variables

  • A public variable is accessible from anywhere (well, anywhere where the class is accessible).

  • A private variable is only accessible inside the class.

  • A static variable belongs to the class rather than to an instance of a class.

设置函数身上的属性

例子:

type Plugin = {
  (aaa: any): void
  init: () => void
}

使用:

const test: Plugin = () => {
  console.log('test')
}
test.init = () => {
  console.log('test init')
}

在react中使用ts的问题

React children type

https://stackoverflow.com/questions/53688899/typescript-and-react-children-type

我们要怎么样才能在react中判断children类型?

This is what worked for me:

interface Props {
  children: React.ReactNode
}

Edit I would recommend using children: React.ReactNode instead now.


You can use ReactChildren and ReactChild:

import React, { ReactChildren, ReactChild } from 'react';

interface AuxProps {
  children: ReactChild | ReactChildren;
}

const Aux = ({ children }: AuxProps) => (<div>{children}</div>);

export default Aux;

推荐一点做法:

使用as重新赋值:

  const childrenComponent = React.Children.map(children, (child, i) => {
      const childElement = child as FunctionComponentElement<MenuItemProps>
	  ......	
    })

as HTMLElement

ReactDOM.render(
  <Hello name="TypeScript" enthusiasmLevel={10} />,
  document.getElementById('root') as HTMLElement
);

这里还有一点要指出,就是最后一行document.getElementById('root') as HTMLElement。 这个语法叫做类型断言,有时也叫做转换。 当你比类型检查器更清楚一个表达式的类型的时候,你可以通过这种方式通知TypeScript。

这里,我们之所以这么做是因为getElementById的返回值类型是HTMLElement | null。 简单地说,getElementById返回null是当无法找对对应id元素的时候。 我们假设getElementById总是成功的,因此我们要使用as语法告诉TypeScript这点。

TypeScript还有一种感叹号(!)结尾的语法,它会从前面的表达式里移除nullundefined。 所以我们也可以写成document.getElementById('root')!,但在这里我们想写的更清楚些。

Event类型使用错误

<li  onMouseEnter: (e: MouseEvent) => {
          .....
      }>
     	......
</li>

出现报错

(property) JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>

查找原因:

react使用的事件是合成事件,而上面的MouseEvent使用的是global.d.ts中的MouseEvent(ES6定义的),并没有使用react中的MouseEvent

解决:

import React, {
  MouseEvent
} from "react";


<li  onMouseEnter: (e: MouseEvent) => {
          .....
      }>
     	......
</li>

e.target.value无法推断

https://stackoverflow.com/questions/59222530/property-value-does-not-exist-on-type-eventtarget-htmlinputelement-ts2339

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    setUserName(value)
  }

出现报错

Property 'value' does not exist on type 'EventTarget & HTMLInputElement'.ts(2339)

解决:

https://stackoverflow.com/questions/44321326/property-value-does-not-exist-on-type-eventtarget-in-typescript

改成这样

(<HTMLInputElement>event.target).value

Here’s another fix that works for me:

(event.target as HTMLInputElement).value

ts(2339)

error TS2339: Property ‘applyParams’ does not exist on type ‘Function’.

Function.prototype.applyParams = (params: any) => {
     this.apply(this, params);
}

解决:

Define the method on an interface named Function in a .d.ts file. This will cause it to declaration merge with the global Function type:

interface Function {
    applyParams(params: any): void;
}

constructor function

https://www.typescriptlang.org/docs/handbook/classes.html#constructor-functions

在ts中我们如何用function模拟一个class?

let Greeter = (function () {
  function Greeter(message) {
    this.greeting = message;
  }

  Greeter.prototype.greet = function () {
    return "Hello, " + this.greeting;
  };

  return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"

ts(7009)

‘new’ expression implicitly has an ‘any’ type.

https://stackoverflow.com/questions/43623461/new-expression-whose-target-lacks-a-construct-signature-in-typescript

const TestVectorLayer = function(layerName: string) {
};

const layer = new TestVectorLayer("");

报错:

'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.ts(7009)

为什么会有这个报错?

我们先看前置知识

noImplicitAny

You can fix this by switching to a class, but in your case this seems a bit more complicated because the inheritance is done by the underlying framework. Because of that, you will have to do something a bit more complicated and it’s not ideal:

interface TestVectorLayer {
  // members of your "class" go here
}

const TestVectorLayer = function (this: TestVectorLayer, layerName: string) {
  // ...
  console.log(layerName);
  ol.layer.Image.call(this, opts);
} as any as { new (layerName: string): TestVectorLayer; };

ol.inherits(TestVectorLayer, ol.layer.Image);

export default TestVectorLayer; 
Then in the file with TestComponent:

const layer = new TestVectorLayer(layerName); // no more compile error



interface TestVectorLayer{
  layerName:string
}

const TestVectorLayer = function(this:TestVectorLayer,layerName: string) {
  this.layerName = layerName
} as any  as { new (layerName: string): TestVectorLayer; };

// 如果不加 as { new (layerName: string): TestVectorLayer; };
// 无法推断出下面的111不合法
const layer = new TestVectorLayer(111) 
 

在class prototype中定义方法

https://stackoverflow.com/questions/26780224/defining-prototype-property-in-typescript

class Test{
  constructor(){}
}

Test.prototype.xxx = function(){
  
}

出现报错:

Property 'xxx' does not exist on type 'Test'.ts(2339)

解决方案:

class A {
    attributeId:string;
}
A.prototype.attributeId = "InternalId";

引申出一个问题:

我们在class中定义的属性是不是都会放进class prototype中?

答案是否定的

举个例子:

class A {
  attributeId:string;
  //  aaa并不会出现在prototype中
  aaa:string
  constructor(obj:any){
    this.aaa = obj.aaa
  }
}
A.prototype.attributeId = "InternalId";

let a = new A({aaa:123})

编译成es5

var A = /** @class */ (function () {
    function A(obj) {
        this.aaa = obj.aaa;
    }
    return A;
}());
A.prototype.attributeId = "InternalId";
var a = new A({ aaa: 123 });

new() 出现问题

https://stackoverflow.com/questions/34698710/defining-typescript-generic-type-with-new

Argument of type ‘typeof Cat’ is not assignable to parameter of type ‘new () => Cat’.

The reason was that I didn’t set default values for the properties in the constructor, hence the parameterless new(): T won’t match.

class Animal {
    // constructor() {} // (This constructor works)
    // constructor(public p: string) {} // (This constructor errors) (p 没有设置默认值)
    constructor(public p = '') {} // (This constructor works)
}
class Cat extends Animal {}

declare function DecorateAnimal<T extends Animal>(original: { new(): T }): { 
    new(): T; 
};

let DecoratedCat = DecorateAnimal(Cat);

存在valuof吗

Is there a valueof similar to keyof in TypeScript?

you can make a ValueOf analogous to keyof, by using lookup types with keyof T as the key, like so:

type ValueOf<T> = T[keyof T];

Usage:

type actions = {
  a: {
    type: 'Reset'
    data: number
  }
  b: {
    type: 'Apply'
    data: string
  }
}
type actionValues = valueof<actions>

ts(1354)

‘readonly’ type modifier is only permitted on array and tuple literal types.ts(1354)

View Problem

https://github.com/microsoft/TypeScript/issues/32234

ts(1385)

https://stackoverflow.com/questions/47748830/typescript-union-type-with-a-function

出现一个报错

Function type notation must be parenthesized when used in a union type.ts(1385)

I’m trying to have a property with a union type of a lambda function or a string.

class TestClass {
    name: string | () => string;
}

解决:

You just need an extra set of parenthesis.

class TestClass {
    name: string | (() => string);
}

The compiler is trying to do (string | ()) => string if you don’t use them, because of precedence.

Is there a type for “Class”

Is there a type for “Class” in Typescript? And does “any” include it?

The equivalent for what you’re asking in typescript is the type { new(): Class }, for example:

class A {}

function create(ctor: { new(): A }): A {
    return new ctor();
}

let a = create(A); // a is instanceof A

The code above will allow only classes whose constructor has no argument. If you want any class, use new (...args: any[]) => Class

Note that { new(): Class } can also be written as new () => Class.

利用 typeof 实现

class A {
  public static attribute = "ABC";
}

// typeof
function f(Param: typeof A) {
  Param.attribute;
   new Param();
}

ts(1205)

Re-exporting a type when the ‘–isolatedModules’ flag is provided requires using ‘export type’

https://github.com/microsoft/TypeScript/issues/28481

export 只需要一次就行了

ts(2345)

https://stackoverflow.com/questions/48488701/type-null-is-not-assignable-to-type-htmlinputelement-reactjs

在react中,我们使用useRef初始化时

const fileInput = useRef<HTMLInputElement>(null)

出现报错

Argument of type 'null' is not assignable to parameter of type 'HTMLInputElement'.ts(2345)

解决:

The error is produced becase the types definitions says input can be null or a HTMLInputElement

You can set "strict": false in your tsconfig.json

如果你不想改变strict这个配置

this is a quirk of how the typings are written:

const fileInput = useRef<HTMLInputElement | null>(null)

ts(1337)

https://blog.csdn.net/weixin_43720095/article/details/106805276

An index signature parameter type cannot be a union type

写接口类型时,希望一个类型的键值是联合类型中固定的几个

const enum MSGTYPE{
    TEXT = 'text',
    IMAGE = 'image',
}
// or
// type MSGTYPE = 'text' | 'image';

export interface QywxSendMessage = {
    msg_id: number;
    msg_name: string;
    [key: MSGTYPE]: number;
}

显然会报错 An index signature parameter type cannot be a union type. Consider using a mapped object type instead. ts(1337)

解决:

export interface QywxSendMessage = {
    msg_id: number;
    msg_name: string;
    [key in MSGTYPE]: number;
}

写接口类型时,希望一个类型的键值是联合类型中固定的几个中的一个

type MSGTYPE = 'text' | 'image';

export interface QywxSendMessage = {
    msg_id: number;
    msg_name: string;
    [key: MSGTYPE]: number;
    // [key: 'text' | 'image']: number;
}

显然也会报错An index signature parameter type cannot be a union type. Consider using a mapped object type instead. ts(1337)

那该如何表示呢?如果用?那显然是有问题的

type MSGTYPE<T> = {'text': T } | {'image':T };

export interface QywxSendMessageParams = {
    msg_id: number;
    msg_name: string;
}

export type QywxSendMessage = QywxSendMessageParams & MSGTYPE<number>;

ts(2451)

Cannot redeclare block-scoped variable

https://www.jianshu.com/p/78268bd9af0a?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

const name = 'youthcity';

function greeter(name:string) {
  return `Hello ${name}`;
}

console.log(greeter(name));

在默认状态下,typescriptDOM typings 作为全局的运行环境,所以当我们声明 name时, 与 DOM 中的全局 window 对象下的 name 属性出现了重名。因此,报了 error TS2451: Cannot redeclare block-scoped variable 'name'. 错误。


方法一:

我们可以在 tsconfig.json 中做一下声明:

{
    "compilerOptions": {
        "lib": [
            "es2015"
        ]
    }
}

方法二:

既然与全局的变量出现重名,那我们将脚本封装到模块(module)内。module 有自己的作用域,自然不会与全局作用域的变量产生冲突。

在 Typescript 中,只要文件存在 import 或 export 关键字,都被视为 module

const name = 'youthcity';

function greeter(name:string) {
  return `Hello ${name}`;
}


console.log(greeter(name));

export {};

ts(1343)

The ‘import.meta’ meta-property is only allowed when the ‘–module’ option is ‘es2020’, ‘esnext’, or ‘system’.

解决:

更改tsconfig中的 module

All files must be modules when the ‘–isolatedModules’ flag is provided

This error is encountered when a file in your project is not considered a module (referring to “modules” in JavaScript, not ModuleScripts). As per the ECMAScript specification, files are considered modules in TypeScript when they contain at least one import or export. If a file is not a module, then it will share its top-most scope with every other file in your project.

当您的项目中的一个文件不被认为是一个模块时(引用JavaScript中的模块,而不是ModuleScripts),就会遇到这个错误。根据ECMAScript规范,当文件包含至少一个导入或导出时,文件被认为是TypeScript中的模块。如果文件不是模块,那么它将与项目中的所有其他文件共享其最顶层的作用域。

Therefore, the solution here is to ensure that every file in your project has at least one import or export statement. If you don’t need either, then you can provide an empty export statement:

因此,这里的解决方案是确保项目中的每个文件至少有一个导入或导出语句。如果您不需要这两个语句,那么您可以提供一个空的export语句

export {}

Unfortunately, there is no other workaround for this problem. Please do not remove the isolatedModules flag from your tsconfig file, as this does not fix the problem, it only suppresses the error.

不幸的是,对于这个问题没有其他的解决方法。请不要从tsconfig文件中删除isolatedModules标志,因为这并不能解决问题,它只会抑制错误。

tsconfig中isolatedModules选项默认为true,这个选项无法更改!!!

How to extend two classes?

How to extend two classes?

abstract class B {
  aaa:()=>{}
}

abstract class C {
  bbb:()=>{}
}

class A extends B,C{

}

出现报错:

Classes can only extend a single class.ts(1174)

解决方案:

TypeScript Mixins Part One

https://www.stevefenton.co.uk/2014/02/typescript-mixins-part-one/

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

class Flies {
        fly() {
                alert('Is it a bird? Is it a plane?');
        }
}

class Climbs {
        climb() {
                alert('My spider-sense is tingling.');
        }
}

class Bulletproof {
        deflect() {
                alert('My wings are a shield of steel.');
        }
}

class BeetleGuy implements Climbs, Bulletproof {
        climb: () => void;
        deflect: () => void;
}
applyMixins (BeetleGuy, [Climbs, Bulletproof]);

class HorseflyWoman implements Climbs, Flies {
        climb: () => void;
        fly: () => void;
}
applyMixins (HorseflyWoman, [Climbs, Flies]);

var superHero = new HorseflyWoman();
superHero.climb();
superHero.fly();

ts(2307)

Cannot find module or its corresponding type declarations.ts

这个是因为找不到module导致的

我们可以在tsconfig设置paths别名

{
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "lib",
    "target": "es6",
    "experimentalDecorators": true,
    "module": "esnext",
    "paths": {
      "agora-rte-sdk": ["../agora-rte-sdk/src"],
      "agora-meeting-core": ["../agora-meeting-core/src"]
    },
  },
}

ts(2339)

Property ‘flexProps’ does not exist on type ‘Object’.

例子:

export type UserInfo = {
  // 用户自定义属性
  userProperties:Object
};
const user:UserInfo

// user.userProperties.flexProps 就会出现错误  

改为

export type UserInfo = {
  // 用户自定义属性
  userProperties:{
    [key: string]: any;
  };
};


constructor does not exist on type

当我们不使用protected时:

class Test{
  constructor(aaa:number){
    // Property 'aaa' does not exist on type 'Test'.ts(2339)   有ts报错
    this.aaa = aaa;
  }
}

解决:

class Test{
  constructor(protected aaa:number){
    this.aaa = aaa;
  }
}

Constructor must be set public, protected, or private

ts(2779)

https://blog.csdn.net/Laladoge/article/details/117085821

https://stackoverflow.com/questions/66020801/angular-error-the-left-hand-side-of-an-assignment-expression-may-not-be-an-opt

The left-hand side of an assignment expression may not be an optional property

解决的办法是 赋值之前先判断是否存在

ts(2250)

Property ‘includes’ does not exist on type ‘any[]’. Do you need to change your target library? Try changing the ‘lib’ compiler option to ‘es2016’ or later.

solve:

https://stackoverflow.com/questions/51811239/ts2339-property-includes-does-not-exist-on-type-string

You should add es2016 or es7 lib complierOptions in tsconfig.json. Default TypeScript doesn’t support some es6 polyfill functions

{
  "compilerOptions": {
    ...
    "lib": [
       "dom",
       "es7"
    ]
  }
}

ts(5053)

https://stackoverflow.com/questions/49403410/why-declaration-can-not-be-used-together-with-isolatedmodules-in-typescript

https://www.coder.work/article/1309192

error TS5053: Option ‘outFile’ cannot be specified with option ‘isolatedModules’.

The reason is for the same reason you can’t use const enums in isolated modules: type information. Since isolated modules compiles each file individually without the types of the files it depends on, any inferred type we would write to a declaration file would potentially be incorrect, as their calculation would be missing information from the rest of the compilation. There is a limited subset of declaration emit where no types are inferred in the output which could be supported, however.

In other words, isolatedModules does not provide enough type information for the creation of complete and accurate *.d.ts declaration files.

换句话说,soldatedModules不提供足够的类型信息,以创建完整和准确的* .ts声明文件。

建议:

搞两个tsconfig

The issue comments also have a suggested workaround, in which we have one tsconfig for compiling with isolated modules, and a second tsconfig for creating declaration files.

ts(7061)

https://bobbyhadz.com/blog/typescript-mapped-type-may-not-declare-properties-or-methods

A mapped type may not declare properties or methods.ts(7061)

type EmailStatuses = 'Read' | 'Unread' | 'Draft'

// tip: 注意下面两个的区别
type StatusFromUnion = {
  [key in EmailStatuses]: string
}

type StatusFromUnion1 = {
  [key in keyof EmailStatuses]: string
}

https://stackoverflow.com/questions/67390147/ts1170-error-with-mapped-type-when-a-new-property-is-added

{[K in XXX]: YYY} does not allow additional properties.

enum A {
  FOO = 0,
  BAR = 1,
  BAZ = 2,
}

// Error
type B = {
  length: number
  [key in A]: number
}

// Success
type B1 = {
  [key in A]: number
} & { length: number }


// Or 
type B = {
    [key in A | "length"]: number;  // 这里可以推断  (不一定都是number)
}

ts(6133)

“is declared but never used”?

声明了引用但是没有使用

"noUnusedParameters": false,
"noUnusedLocals": false,

ts(4094)

exported class expression may not be private or protected

ts(2527)

The inferred type of ‘createEventBase’ references an inaccessible ‘unique symbol’ type. A type annotation is necessary.ts(2527)