TypeScript学习小结:根底应用

某册子买了两年多了,到最近才开始学习TypeScript,迁延症的重大症状了;不过我还是坚信人做一件事是须要一个契机的。
学完之后整体感触是:TypeScript在JavaScript的根底上提供了一套类型零碎,用以在编码时提供类型提醒,并利用类型推断对代码进行查看以及给出谬误提醒,以躲避一些可能潜在的执行JavaScript代码时会呈现的谬误;能够晋升团队合作效率,节俭沟通老本、代码浏览老本等等。
对tsconfig.json进行配置,咱们能够自定义敞开和开启的查看项以及TS的编译配置,比方是否对空值进行查看、编译的指标JS版本、源代码反对的ES版本等等。

基础知识分为以下几块:

  1. TypeScript的类型零碎
  2. TypeScript内置的类型
  3. TypeScript类型定制

    1. 对象构造的类型:interface和class

      1. 根底应用
      2. 定义可选属性和只读属性
      3. 可索引类型
    2. 函数构造的类型

      1. 根底应用
      2. 可选参数、默认参数、残余参数
      3. 函数重载:用于更精准的类型提醒和推导
      4. 可调用类型
      5. 构造函数的注解
    3. 组合构造的类型
    4. 类型别名:type
  4. 利用类型零碎来放大类型范畴:类型守卫

    1. JS操作符:in、typeof、instanceof
    2. 字面量类型守卫
    3. TS关键字is定制类型守卫
  5. TypeScript对类型的操作

    1. 类型变量(绝对于值变量)

      1. 泛型
      2. 泛型束缚:应用关键字extends
    2. 操作符

      1. 应用TS关键字keyof查问索引失去索引的一个联结类型
      2. 应用[]获取索引对应的类型
      3. 应用TS操作符in的映射操作
      4. 应用TS关键字extends的条件判断:三目运算
      5. 应用TS操作符typeof获取类型(构造)
      6. 应用[keyof T]过滤never类型的属性
      7. 应用TS关键字infer用于标识待推导和应用的类型(用在函数的参数类型和返回值类型)
      8. +-操作符
    3. 工具类型:应用关键字type定义,应用泛型和操作符创立的相似函数性能的类型,通过泛型接管输出的类型,通过一系列操作失去并输入新的类型

前置筹备

1. 学习环境搭建

全局装置TypeScript

$ yarn global add typescriptyarn global v1.22.10[1/4]   Resolving packages...[2/4]   Fetching packages...[3/4]   Linking dependencies...[4/4]   Building fresh packages...warning Your current version of Yarn is out of date. The latest version is "1.22.19", while you're on "1.22.10".info To upgrade, run the following command:$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bashsuccess Installed "[email protected]" with binaries:      - tsc      - tsserver✨  Done in 67.21s.

创立我的项目,初始化配置

## 创立我的项目目录$ mkdir ts_learning && cd ts_learning ## 创立src目录mkdir src && touch src/index.ts## 应用npm初始化npm init## 应用tsc初始化ts配置tsc --init // npx typescript --init
$ tsc --initCreated a new tsconfig.json with:                                                                                                                            TS   target: es2016  module: commonjs  strict: true  esModuleInterop: true  skipLibCheck: true  forceConsistentCasingInFileNames: trueYou can learn more at https://aka.ms/tsconfig

执行tsc --init后,目录下会新增一个tsconfig.json文件,这是TypeScript的配置文件,外面蕴含了官网初始化的一些配置以及正文:

{  "compilerOptions": {    /* Visit https://aka.ms/tsconfig to read more about this file */    /* Projects */    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */    /* Language and Environment */    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */    // "jsx": "preserve",                                /* Specify what JSX code is generated. */    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */    /* Modules */    "module": "commonjs",                                /* Specify what module code is generated. */    // "rootDir": "./",                                  /* Specify the root folder within your source files. */    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */    // "resolveJsonModule": true,                        /* Enable importing .json files. */    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */    /* JavaScript Support */    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */    /* Emit */    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */    // "removeComments": true,                           /* Disable emitting comments. */    // "noEmit": true,                                   /* Disable emitting files from a compilation. */    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */    // "newLine": "crlf",                                /* Set the newline character for emitting files. */    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */    /* Interop Constraints */    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */    /* Type Checking */    "strict": true,                                      /* Enable all strict type-checking options. */    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */    /* Completeness */    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */  }}

做一些自定义的配置:

{  "compilerOptions": {    /* Visit https://aka.ms/tsconfig to read more about this file */    /* Projects */    /* Language and Environment */    "target": "es2016",                                  /* 指定 ECMAScript 指标版本。 */    "lib": ["es6", "dom"],                               /* 指定一组打包的库申明文件,形容指标运行时环境。 */    "experimentalDecorators": true,                      /* 启用对TC39第2阶段草案装璜器的实验性反对。 */    /* Modules */    "module": "commonjs",                                /* 指定生成哪类模块代码。 */    "rootDir": "./src",                                  /* 指定源文件的根目录。 */    "moduleResolution": "node",                          /* 指定TypeScript如何从一个给定的模块标识符中查找一个文件(解析策略)。 */    "typeRoots": ["node_modules/@types"],                /* 指定多个文件夹,这些文件夹的作用相似于 './node_modules/@types'. */    /* JavaScript Support */    /* Emit */    "declaration": true,                                 /* 从我的项目中的TypeScript和JavaScript文件生成.d.ts文件. */    "sourceMap": true,                                   /* 为生成的JavaScript文件创建source map文件. */    "outDir": "./dist",                                  /* 为所有生成的文件指定一个输入文件夹. */    "removeComments": true,                              /* 生成的文件中删除正文. */    /* Interop Constraints */    "allowSyntheticDefaultImports": true,                /* 当一个模块没有默认导出时,容许应用 "import x from y"。 */    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */    /* Type Checking */    "strict": true,                                      /* 启用所有严格的类型查看选项。 */    "noImplicitAny": true,                               /* 对具备隐含 "any"类型的表达式和申明启用错误报告。 */     "alwaysStrict": true,                               /* 确保 "use strict "总是被加到生成的代码中。 */    "noImplicitReturns": true,                           /* 启用对函数中没有明确返回的代码门路的错误报告。 */    /* Completeness */    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */  },  "include": [                                           // 须要编译的ts文件一个*示意文件匹配**示意疏忽文件的深度问题    "./src/**/*.ts"  ],  "exclude": [    "node_modules",    "dist",    "**/*.test.ts"  ]}

而后在package.json中退出script命令:

{  "name": "learn_ts",  "version": "1.0.0",  "description": "",  "main": "src/index.ts",  "scripts": {    "build": "tsc", // 编译    "build:w": "tsc -w", // 监听文件,有变动就编译    "test": "echo \"Error: no test specified\" && exit 1"  },  "author": "",  "license": "ISC"}

当咱们编写代码后,就能够通过yarn build命令进行编译,失去编译后的JavaScript代码。

yarn build

2. 感性认识

假如咱们应用JavaScript定义一个变量a:

const a = 1;

如果改为应用TypeScript来编写,能够有以下两种形式:

// 1.和JavaScript一样定义const a = 1;// 2.定义变量时显式地指定类型,变量中存储的是什么类型的值const a: number = 1;

乍一看,仿佛TS应用显式的形式指定类型的写法繁琐了一些,然而当咱们用到简单的类型,比方援用类型,益处就非常显著,首先在申明和赋值变量时,会提醒咱们类型外部是什么构造、外部的数据是什么类型,躲避一些低级谬误,在赋值有问题时会给出更精确的谬误提醒,这在开发大型项目和多人合作时非常有用;其次在应用变量时,也能够给出更多更精准的提醒,比方一个对象能够蕴含哪些属性,属性的类型,防止一些谬误的操作。

1. TypeScript的类型零碎

TypeScript的类型,与其余语言的类型不同,指的是合乎某种构造的数据分类,查看的是各种约束条件。

假如我定义了一个接口A和一个类B,构造如下:

interface A {  name: string,  age: number}class B {  name: string  age: number    constructor(name: string, age: number) {    this.name = name;    this.age = age;  }}

此时再定义几个变量:

const a: A = {    name: 'lilei',    age: 18};const b: A = new B('hanmeimei', 18);const c: B = new B('lily', 18);

能够看到:

将变量a的类型指定为接口A,咱们在定义时,TS会提醒它的外部须要定义哪些属性;如果它的构造不合乎接口A的定义,就会给出谬误提醒。

将变量b的类型指定为接口A,b的值指向一个B的实例化对象,因为实例化对象的构造和接口A是统一的,所以也能够通过编译。

2. TypeScript内置的类型

原始类型

  • 布尔类型:boolean

    const isLoading: boolean = false;
  • 数字类型:number

    TypeScript中的二进制、十进制、十六进制等数字都能够用number类型示意。

    const decLiteral: number = 6; // 十进制const hexLiteral: number = 0xf00d; // 十六进制const binaryLiteral: number = 0b1010; // 二进制const octalLiteral: number = 0o744; // 八进制
  • 字符串类型:string

    const book: string = 'Hello, TypeScript';
  • 空值:void

    示意没有任何类型。

    当一个函数没有返回值时,通常会见到其返回值类型是void:

    function warnUser() {  alert("This is a warning message.");} // ts推导出的类型:function warnUser(): void

    实际上只有nullundefined能够赋值给void(与strictNullChecks设置无关):

    const aVoid: void = undefined;
  • null和undefined

    在TypeScript中,undefined和null两者各自有本人的类型别离是undefined和null,和void类似,它们自身的类型用途不是很大:

    let x: undefined = undefined;let y: null = null;// strictNullChecks: trueconst u2v: void = undefined; // okconst n2v: void = null; // TS2322: Type 'null' is not assignable to type 'void'.let n2u: undefined = null; // TS2322: Type 'null' is not assignable to type 'undefined'.let u2n: null = undefined; // TS2322: Type 'undefined' is not assignable to type 'null'.let n2num: number = null; // TS2322: Type 'null' is not assignable to type 'number'.let u2num: number = undefined; // TS2322: Type 'undefined' is not assignable to type 'number'.// strictNullChecks: falseconst u2v: void = undefined; // okconst n2v: void = null; // oklet n2u: undefined = null; // oklet u2n: null = undefined; // oklet n2num: number = null; // oklet u2num: number = undefined; // ok

    默认状况下,null和undefined是所有类型的子类型,就是说你能够把null和undefined赋值给number类型的变量。

    但在正式我的项目中个别都开启strictNullChecks检测,即null和undefined只能赋值给any和各自的类型(undefined还能赋值给void),能够躲避十分多的问题。

  • symbol

    注:应用symbol时,必须增加es6的编译辅助库:

    // tsconfig.json{  "lib": ["es6", ...]}

    Symbol是在ES2015之后成为新的原始类型,它通过Symbol函数创立:

    const sym1 = Symbol('key1');const sym2 = Symbol('key2'); // 编译出的.d.ts => declare const sym2: unique symbol;

    Symbol的值是惟一不变的。

    Symbol('key1') === Symbol('key1') // false
  • 大整数类型:bigint

    BigInt类型在TypeScript3.2版本被内置,应用BigInt能够平安地存储和操作大整数,即便这个数曾经超出了JavaScript中Number所能示意的平安整数范畴。

    注:应用BigInt时,必须增加ESNext的编译辅助库

    // tsconfig.json{  "lib": ["ESNext", ...]}

    在JavaScript中采纳双精度浮点数,这导致精度无限,比方Number.MAX_SAFE_INTEGER示意能够平安递增的最大可能整数,即2*53-1

    const max = Number.MAX_SAFE_INTEGER;const max1 = max + 1;const max2 = max + 2;max1 === max2; // true

    呈现上述后果的起因就是超过了精度范畴。BigInt就能够解决此类问题:

    // 此处是JavaScript代码,不是TypeScriptconst max = BigInt(Number.MAX_SAFE_INTEGER);const max1 = max + 1n;const max2 = max + 2n; // TS2737: BigInt literals are not available when targeting lower than ES2020. 须要将tsconfig.json中的target改为ES2020才反对此种写法max1 === max2; // false

    注:咱们须要用BigInt(number)把Number转化为BigInt,同时如果类型是BigInt的字面量,数字前面须要加n

    在TypeScript中,number类型尽管和BigInt都用于示意数字,但实际上两者类型是不同的:

    declare let foo: number;declare let bar: bigint;foo = bar; // TS2322: Type 'bigint' is not assignable to type 'number'.bar = foo; // TS2322: Type 'number' is not assignable to type 'bigint'.

顶级类型

也称为通用类型,所有类型都是它的子类型

  • any
  • unknown

any

为那些在编程阶段还不分明类型的变量指定一个类型,这些值可能来自于动静的内容,比方来自用户输出或第三方代码库。

这些状况下,咱们不心愿类型查看器对这些值进行查看而是间接让它们通过编译阶段的查看。此时能够应用any类型来标记这些变量:

let notSure: any = 4;notSure = 'maybe a string instead';

留神:any类型是多人合作我的项目的大忌,很可能把TypeScript变成AnyScript,除非不得已,不应首先思考应用此类型。

unknown

unknown是TypeScript3.0引入的新类型,是any类型对应的平安类型。

他们的共同点是,能够被赋值任何类型的值。

// anylet notSure: any = 4;notSure = 'maybe a string instead';notSure = Symbol('anytype');notSure = {};notSure = [];notSure = true;// unknownlet notKnown: unknown = 4;notKnown = 'maybe a string instead';notKnown = Symbol('anytype');notKnown = {};notKnown = [];notKnown = true;

两者的次要区别是,unknown类型会更加严格:在对unknown类型的值执行大多数操作之前,必须进行某种模式的查看,而对any类型的值执行操作前,不用做任何查看。

// anylet anyValue: any;anyValue.foo.bar;anyValue();new anyValue();anyValue[0][1];// unknownlet unknownValue: unknown;// unknownValue.foo.bar; // TS2571: Object is of type 'unknown'.// unknownValue(); // TS2571: Object is of type 'unknown'.// new unknownValue(); // TS2571: Object is of type 'unknown'.// unknownValue[0][1]; // TS2571: Object is of type 'unknown'.

能够看到,unknown类型被确定是某个类型之前,它不能被进行任何操作比方实例化、getter、函数调用等等,而any是能够的。这也是unknownany更平安的起因。

any因为过于灵便,导致与JavaScript没有太多区别,很容易产生低级谬误,很多场景下能够抉择unknown作为更好的替代品。

什么状况下能够对unknown执行操作呢?那就是放大其类型范畴之后,比方:

function getValue(value: unknown): string {    if (value instanceof Date) { // 把value的类型范畴放大至Date实例,所以能够调用Date的实例办法        return value.toISOString();    }    return String(value);}

底部类型

  • never

never类型示意的是那些永不存在的值的类型,是任何类型的子类型,能够赋值给任何类型(?);然而,没有类型能够赋值给never类型(除了never类型自身之外)。

const never1: never = testNever();const num2: number = never1;function testNever(): never {    throw 'error';}let neverValue: never = 1; // TS2322: Type 'number' is not assignable to type 'never'.let anyValue: any = 1;neverValue = anyValue; // TS2322: Type 'any' is not assignable to type 'never'.let neverArray: never[] = [];

never类型常见的两种应用场景:

// 1.抛出异样的函数永远不会有返回值function error(message: string): never {    throw new Error(message);}let n3: number = error('123'); // ok// 2.空数组,而且永远是空的const empty: never[] = [];// empty.push(1); // TS2345: Argument of type 'number' is not assignable to parameter of type 'never'.

数组、元组等

数组

数组有两种定义形式:

// 1. 应用泛型const list: Array<number> = [1, 2, 3];// 2. 在类型前面加上`[]`const table: number[] = [1, 2, 3];// list.push('123'); // TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

元组

元组与数组相似,示意一个已知长度和元素类型的数组,各元素的类型不用雷同。

元组数据如果与申明的元素类型不统一、或者长度不统一,就会报错,必须严格跟当时申明的类型和程序保持一致。

let tt: [string, number];// tt = ['hello', 10, false]; // TS2322: Type '[string, number, boolean]' is not assignable to type '[string, number]'. Source has 3 element(s) but target allows only 2.// tt = [10, 'hello']; // TS2322: Type 'number' is not assignable to type 'string'. Type 'string' is not assignable to type 'number'.// tt = ['hello']; // TS2322: Type '[string]' is not assignable to type '[string, number]'. Source has 1 element(s) but target requires 2.

能够把元组看成严格版的数组,如[string, number]能够看成是:

interface Tuple extends Array<string | number> {  0: string;  1: number;  length: 2;}

元组继承于数组,但比数组领有更严格的类型查看。

此外,还存在一个元组越界问题,如TypeScript容许向元组中应用push办法插入新元素,但拜访越界的元素时会报错:

tt = ['hello', 10];tt.push(2);console.log(tt);// console.log(tt[2]); // TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.

非原始类型 oject

  • object

object示意非原始类型,也就是除number、string、boolean、symbol、null和undefined之外的类型。

enum Direction {    Center = 1}let oo: object;oo = Direction;oo = [1];oo = tt;oo = {};

能够看到,一般对象、枚举、数组、元组都是object类型。

枚举类型

假如变量的类型只会是某几个常量之一,此时就能够应用枚举类型。枚举成员的值能够是数字或字符串。

enum Direction {  UP,  DOWN,  LEFT,  RIGHT}// 以上定义相当于enum Direction {  UP = 0,  DOWN = 1,  LEFT = 2,  RIGHT = 3}

因为枚举成员对应的值默认是数字类型,而且默认从0开始顺次累加。

// 编译后的JavaScriptvar Directions;(function (Directions) {    Directions[Directions["Up"] = 0] = "Up";    Directions[Directions["Down"] = 1] = "Down";    Directions[Directions["Left"] = 2] = "Left";    Directions[Directions["Right"] = 3] = "Right";})(Directions || (Directions = {}));

当咱们把某个成员赋值数字后,前面也会依据前一个成员的值进行累加。

enum Directions {    Up = 10,    Down = 10,    Left,    Right}console.log(Directions.Up, Directions.Down, Directions.Left, Directions.Right); // 10 10 11 12

枚举类型成员的值也能够是字符串类型。

enum Directionss {    Up = 'UP',    Down = 'DOWN',    Left = 'LEFT',    Right = 'RIGHT'}console.log(Directionss.Up, Directionss['Right']); // UP RIGHT// 非数字类型的枚举成员,必须给成员赋初始值enum Directionss {    Up = 'UP',    Down = 'DOWN',    Left = 'LEFT',    Right // TS1061: Enum member must have initializer.}

依据下面编译后的JavaScript的代码,能够看出,咱们能够通过枚举值获取枚举成员的名字,即反向映射。

enum Directions {    Up = 10,    Down = 10,    Left,    Right}console.log(Directions[10]); // Down

能够看出枚举类型实质上是一个JavaScript对象,而因为其非凡的结构,导致其领有正反双向同时映射的个性。

并且咱们即便离开申明枚举类型,也会主动合并到一个对象上,比方:

enum Directions {    Up = 10,    Down = 10,    Left,    Right}enum Directions {    Center}// 编译后的JavaScript代码var Directions;(function (Directions) {    Directions[Directions["Up"] = 10] = "Up";    Directions[Directions["Down"] = 10] = "Down";    Directions[Directions["Left"] = 11] = "Left";    Directions[Directions["Right"] = 12] = "Right";})(Directions || (Directions = {}));(function (Directions) {    Directions[Directions["Center"] = 0] = "Center";})(Directions || (Directions = {}));

可见TypeScript中反复定义枚举的代码并不抵触。

3. TypeScript类型定制

定义对象构造的类型:interface和class

当咱们想要定义一个更具体的对象构造的类型时,能够应用interface或class关键字,两者看上去有点类似,但应用上也有不同。

  • class是JavaScript的关键字,是function的一种语法糖,能够通过new操作来创立实例,能够用instanceof操作来判断类型;而interface通常被看作为你的代码或第三方代码定义的一种契约,能够用于与后端数据对接的标准。
  • interface中的函数只有申明,没有具体实现;class中能够有函数具体实现的代码。

根底应用

// interfaceinterface User {    name: string    age: number    gender: boolean    say: (words: string) => string}// classclass Vehicle {    public startRun(): void {        console.log('starting ...');    }}const car = new Vehicle();car.startRun(); // starting ...

定义可选属性和只读属性

  1. 假如对象的某个属性能够不定义,即存在可选的属性,能够通过?来定义这个属性:
interface User {    name: string    age?: number    gender: boolean    say: (words: string) => string}

上述代码示意申明类型为User的数据,能够不定义age属性,比方:

const u1: User = {  name: 'lilei',  gender: true,  say: (words: string) => {    return `message: ${words}`;  }}
  1. 假如对象的某个属性不可写,即存在只读属性,能够通过readonly来定义这个属性:
interface User {    name: string    age?: number    readonly gender: boolean    say: (words: string) => string}u1.name = 'hanmeimei'; // oku1.gender = false; // TS2540: Cannot assign to 'gender' because it is a read-only property.

上述代码中定义gender为只读属性,给对象的非只读属性name从新赋值没有问题,然而给gender从新赋值就会报错,提醒只读属性不能赋值。

可索引类型

假如存在一个对象,所有属性对应的值都是一样的类型,比方是string,就能够用可索引类型示意。

可索引类型具备一个索引签名,它形容了对象索引的类型,还有相应的索引返回值类型。比方:

interface Email {  [name: string]: string}

上述代码表明Email类型的变量,领有0个或多个string类型的索引并且属性值也是string类型。应用:

const email1: Email = {};const email2: Email = { netease: '[email protected]' };const email3: Email = { netease: '[email protected]', qq: '[email protected]' };

在理论利用中,能够用于与接口返回数据的约定,比方除了确定存在的属性,也存在一些不确定的数据。比方:

interface Item {  name: string  id: number  status: number  [key: string]: any}

上述代码约定了返回的数据对象中肯定有nameidstatus属性,也有一些不确定的数据。

函数构造的类型

在JavaScript中函数的类型就是指Function;而在TS中,函数还定义了更具体的构造的类型。

函数的类型具体蕴含了参数个数、各个参数的类型以及返回值类型。

根底应用

// 1. 作为对象类型的属性interface User {    name: string    age: number    gender: boolean    say: (words: string) => string }// 2. 独自定义interface Say { // 应用interface定义    (words: string): string}// 3. 或者定义别名(相当于定义了一个简写)type Say = (words: string) => string// 再应用interface User {    name: string    age: number    gender: boolean    say: Say}

可选参数、默认参数、残余参数

  1. 定义可选参数与定义可选属性一样,都是应用?

    const add: Add = (a:number, b?: number) => a + (b || 0);

    参数bnumberundefined两种可能。

  2. 定义默认参数的形式与JavaScript一样

    const add: Add = (a:number, b:number = 0) => a + b;
  3. 定义残余参数的形式与JavaScript一样

    // 残余参数`rest`是一个由number组成的数组,在本函数中用reduce进行了累加求和。const add2 = (a: number, ...rest: number[]) => rest.reduce((a, b) => a+ b, a);

函数重载:用于更精准的类型提醒和推导

假如定义了一个函数assigned,外表上看能够接管1~4个参数,但理论接管3个参数是有问题的。

function assigned(a: number, b?: number, c?: number, d?: any) {    if (b === undefined && c === undefined && d === undefined) {        b = c = d = a;    } else if (c === undefined && d === undefined) {        c = a;        d = b;    }    return {        top: a,        right: b,        bottom: c,        left: d    };}assigned(1, 2, 3);

但此时TS并不会给出谬误提醒,在编写代码中调用assigned函数时也不会给出精确的提醒,须要传几个参数,这就减少了类型的不平安。

此时函数重载就派上了用场。函数重载就是指用同样的函数名申明别离对应不同的状况。比方:

interface Position {    top: number,    right?: number,    bottom?: number,    left?: number}function assigned(all: number): Position;function assigned(topAndBottom: number, leftAndRight: number): Position;function assigned(top: number, right: number, bottom: number, left: number): Position;function assigned(a: number, b?: number, c?: number, d?: any) {    if (b === undefined && c === undefined && d === undefined) {        b = c = d = a;    } else if (c === undefined && d === undefined) {        c = a;        d = b;    }    return {        top: a,        right: b,        bottom: c,        left: d    };}assigned(1);assigned(1, 2);assigned(1, 2, 3); // TS2575: No overload expects 3 arguments, but overloads do exist that expect either 2 or 4 arguments.assigned(1, 2, 3, 4);

上述代码中别离申明了参数为1、2、4的状况。能够看到,只有传入3个参数的状况下报错了。

能够了解成重载是定义了函数的几种不同的状态,如果不满足下面重载定义的其中一种状态,就会报错!另外函数重载的入参类型和返回值类型须要和函数实现保持一致,否则也会报错!

可调用类型

可调用类型就是后面所说的独自定义一个函数类型

interface Say { // 应用interface定义    (words: string): string}

此时表明Say是一个可调用类型,申明一个变量类型为Say时,阐明这个变量是能够执行调用操作的。如下:

declare const say: Say;say('1');

构造函数的注解

假如咱们要对下面的变量say进行实例化操作,会报错,因为没有对应的构造函数签名。

new say('1'); // TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.

此时能够加上new来示意此接口能够实例化。比方:

interface Say { // 应用interface定义    (words: string): string    new (): string}

这样就能够执行结构调用了。

new say();

组合构造的类型

在理论我的项目中,可能会呈现简单类型的应用,咱们能够在现有类型的根底上,发明出更简单的类型来满足理论场景。

  • &造成穿插类型

    应用&能够造成穿插类型,&在逻辑中就是且的意思,示意两侧的条件必须同时满足。

    比方将两个对象构造的类型穿插,就是将两个类型中雷同的属性合并为一个,再与残余的属性一起造成新的类型,这样就是两侧的定义的构造都满足了。比方:

    interface I1 {  name: string  age: number}interface I2 {  name: string  status: number}type T1 = I1 & I2; const t1: T1 = {    name: 'lilei',    age: 12,    status: 1}; // t1必须同时满足I1和I2的束缚// 如果I2的定义中也定义了age字段,类型为stringinterface I2 {  name: string  age: string  status: number}// 那么I1&I2穿插的后果中,age字段就会变成never,因为age为number & string,一个数据不可能既是string类型又是number类型,就变成了不存在的never类型数据// I1 & I2 失去的后果{  name: string  age: number & string -> never  status: number}
  • |造成联结类型

    应用|能够造成联结类型,|在逻辑中就是或的意思,示意两侧的条件只有满足其一。

    比方将两个类型进行联合操作,造成新的类型,就示意两侧的构造满足其中之一即可。比方:

    type T2 = number | string;const t21: T2 = 1; // okconst t22: T2 = '1'; // okconst t23: T2 = true;  // TS2322: Type 'boolean' is not assignable to type 'T2'.

    内置类型中的枚举类型能够看作是申明了一个联结类型。

    enum Direction {    UP,    DOWN,    LEFT,    RIGHT}//type T3 = Direction.UP | Direction.DOWN | Direction.LEFT | Direction.RIGHT;let enu1: Direction = Direction.UP;let enu2: T3 = Direction.UP;

类型别名:type

应用类型别名相当于给类型指定了一个简写。比方:

type AddFunction = (a: number, b: number) => number;const add: AddFunction = (a: number, b: number) => a + b;// 的写法相当于const add: (a: number, b: number) => number = (a: number, b: number) => a + b;

也就是说关键字type并没有申明一个新的类型,只是给类型设置了一个简写,使代码更简洁可读性更高。

typeinterface在应用有一些相似之处,比方都能够用于对象构造类型的定义,也都能够被implement:

type TUser = {    name: string    age: number}interface IUser {    name: string    age: number}const tUser: TUser = {    name: 'a',    age: 12};const iUser: IUser = {    name: 'b',    age: 13};// 类型别名能够被implementsclass AA implements TUser {    name = 'aa'    age = 12}// interface也能够被implementsclass BB implements IUser {    name = 'bb'    age = 13}

但还有很多不同的用法:

  1. interface只用于定义对象构造的类型,interface申明时能够应用extends关键字来继承其余interface、type和class。比方:

    // interface申明extends类型别名interface IUser2 extends TUser {    // ...}// interface申明extends其余interfaceinterface IUser3 extends IUser {    // ...}// interface申明extends classinterface IUser4 extends AA {    // ...}// interface申明合并接口interface IUser5 extends IUser2, IUser3 {    // ...}
  2. type的申明中不能用extends关键字,但type除了申明对象构造的类型,还能够对类型进行组合操作,比方穿插操作、联合操作,等等,还有前面会提到的与泛型还有其余类型操作符,造成简单的类型。相当于施展相似工具函数的作用,应用范畴更广。

    type Action = {  type: 'create'  payload: {    name: string    age: number  }} | {  type: 'delete',  payload: {    id: number  }}type Direction = "UP" | "DOWN" | "LEFT" | "RIGHT"

4. 利用类型零碎来放大类型范畴:类型守卫

假如某个变量可能是多种类型之一,如果要对它进行某种类型的特定操作,就须要放大类型范畴,来保障类型的安全性,比方某个函数的参数,可能是number类型或string类型,如果要调用string类型的办法,就必须在参数为string的状况下才能够进行操作,达到缩小报错的目标,缩小编码中可能存在的潜在谬误。

能够通过以下几种形式来放大类型范畴:

JS操作符:in、typeof、instanceof

instanceof类型爱护是通过构造函数来细化类型的一种形式:

function getSomething(arg: Date | Date[]) {    // 细化类型为Date    if(arg instanceof Date) {        // arg.pop(); // TS2339: Property 'pop' does not exist on type 'Date'.        console.log(arg.getDate());    }    // 细化类型为Array    if(arg instanceof Array) {        // arg.getDate(); // TS2339: Property 'getDate' does not exist on type 'Date[]'.        console.log(arg.pop());    }}getSomething(new Date()); // 2getSomething([new Date()]); // 2022-11-02T03:37:45.874Z

typeof也是差不多的形式,通过判断数据的JavaScript根本类型来放大范畴:

function getMessage(arg: number | string) {    if (typeof arg === "number") {        // arg.toUpperCase(); // TS2339: Property 'toUpperCase' does not exist on type 'number'.        console.log(arg + 1);    }    if (typeof arg === "string") {        console.log(arg.toUpperCase());    }}getMessage(33); // 34getMessage('excellent'); // EXCELLENT

相似的,x in y示意x属性在y中存在。

function getSomething2(arg: Date | Date[]) {    if ('getDate' in arg) {        // arg.pop(); // TS2339: Property 'pop' does not exist on type 'Date'.        arg.getDate();    }    if ('length' in arg) {        // arg.getDate(); // TS2339: Property 'getDate' does not exist on type 'Date[]'.        arg.pop();    }}

字面量类型守卫

假如咱们定义一个变量v1:

const v1 = "v1"; // const v1: "v1"let v2 = "v2"; // let v2: string

此时咱们没有指定类型,但TS会推导出v1的类型是"v1",尽管看上去有点奇怪,但这就是TS类型零碎中的字面量类型。比方{}[]""123都是字面量,所以字面量都是单例类型,应用const申明一个原始类型就会失去一个字面量类型。比方对象类型就不能失去字面量类型:

const v3 = {    name: '1',    address: {        city: 'hz',        country: 'China'    }}; // const v3: {name: string, address: {city: string, country: string}}

这在联结类型中十分实用,比方两个对象构造的类型造成一个联结类型,如果通过in操作符来判断其中某个属性从而辨别两个类型,存在类型危险,比方前期这个属性变动不存在了,此时就能够增加一个属性,指定为字面量类型,从而能够分辨两个类型,就造成了可辨识联结类型。比方:

type Error1 = {    kind: 'networkError', // 字面量类型    networkStatus: string}type Error2 = {    kind: 'serverError', // 字面量类型    serverStatus: string}function doHandle(error: Error1 | Error2) {    if (error.kind === 'networkError') {        // console.log(error.serverStatus); // TS2339: Property 'serverStatus' does not exist on type 'Error1'.        console.log(error.networkStatus);    }    if (error.kind === 'serverError') {        // console.log(error.networkStatus); // TS2339: Property 'networkStatus' does not exist on type 'Error2'.        console.log(error.serverStatus);    }}doHandle({ kind: 'serverError', serverStatus: 'server error...' });

能够看出TypeScript依据字面量的比拟操作进行了类型推断

TS关键字is定制类型守卫

假如咱们要判断一个数据的类型,除了合乎个性的构造以外,还须要满足一些额定的条件,就能够应用关键字is来定制咱们的类型守卫,比方Student和Person的构造一样,然而Student的范畴更小一些,比如说年龄范畴在12至15岁之间,就能够编写如下代码来进行辨别:

interface Person9 {    name: string,    age?: number,}interface Student9 extends Person9 {}function checkValidStudent(p: Person9): p is Student9 {    return p.age !== undefined && p.age >= 12 && p.age <= 15;}const p1: Person9 = {    name: 'lilei',    age: 18}const p2: Person9 = {    name: 'hanmeimei',    age: 15}function checkPerson(p: Person9) {    if (checkValidStudent(p)) {        console.log(`${p.name} is a Student`);    } else {        console.log(Object.prototype.valueOf.call(p));    }}checkPerson(p1);checkPerson(p2);

上述代码运行的后果是:

{ name: 'lilei', age: 18 }hanmeimei is a Student

if的条件里,p提醒的类型是Person9,在if判断外部,p提醒的类型就变成Student9了。

在else判断里,p提醒的类型是never,不懂为啥?兴许是因为Person9和Student9的构造一样

又比方除了函数类型必须得通过typeof验证,从而保障类型的平安:

function isFunction(arg: any): arg is Function {  return typeof arg === "function";}

尽管函数编译后显示返回值是布尔类型,但在TS的类型零碎中给出的是不同的提醒,间接定义返回类型是布尔类型boolean,就达不到判断类型的目标。

5. TypeScript对类型的操作

类型变量(绝对于值变量)

  • 泛型

    泛型就相当于对类型的一种指代,相似于变量是对值的一种指代;所以泛型就是类型变量。

    当咱们在编码时还不确定某些数据的类型,然而须要应用类型,或者依据不同类型做不同的解决,就能够用到泛型,通常泛型应用T、V、U等大写字母,通过<>来申明。比方:

    // 利用于函数function print<T>(a: T) {    console.log(a);}// 假如曾经明确传入的参数是数组,只是不晓得元素的类型未知,则能够这样定义function getArrayLength<T>(arg: Array<T>): Array<T> {    console.log(arg.length);    return arg;}// 利用于类class Stack<T> {    private arr: T[] = [];    public push(n: T) {        this.arr.push(n);    }    public pop() {        this.arr.pop();    }}// 利用于接口interface Tree<T> {    name: string,    node: T,    child: Tree<T>}interface ReturnItemFn<T> {    (para: T): T}// 利用于类型别名type T5<T> = {    name: string,    node: T,    child: T5<T>}

    在应用时能够显示的指定泛型的类型,TS也能够通过数据理论值的类型进行推导。

    print(1); // print推导出function print<number>(     a: number): voidprint<string>('1');const returnItem: ReturnItemFn<number> = para => para; // 实现一个接口须要明确传入泛型的类型
  • 泛型束缚:应用关键字extends

    尽管编码时不确定操作的数据类型,但通常实际操作的数据会在肯定的范畴内,此时咱们就能够通过extends关键字来限度所指代的类型。比方给下面的print的泛型增加束缚:

    function print<T extends boolean | number>(a: T) {  console.log(a);}// 此时就会报错print<string>('1'); // TS2344: Type 'string' does not satisfy the constraint 'number | boolean'.

    T限度为 string和number造成的联结类型,即a的值的类型必须满足这个束缚。

    乍一看仿佛没什么用途,为什么不间接定义一个类型CC,而后将a指定为CC类型呢?比方:

    type CC = boolean | numberfunction printf(a: CC) {  console.log(a);}

    在这样简略的代码中,的确显示不出用处。

    如果有一个需要,要设计一个函数,接管两个参数,一个参数为对象,另一个参数为对象上的属性,通过这两个参数返回这个属性的值。如果应用以下代码:

    function getObjProperty(obj: object, key: string) {    return obj[key];}

    会失去TypeScript的谬误提醒:

    TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.   No index signature with a parameter of type 'string' was found on type '{}'.元素隐含有一个'any'类型,因为'string'类型的表达式不能被用于索引'{}'类型。   在类型'{}'上没有找到带有'string'类型参数的索引签名。

    因为obj默认状况下是{}key是无奈在下面取到任何值的。但咱们接管的对象是各种各样的,咱们须要一个泛型来示意传入的对象类型,比方T extends object

    function getObjProperty<T extends object>(obj: T, key: string) {    return obj[key];}

    但此时仍不能解决问题,因为第二个参数key是不是存在于obj上是无奈确定的,因而咱们须要对key也进行束缚,把它束缚为只存在于obj属性的类型。这里能够借助获取索引的类型进行实现<U extends keyof T>,用索引类型查问操作符keyof T把传入的对象的属性取出生成一个联结类型,这样泛型U就被束缚在这个联结类型中,这样一来函数就被残缺定义了:

    function getObjProperty<T extends object, U extends keyof T>(obj: T, key: U) {    return obj[key];}

    假如传入以下对象:

    const person = {    id: 1,    name: 'lily'}

    当咱们向getObjProperty传入第一个参数为person时,第二个参数key的类型就被推导束缚为一个联结类型"name" | "id",只可能是这两者之一,因而能取得良好的类型提醒:

    function getObjProperty<{    id: number;    name: string;}, "id" | "name">(         obj: {id: number, name: string},         key: "id" | "name"): string | number

操作符

  • 应用TS关键字keyof查问索引失去索引的一个联结类型

    class Logo {    public src: string = 'https://www.google.com.hk/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'    public alt: string = 'google'    public width: number = 500}type propNames = keyof Logo;

    TS的推导:

    type propNamesAlias for:keyof LogoInitial type:"src" | "alt" | "width"
  • 应用[]获取索引对应的类型

    type propsType = Logo[propNames];

    TS的推导:

    type propsTypeAlias for:Logo[propNames]Initial type:string | number

    如上propNames是一个联结类型,[]操作也是将联结类型中的每个类型去取一遍,再失去一个联结类型。

  • 应用TS操作符in的映射操作

    假如此时有一个User接口,当初有一个需要是把User接口中的成员全副变成可选的,要怎么做呢?

    interface User {    username: string,    id: number,    token: string,    avatar: string,    role: string}

    一个个:前加上?会不会太繁琐,有没有更便捷的办法?

    此时映射操作就派上用场了,语法是[K in Keys]

    • K:属性名变量,顺次绑定到每个属性上,对应每个属性名
    • Keys:字符串字面量形成的联结类型,示意一组属性名。

      此处能够应用keyof操作符,假如传入的类型是泛型T,失去keyof T,即传入类型T的属性名的联结类型,也就是Keys。

    keyof T的属性名称一一映射进去就是[K in keyof T]

    要把所有的属性成员变为可选类型,那么须要用T[K]取出相应的属性的类型,而后从新生成一个可选的新类型{ [K in keyof T]?: T[K] }

    应用类型别名示意:

    type partial<T> = {    [K in keyof T]?: T[K]}// 测试type partialUser = partial<User>;

    能够看到所有属性都变成了可选类型:

    type partialUserAlias for:partial<User>Initial type:{username?: string, id?: number, token?: string, avatar?: string, role?: string}

    应用in操作符就相当于数组的map操作,对一组联结类型进行遍历的映射操作,失去的后果造成新的类型。

  • 应用TS关键字extends的条件判断:三目运算

    在TS的类型中来能够通过extends操作符来实现三目运算,通常在泛型中应用。比方:

    T extends U ? X : Y

    能够这样了解:若T可能赋值给U,那么类型是X,否则是Y

    假如咱们申明一个函数f,它的参数接管一个布尔类型,当布尔类型为true时返回string类型,否则返回number类型,代码如下:

    declare function f1<T extends boolean>(arg: T): T extends true ? string : number;f1(Math.random() < 0.5); // function f1<boolean>(arg: boolean): string | numberf1(true); // function f1<true>(arg: true): stringf1(false); // function f1<false>(arg: false): number

    能够看到当参数的类型不同,会推导出不同的返回值类型。

    当类型零碎给出短缺的条件,就能依据条件推断出类型后果。

    extends前面跟的是一个联结类型,失去的后果也是一个联结类型,相当于联结类型中的每个类型进行了一次判断,最初组合再一起。比方:

    type Diff<T, U> = T extends U ? never : T;type R = Diff<"a"| "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"

    遍历联结类型"a"| "b" | "c" | "d"中每个类型做一次判断,是否能赋值给"a" | "c" | "f",最初失去"b"|"d",因为依据条件,能赋值的类型在三目运算后失去的为never

    条件判断和映射操作还能组合做更简单的操作。

  • 应用[keyof T]取出对象属性的类型,造成联结类型

    可用于过滤never类型的属性。

    假如有一个interface Part,须要将interface中函数类型的属性名称取出来:

    interface Part {  id: number,  name: string,  subparts: Part[],  updatePart(newName: string): void}

    此时咱们能够把interface看作是对象字面量;遍历整个对象,找出value是函数类型的局部取出key即可;TypeScript的类型编程也是相似操作,遍历interface,找出类型为Function的局部取出key即可:

    type FunctionPropertyNames<T> = { [K in keyof T] : T[K] extends Function ? K : never }[keyof T]type R = FunctionPropertyNames<Part>;

    执行步骤:

    1. Part代入泛型T[K in keyof T]相当于遍历整个interface,此时K相当于key,T[K]相当于value
    2. 验证value的类型,如果是Function那么将key作为value,否则为never,这样就失去一个新的interface:

      {  id: never,  name: never,  subparts: never,  updatePart(newName: string): "updatePart"}
    3. 最初再用[keyof T],顺次取出新interface的value,因为idnamesubparts的value为never就不会返回任何类型了,所以只会返回"update"
  • 应用TS操作符typeof获取类型(构造)

    比方:

    const a1: number = 1;type C = typeof a1; // numberconst c: C = '1'; // TS2322: Type 'string' is not assignable to type 'number'.

    typeof a1失去的就是变量a1的类型number。

    假如定义了一个函数和一个对象:

    function addFunction(a: number, b: number) {    return a + b;}type addType = typeof addFunction;const student = {  name: 'xiaoming',  age: 12,  grade: 1}type Student = typeof student;

    addType失去的类型就是(a: number, b: number) => numberStudent失去的类型就是{grade: number, name: string, age: number}

  • 应用TS关键字infer用于标识待推导和应用的类型(用在函数的参数类型和返回值类型)

    通常配合extends三目运算的条件判断来推断类型。

    如果咱们想晓得一个函数的返回值类型,能够应用infer关键字:

    type getReturnType<T> = T extends (...args: any[]) => infer P ? P : never;type addReturnType = getReturnType<typeof addFunction>; // number

    上述代码中定义了一个接管泛型的类型别名getReturnType,当它接管的泛型满足约束条件,即合乎函数构造(...args: any[]) => infer P时,就失去类型P,否则就失去类型never。infer标识了这个待推导类型。

    接着应用typeof addFunction失去addFunction函数的类型,将它传递给getReturnType,最初失去返回值的类型number

    获取函数参数的类型也是一样的。例子:

    type getParamType<T> = T extends (x: infer V) => any ? V : never;type addParamType = getParamType<typeof addFunction>; // never

    addFunction的类型不合乎约束条件,即(a: number, b: number) => number不能赋值给(x: infer V) => any,所以失去的参数类型是never。

    当满足条件时,就失去参数类型。

    type getParamType<T> = T extends (...x: infer V) => any ? V : never;type addParamType = getParamType<typeof addFunction>; // [number, number] 元组类型                                                          type getParamType<T> = T extends (x: infer V, y: infer U) => any ? V : never;type addParamType = getParamType<typeof addFunction>; // number

    函数类型是否满足约束条件,咱们须要通过参数类型和返回类型同时判断,比方上述代码中,比照参数(x: infer V),是否传递给addFunction执行,显然不行,addFunction须要接管两个参数;再比照返回类型,addFunction的返回类型number是any的子类型,所以返回值类型是满足的。综合下来,就是不满足约束条件了。

    总之就是调用满足类型(x: infer V)=>any的函数的中央,是否能替换成addFunction来执行,如果不能,就是不满足。

  • +-操作符

    + -这两个关键字用于映射操作中给属性增加修饰符,比方-?就是将可选属性变为必选,-readonly就代表将只读属性变为非只读。

    TS有一个内置的工具类型Required<T>,作用就是将传入的类型属性变为必选项,咱们也能够本人实现这个性能:

    type RawRequired<T> = { [P in keyof T]-?: T[P] };type Writable<T> = { -readonly [P in keyof T]: T[P] }// 应用如下interface Animal {    type: string,    name?: string,    readonly gender: number}type requiredAnimal = RawRequired<Animal>; // {type: string, name: string, gender: number}type writableAnimal = Writable<Animal>; // {type: string, name?: string, gender: number}const cat1: Animal = { // name为可选属性,没有初始化也没关系    type: 'cat',    gender: 1};// gender是只读属性,赋值会报错cat1.gender = 0; // TS2540: Cannot assign to 'gender' because it is a read-only property.const cat2: requiredAnimal = { // name变成了必选属性,初始化没有设置会报错    type: 'cat',    gender: 1}; // TS2741: Property 'name' is missing in type '{ type: string; gender: number; }' but required in type 'RawRequired<Animal>'.const cat3: writableAnimal = { // gender去除了只读限度,能够再次赋值    type: 'cat',    gender: 1};cat3.gender = 0;

工具类型:联合泛型和操作符

通常应用关键字type定义,应用泛型和操作符创立的相似函数性能的类型,能够通过泛型接管输出的类型,通过一系列操作失去并输入新的类型。

在后面的示例代码中,就利用了很多工具类型。比方:

type RawRequired<T> = { [P in keyof T]-?: T[P] };

能够通过泛型T接管指定的类型,通过操作后,失去新的类型:

type requiredAnimal = RawRequired<Animal>;

requiredAnimal就是失去的新类型{type: string, name: string, gender: number}