TypeScript学习指北

引子

越来越发觉基础的重要性,在工作中经常有基础不牢,地动山摇的感觉。所以在学习新技术之前,我决定先夯实自己的基础。比如TypeScript知识,现在我决定系统学习一下TypeScript知识, 主要的学习资源是basarat.gitbook.io, 中文翻译指路👉 编译上下文 | 深入理解 TypeScript~

Why TypeScript

(这一章主要介绍为什么需要TS,涉及到比较多的JS概念,总结就是下面一句话,其余内容先跳过~)

TS的诞生主要是两个目的:①给JS提供类型系统,②为现在的JS引擎引入未来的JS版本语法等

Project

这章将会介绍我们如何在自己的项目中成功引入TS

Compilation Context

在TS中,编辑上下文指的是 一组要被编译的文件以及相关的编辑选项, 最常见的方式是用 tsconfig.json 来定义这个编译上下文

接下来我们学习一下tsconfig.json文件的内容:

最简单的就是在根目录下面的空的JSON文件,这样会指定所有的.ts文件为编译的文件,同时会包含几个默认的编译选项

ts 复制代码
{}

compilerOptions

你可以自定义编译选项使用 compilerOptions, 如下:

ts 复制代码
{
  "compilerOptions": {

    /* Basic Options */                       
    "target": "es5",                       /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",                  /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */
    "lib": [],                             /* Specify library files to be included in the compilation:  */
    "allowJs": true,                       /* Allow JavaScript files to be compiled. */
    "checkJs": true,                       /* Report errors in .js files. */
    "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    "sourceMap": true,                     /* Generates corresponding '.map' file. */
    "outFile": "./",                       /* Concatenate and emit output to single file. */
    "outDir": "./",                        /* Redirect output structure to the directory. */
    "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    "removeComments": true,                /* Do not emit comments to output. */
    "noEmit": true,                        /* Do not emit outputs. */
    "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */        
    "strict": true,                        /* Enable all strict type-checking options. */
    "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    "strictNullChecks": true,              /* Enable strict null checks. */
    "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */                   
    "noUnusedLocals": true,                /* Report errors on unused locals. */
    "noUnusedParameters": true,            /* Report errors on unused parameters. */
    "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */           
    "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    "typeRoots": [],                       /* List of folders to include type definitions from. */
    "types": [],                           /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */

    /* Source Map Options */                  
    "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
    "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */                
    "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    "emitDecoratorMetadata": true          /* Enables experimental support for emitting type metadata for decorators. */
  }
}

Which Files?

使用include和exclude来说明包含哪些文件或文件夹:

ts 复制代码
{
    "include":[
        "./folder"
    ],
    "exclude":[
        "./folder/**/*.spec.ts",
        "./folder/someSubFolder"
    ]
    
    /*OR*/
    "files":[        
        "./some/file.ts"    
     ]
}

默认只匹配特定的文件类型:
.ts, .tsx 是默认匹配的类型
如果在 tsconfig.json 中设置了 allowJs: true,那么 .js, .jsx 也会被包含

Declaration Spaces

在TS中有两个命名空间,一个是变量的命名空间,另外一个是类型的命名空间

Type Declaration Space

类型声明空间意味着你可以使用它们作为类型注释:

ts 复制代码
/*类型声明*/
class Foo {}
interface Bar {}
type Bas = {}

/*使用*/
var foo: Foo;
var bar: Bar;
var bas: Bas;

Note: 尽管有interface Bar , 无法被直接当作变量使用的

ini 复制代码
interface Bar {};
var bar = Bar; // ERROR: "cannot find name 'Bar'"

Variable Declaration Space

变量命名空间意味着你可以直接当作变量使用,有些类型命名空间也可以当作变量命名空间来使用

ts 复制代码
class Foo {};
var someVar = Foo;
var someOtherVar = 123;

同样,我们使用 var 定义的变量只在变量命名空间有效,无法当作类型注释使用:

ts 复制代码
var foo = 123;
var bar: foo; // ERROR: "cannot find name 'foo'"

/*不能找到name,是因为name foo不是被定义在类型命名空间*/

Modules

Global Module

默认情况你编写的TS文件中的代码都是在全局命名空间之下的~

ts 复制代码
var foo = 123;

/*其他文件*/
var bar = foo; // allowed

File Module

也称为外部模块。如果你在 TypeScript 文件的顶层使用了 importexport,那么它会在该文件中创建一个本地作用域。

ini 复制代码
export var foo = 123;

这时在全局就没有foo,如果你希望在另外一个文件中引用foo, 那么必须显式引入它:

ts 复制代码
import { foo } from "./foo";
var bar = foo; // allowed

同时使用import也表明这个文件也是文件模块,不会污染全局命名空间

使用File Module的TypeScript 文件会生成什么样的 JavaScript 代码,是由一个名为 module 的编译器选项决定的。

File Module Details

通过指定 module 选项,可以将TS生成不同的JS代码,但是由于AMD和SystemJS, ESM的各种兼容性问题,我们推荐使用 module: commonjs, 但是在编写代码的时候使用ESM

Summary: Use module:commonjs and use the ES module syntax to import / export / author modules.

Module paths

当你设置了 module: commonjs 时,默认使用 moduleResolution: "Node"

模块主要有两种不同类型。这种区分是由 import 语句中的路径部分决定的:

  • 相对路径模块(Relative path modules)(路径以 . 开头,例如 ./someFile../../someFolder/someFile 等)
  • 其他动态查找模块(Dynamic lookup)(例如 'core-js''typestyle''react',甚至 'react/core' 等 => 采用Node.js 默认的"逐级向上查找"策略

Node.js 默认的"逐级向上查找"策略:

./node_modules/foo

../node_modules/foo

../../node_modules/foo

...

直到文件系统的根目录

这两类模块的主要区别在于:它们在文件系统上的解析方式不同。

现在我们知道这两种模块查找哪些地方了,那么我们要查找什么文件呢?

会按照以下的顺序查找:

  • 如果是文件,直接是:foo.ts
  • 如果是文件夹,查找foo/index.ts
  • 如果是文件夹,且有foo/package.json,且存在 types 指定的文件,就是我们查找的目标文件
  • 同上,不过是 main 字段指定的文件

这个文件指的是 .ts / .d.td / .js 文件

Overturning dynamic lookup just for types

对于动态查找模块,我们可以使用一些方式覆盖默认的查找规则

ts 复制代码
// global.d.ts
declare module 'foo' {
  // Some variable declarations
  export var bar: number; /*sample*/
}

这样就有一个模块叫做foo, 其中声明了bar变量

ts 复制代码
// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// TypeScript assumes (without doing any lookup) that
// foo is {bar:number}

import/require for importing type only

ts 复制代码
import foo = require('foo');

在上面这个表达式中,你引入了foo变量,如果你用类型的方式使用它,那么foo文件就不会包含在编辑之后的代码中,如果你以变量的方式使用它,那么就会包含在编译之后的JS文件中

假设我们的foo文件是这样的:

ts 复制代码
// foo.ts
export let someValue: number;

export function greet(name: string): string {
  return `Hello, ${name}!`;
}

export interface A {
    name: string,
    id: number
}
```ts

下面举几个例子说明问题:

/Example 1/ import foo = require('foo'); generate the JavaScript :

/Example 2/ import foo = require('foo'); var bar: foo; generate the JavaScript : var bar;

/Example 3/ import foo = require('foo'); var bar = foo; generate the JavaScript : var foo = require('foo'); var bar = foo;

php 复制代码
**使用例子:懒加载**

```ts
import foo = require('foo'); 
export function loadFoo() {
    // 这是懒加载 foo,原始的加载仅仅用来做类型注解
    const _foo: typeof foo = require('foo');
    // 现在,你可以使用 `_foo` 替代 `foo` 来作为一个变量使用
 }

使用例子:确保导入

如果仅仅是使用模块的副作用,但是害怕JS编译代码的时候排除,这个时候可以 ensureImport 变量,确保编译的JavaScript依赖与模块:

ts 复制代码
import foo = require('./foo');
import bar = require('./bar');
import bas = require('./bas');
const ensureImport: any = foo || bar || bas;

Dynamic Import Expressions

tsconfig.json 中的 "module" 设置:

  • 如果使用 "module": "ESNext""ES2020": TypeScript 会保留 import() 语法,交给打包器(如 Webpack)处理,因此可以实现 懒加载 / 代码分割。

  • 如果使用 "module": "CommonJS":TypeScript 会尝试编译 import() 为 require() 或根本无法识别这个用法,结果会导致:

    • 打包器无法做代码分割;
    • 有些配置下甚至会直接报错。

TypeScript's Type System

JS Migration Guide

常见的这里就不提了,主要说一些大家可能不知道或不会注意到的~

我们可能会看到内联类型声明,但是我们可能没怎么接触到这个概念,类似下面这种:

ts 复制代码
var name1: {
    first: string;
    second: string;
};
name1 = {
    first: 'John',
    second: 'Doe'
};

name1 = {           // Error : `second` is missing
    first: 'John'
};
name1 = {           // Error : `second` is the wrong type
    first: 'John',
    second: 1337
};

什么时候使用 inline 类型?

  • 简单结构、只用一两次:适合用 inline type。
  • 结构复杂、重复使用:建议用 interface 或 type alias 来提取,更可读也更易维护。

Third Party ****JavaScript

你不能强迫所有第三方库都写 TypeScript,但你可以通过 .d.ts 文件告诉 TypeScript 编译器这些库的接口或变量"确实存在"。

ts 复制代码
// vendor.d.ts
declare var someGlobalLib: any;

// some-lib.d.ts
declare module 'some-lib' {
  export function doSomething(): void;
}

对于流行库,强烈建议使用 DefinitelyTyped 提供的完整类型定义:

css 复制代码
npm install --save-dev @types/jquery

安装完后,你就可以像这样导入,同时拥有完善的类型支持:

ts 复制代码
import * as $ from "jquery";
$('div').fadeOut(); // 会有类型提示和错误检查

同时,由于TypeScript默认只认识 .ts/.js 文件,所以类型于下面这样的下发会报错:

javascript 复制代码
import styles from './styles.css';
/*Cannot find module './styles.css' or its corresponding type declarations.*/

解决方法就是在 .d.ts 文件加上:

ts 复制代码
// global.d.ts
declare module '*.css';
declare module '*.html';

这样就告诉 TypeScript:"你放心,这类文件存在,并且我知道怎么处理它们(即使我现在不关心具体类型)"

@types

@types 是一个第三方提供的 TypeScript 类型声明文件库 ,由社区维护并托管在 DefinitelyTyped 仓库中。

例如,jquery 是用 JavaScript 写的,没有内建类型定义文件。为了让 TypeScript 能正确识别其类型信息,我们可以安装:

ts 复制代码
npm install @types/jquery --save-dev

@types支持全局和模块类型定义

然而,一些@types类型声明文件会默认向全局作用域注入类型和变量(比如@types/node会在全局注入process, global, require等等变量), 这可能会引发冲突或"污染"项目中的全局命名空间

为了控制哪些类型可以注入到全局 作用域 ,TypeScript 提供了 tsconfig.json 中的 compilerOptions.types 配置项。

ts 复制代码
{
  "compilerOptions": {
    "types": ["jquery"]
  }
}
/*
如果不配置 types,那么 TypeScript 会默认加载所有安装的 @types 包里的全局类型。
配置了之后,那么 只有 @types/jquery 中的类型声明会生效,其他比如 @types/node 的全局声明就不会影响到你当前的项目,除非你也把 "node" 加进去。
*/

Ambient Declarations

如果写得是一个以.d.ts 为扩展名的类型声明文件(.d.ts 文件专门用来写类型信息,不会被编译成 JavaScript。),那么其中每个顶层声明都要以declare开头,都是为了告诉TypeScript,这是一个类型声明,不是实际的可执行代码

Interfaces

在TS中,接口是一个很重要的概念,它可以将一组类型组合在一起,按照你预期的方式组织变量结构。我们可以使用内联注释和接口注释来创建一个新的类型,但是接口注释允许我们对于这个类型不断扩展:

ts 复制代码
// Lib a.d.tsinterface 
Point {
  x: number,
  y: number}declare const myPoint: Point

// Lib b.d.tsinterface 
Point {
  z: number
}// Your code
myPoint.z // Allowed!

(接口是开放的,这是TS的一个重要原则,允许接口来模仿JS的可扩展性)

同时,接口的开放性还体现在类可以去实现接口,像下面这种:

ts 复制代码
class myPoint implements Point {
    x: number;
    y: number;
    z: number
}

Enum

基本用法:

ts 复制代码
 /*类型推断*/
enum CardSuit {
    Clubs = '0',
    Diamonds = '1',
    Hearts = '2',
    Spades = '3'
}

let Card = CardSuit.Clubs // 自动推断Crad是CardSuit类型
Card = CardSuit.Diamonds

/*多个Enum结构会自动合并*/
enum Color {
  DarkRed = 5,
  DarkGreen,
  DarkBlue
}

enum Color {
  Red,
  Green,
  Blue
}

let a : Color = Color.Blue

常量枚举:

我们经常看到这两种写法,后者被叫做常量枚举

ts 复制代码
 /*第一种*/
enum Tristate {
  False,
  True,
  Unknown
}
const lie = Tristate.False;
会被编译成👉 let lie = Tristate.False

/*第二种*/
const enum Tristate {
  False,
  True,
  Unknown
}const lie = Tristate.False;
会被编译成👉 let lie = 0

const enum 只能在 TypeScript 编译时使用,它在运行时是不存在的。

const enum 更高效,但只能用于静态、明确的代码场景不能用在需要运行时访问枚举对象的地方。

lib.d.ts

在我们安装TypeScript的时候,会默认安装一个 lib.d.ts 声明文件,这个文件会包含JS运行时以及DOM中各种常见的环境声明

Eg:

ts 复制代码
const foo = 123;
const bar = foo.toString();

当TS推断出foo是一个string,为什么可以知道它身上的toString方法呢?就是因为这个文件上面定义了,我们可以在 tsconfig.json 里面配置这个选项:

ts 复制代码
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "./dist",
    "strictNullChecks": false,
    // "noLib": true
  },
  "include": ["src", "global.d.ts"]
}

当设置了 "noLib": true,可以观察到foo.toString() 会立即报错

同样,我们编写.d.ts 或者 global.d.ts 文件扩展 lib.d.ts 声明文件:

ts 复制代码
interface Window {
  helloWorld(): void
}

这样在window对象身上就有了helloWorld方法

--lib 选项

有的时候,我们希望解耦编译目标和环境库目标之前的关系。比如对于Promise编译目标是 --target es5, 但是我们需要在环境库中使用它,这时我们可以在tsconfig.json 中配置:

ts 复制代码
"compilerOptions": 
{
    "target": "es5",
    "lib": ["dom", "es6"]
}

Functions

函数声明有两种写法,之前不知道:

ts 复制代码
/*函数声明*/
type barr1 = {
  (a: number) : number
}
type barr2 = (a: number) => number

const barr1C:barr1 = (a) => {
  return a
}

const barr2C:barr2 = (a) => {
  return a
}

但是注意的是函数重载只能使用function函数声明,如果想要这么写,那么只能使用下面的方式:

ts 复制代码
interface Overloaded {
  (foo:string): string
  (foo:number):number
}

function stringOrNumber(foo:number):number
function stringOrNumber(foo:string):string
function stringOrNumber(foo: any) : any {
    if (typeof foo === 'number') {
    return foo * foo;
  } else if (typeof foo === 'string') {
    return `hello ${foo}`;
  }
}

const overloaded: Overloaded = stringOrNumber;

const str = overloaded('');  
const num = overloaded(123);  

readonly

全部属性变为只读:

ts 复制代码
type Foo3 = {
    bar : number;
    baz : number
}

type Foo3readonly = Readonly<Foo3>

const foo3: Foo3 = {bar: 123, baz: 456}
const foo3readonly : Foo3readonly = {bar: 1233, baz: 4566}

foo3.bar = 456
foo3readonly.bar = 22312

部分属性变为只读:

ts 复制代码
type Foo3 = {
    bar : number;
    baz : number
}

type Foo3readonly<T, K extends keyof T> =  Readonly<Omit<T, K>> & Pick<T, K>

const foo3: Foo3 = {bar: 123, baz: 456}
const foo3readonly : Foo3readonly<Foo3, 'bar'> = {bar: 1233, baz: 4566}

foo3.bar = 456
foo3readonly.bar = 22312

绝对不可变:

ts 复制代码
interface Foo4 {
    readonly [x: number] : number
}
const foo4: Foo4 = {0:123,1:1212}
console.log(foo4[0]);

如果想以不变的方式使用原生 JavaScript 数组,可以使用 TypeScript 提供的 ReadonlyArray<T> 接口:

ts 复制代码
let foo: ReadonlyArray<number> = [1, 2, 3];console.log(foo[0]); // ok
foo.push(4); // Error: ReadonlyArray 上不存在 `push`,因为他会改变数组
foo = foo.concat(4); // ok, 创建了一个复制

注意:readonly 能确保"我"不能修改属性,但是当你把这个属性交给其他并没有这种保证的使用者(允许出于类型兼容性的原因),他们能改变它。

Type Compatibility

在 TypeScript 中,函数参数类型既允许"变宽",也允许"变窄" ,默认不报错,这种设计叫做"双向协变"。

ts 复制代码
interface Point2D {
  x: number;
  y: number;
}
interface Point3D {
  x: number;
  y: number;
  z: number;
}

let fn2D = (p: Point2D) => {};
let fn3D = (p: Point3D) => {};

// 两个方向都允许(默认):
fn3D = fn2D; // ✅ 合理:接受更宽的类型
fn2D = fn3D; // ✅ 默认也允许:接受更窄的类型(双向)

开启 tsconfig.jsonstrictFunctionTypes: true 后:

  • 只允许"参数变宽" (即逆变)
  • 拒绝"参数变窄"的赋值(更安全)
ts 复制代码
let fn3D: (p: Point3D) => void;
let fn2D: (p: Point2D) => void;

fn3D = fn2D; // ✅ OK
fn2D = fn3D; // ❌ 报错(strictFunctionTypes 下)

Moving Types

TypeScript 类型系统非常强大,它支持其他任何单一语言无法实现的类型流动和类型片段。

比较常用的是使用 typeof 来捕获一个变量的类型:

ts 复制代码
let foo = 123;let bar: typeof foo; // 'bar' 类型与 'foo' 类型相同(在这里是: 'number')

bar = 456; // ok
bar = '789'; // Error: 'string' 不能分配给 'number' 类型

keyof 操作符能让你捕获一个类型的键。例如,你可以使用它来捕获变量的键名称,在通过使用 typeof 来获取类型之后:

ts 复制代码
/*注意:const定义的是字面量类型*/
const colors = {
  red: 'red',
  blue: 'blue'
};
type Colors = keyof typeof colors;

let color: Colors; // color 的类型是 'red' | 'blue'
color = 'red'; // ok
color = 'blue'; // ok
color = 'anythingElse'; // Error

Utility Types

在这章我主要会介绍常见的类型工具,主要参考资料来自 www.typescriptlang.orgtype-fest, 可以到

github.com练习~

首先是TS内置的类型工具大多数看名词就可以知道使用规则:

ts 复制代码
 /*1.Awaited<Type>*/
type promiseType = Promise<string>
const p: promiseType = Promise.resolve('1212')
p.then((res: Awaited<promiseType>) => {
    console.log('res:', res);
})

/*2.Partial<Type>*/
interface Todo {
    title: string;
    desp: string
}
function updateTodo(todo:Todo, fieldsToUpdate: Partial<Todo>) {
    return {...todo, ...fieldsToUpdate}
}
const todo1 = {
    title: 'organize desk',
    desp: 'clear clutter'
}
const todo2 = updateTodo(todo1, {
    desp: 'aaa'
})

/*3.Required<Type>*/
interface Props {
    a?: number;
    b?: string
}
const obj1: Props = {a: 5}
const obj2: Required<Props> = {a : 5, b: '12'}

/*4.Readonly<Type>*/
const todo3 = {
    title: 'organize desk',
    desp: 'clear clutter'
}
const readonlyTodo3 : Readonly<Partial<typeof todo3>> = {
    title: '2'
}
console.log(readonlyTodo3.desp);

/*5.Record<Keys, Type>*/
type CatName = "miffy" | "boris" | "mordred"
interface CatInfo {
    age: number;
    breed: string
}
const cats: Record<CatName, CatInfo> = {
    miffy: { age: 10, breed: "Persian" },
    boris: { age: 5, breed: "Maine Coon" },
    mordred: { age: 16, breed: "British Shorthair" },
}
cats.boris

/*6.Pick<Type, Keys>*/
interface todoPick {
    title: string;
    desc: string;
    completed: boolean
}
type todoPreview = Pick<todoPick, 'completed'|'desc'>
const todopick: todoPreview = {
    completed: false,
    desc: 'sasasa'
}

/*7.Omit<Type, Keys>*/
interface TodoOmit {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
type todoOmit = Partial<Omit<TodoOmit, 'title'>>
const todoOmit1: todoOmit = {
    description: '1212'
}

/*8.Exclude<UnionType, ExcludedMembers>*/ 
type T0 = Exclude<'a'|'b'|'c', 'a'>
type T1 = Exclude<string | (() => void), Function>
type Shape = { kind: "circle"; radius: number } | { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number };
type T3 = Exclude<Shape, {kind: 'square'}>

/*9.Extract<Type, Union>*/
type T11 = Extract<string | (() => void) | number, number>

/*10.NonNullable<Type>*/
type Tnotnull = NonNullable<string | null | undefined>

/*11.Parameters<Type>*/
declare function f1(args: {a: number, b: string}, args2 : number): void;
type Tparams = Parameters<() => string>
type Tparams1 = Parameters<(s: string) => void>
type Tparams2 = Parameters<<T>(args: T) => T>
type Tparams3 = Parameters<typeof f1>
// type Tparams3 = [args: {
//     a: number;
//     b: string;
// }, args2: number]

/*12.ReturnType<Type>*/
type Treturn0 = ReturnType<() => string>;
type Treturn1 = ReturnType<(s: string) => void>;
type Treturn2 = ReturnType<<T>() => T>;
type Treturn3 = ReturnType<<T extends U, U extends number[]>() => T>

/*13.InstanceType<Type>*/
class C {
    x = 0;
    y = 0
}
type instance0 = InstanceType<typeof C>
type instance1 = InstanceType<any>

接下来是type-fest提供的一些在项目中会用到的类型工具:

类型名 作用 示例
Except<T, K> 从 T 中排除某些键 类似 Omit<T, K>,但更安全
Merge<T, U> 合并两个类型(U 优先) {a: number} + {a: string, b: boolean} → {a: string, b: boolean}
SetOptional<T, K> 指定 K 中的键为可选 SetOptional<{a: string, b: number}, 'a'> → {a?: string, b: number}
SetRequired<T, K> 指定 K 中的键为必选 与上相反
Simplify 展开类型嵌套层级 展示更直观的结构,常用于调试复杂泛型
Jsonify 转换为 JSON 兼容结构 移除函数、Symbol 等不可序列化内容
Opaque<Type, Token> 创建不可直接访问的封装类型 用于品牌化 primitive(如 UserID)
ValueOf 取联合值类型 {a: 1, b: 2} → `1
AsyncReturnType 获取 async 函数的返回值类型 () => Promise → number
PackageJson 已定义好结构的 npm package.json 类型 非常适合构建 CLI 工具时使用

Compiler Internals

关于TS的编译原理后面会出文章单独介绍

以上就是TS的基础知识,仅用于本人查漏补缺,看了一遍确实解答了我很多疑惑,后续会单独介绍TS的编译原理~

相关推荐
先做个垃圾出来………8 分钟前
split方法
前端
前端Hardy42 分钟前
HTML&CSS:3D图片切换效果
前端·javascript
spionbo1 小时前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
全宝1 小时前
✏️Canvas实现环形文字
前端·javascript·canvas
lyc2333331 小时前
鸿蒙Core File Kit:极简文件管理指南📁
前端
我这里是好的呀1 小时前
全栈开发个人博客12.嵌套评论设计
前端·全栈
我这里是好的呀1 小时前
全栈开发个人博客13.AI聊天设计
前端·全栈
金金金__1 小时前
Element-Plus:popconfirm与tooltip一起使用不生效?
前端·vue.js·element
lyc2333331 小时前
小L带你看鸿蒙应用升级的数据迁移适配📱
前端