Typescript介绍
什么是Typescript
Typescript是 JavaScript 超集,包含类型系统,以及其他一些功能。
随着 Promise、Generators 等 API 进入 JS 标准,TS 和 JS 在这些 API 方面的差别在变小,但 TS 还有一些独有特性,这些特性才是和 JS 的主要区别:
- 类型系统、type-checking;
- 类型(自动)推导、auto-completion;
为什么要用Typescript
- 提升代码健壮性;
- 面向接口编程(代码自解释,并行开发);
- 静态检查可以提高开发效率;
- 减少开发时(人工推导带来的)认知负荷
Typescript资料
- 快速入门
a. TypeScript - Learn X in Y minutes(opens new window):语法速查表 - 社区文档
a. TypeScript for Beginner Programmers(opens new window);
b. TypeScript Generics for People Who Gave Up on Understanding Generics;
c. TypeScript 入门教程; - 官方文档
a. Documentation - TypeScript;
b. Do’s and Don’ts;
c. TypeScript-Handbook; - 在线调试(TS 检查、编译到 JS)
a. TypeScript Playground;
b. Babel - Try it out; - 进阶
a. 深入理解 TS
ⅰ. TypeScript Deep Dive;
ⅱ. 深入理解 TypeScript;
b. Effective TS
ⅰ. Effective Typescript:使用 Typescript 的 n 个技巧;
ⅱ. Effective Typescript - 杨健;
c. 类型推导
ⅰ. TypeScript 类型推导趣事一则;
ⅱ. Typescript Tips: 动态重载实现廉价版 dependent type;
Typescript的基础内容
安装环境
npm install -g typescript // 全局安装 ts
tsc -v
// 输出对应版本,说明安装成功
Version 5.0.4
// 执行 tsc --init
可以看到,生成了tsconfig.json的文件,里面是对应的配置信息
{
"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 legacy experimental 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": "node10", /* 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. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "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. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "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. */
// "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. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "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. */
}
}
创建helloTypescript.ts
const s:string = "hello xianzao";
console.log(s);
使用tsc生成js代码:
tsc helloTypescript.ts
生成最终的JS代码
var s = 'hello xianzao';
console.log(s);
node helloTypescript.js
安装ts-node
通过上述操作,知道了运行tsc命令就可以编译生成一个js文件,但是如果每次改动我们都要手动去执行编译,然后再通过 node命令才能查看运行结果岂不是太麻烦了。
ts-node 正是来解决这个问题的
npm i -g ts-node // 全局安装ts-node
ts-node helloworld.ts // 执行ts代码
基础类型
Boolean 类型
const flag: boolean = true;
Number 类型
const count: number = 10;
String 类型
let name: string = "xianzao";
Enum 类型
枚举类型用于定义数值集合,使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。
- 普通枚举
初始值默认为 0 其余的成员会会按顺序自动增长 可以理解为数组下标
enum Color {
RED,
PINK,
BLUE,
}
const red: Color = Color.RED;
console.log(red); // 0
- 设置初始值
enum Color {
RED = 2,
PINK,
BLUE,
}
const pink: Color = Color.PINK;
console.log(pink); // 3
- 字符串枚举
enum Color {
RED = "红色",
PINK = "粉色",
BLUE = "蓝色",
}
const pink: Color = Color.PINK;
console.log(pink); // 粉色
- 常量枚举
使用 const 关键字修饰的枚举,常量枚举与普通枚举的区别是,整个枚举会在编译阶段被删除 我们可以看下编译之后的效果
const enum Color {
RED,
PINK,
BLUE,
}
const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]
// 编译之后的js如下:
var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
// 可以看到我们的枚举并没有被编译成js代码 只是把color这个数组变量编译出来了
Array 类型
对数组类型的定义有两种方式:
const arr: number[] = [1,2,3];
const arr2: Array<number> = [1,2,3];
元组(tuple)类型
元组( Tuple )表示一个已知数量和类型的数组,可以理解为他是一种特殊的数组
const tuple: [number, string] = [1, "xianzao"];
需要注意的是,元组类型只能表示一个已知元素数量和类型的数组,长度已指定,越界访问会提示错误。例如,一个数组中可能有多种类型,数量和类型都不确定,那就直接any[]。
undefined和null
默认情况下 null 和 undefined 是所有类型的子类型。 也就是说你可以把 null 和 undefined 赋值给其他类型。
let a: undefined = undefined;
let b: null = null;
let str: string = 'xianzao';
str = null; // 编译正确
str = undefined; // 编译正确
但是 undefined 可以给 void 赋值
let c:void = undefined // 编译正确
let d:void = null // 编译错误
any 类型
any会跳过类型检查器对值的检查,任何值都可以赋值给any类型。
let value: any = 1;
value = "xianzao"; // 编译正确
value = []; // 编译正确
value = {};// 编译正确
void 类型
void 意思就是无效的, 一般只用在函数上,告诉别人这个函数没有返回值。
function sayHello(): void {
console.log("hello xianzao");
}
never 类型
never 类型表示的是那些永不存在的值的类型。 例如never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
值会永不存在的两种情况:
- 1 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了)
- 2 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
// 异常
function error(msg: string): never { // 编译正确
throw new Error(msg);
}
// 死循环
function loopForever(): never { // 编译正确
while (true) {};
}
Unknown 类型
unknown与any一样,所有类型都可以分配给unknown:
let value: unknown = 1;
value = "xianzao"; // 编译正确
value = false; // 编译正确
unknown与any的最大区别是:
任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any。
对象类型
object, Object 和 {} 类型
- object 类型用于表示所有的非原始类型,即我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object。
let object: object;
object = 1; // 报错
object = "a"; // 报错
object = true; // 报错
object = null; // 报错
object = undefined; // 报错
object = {}; // 编译正确
- Object
Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)
let bigObject: Object;
object = 1; // 编译正确
object = "a"; // 编译正确
object = true; // 编译正确
object = null; // 报错
ObjectCase = undefined; // 报错
ObjectCase = {}; // ok
- {}
空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
类
在 TypeScript 中,我们通过 Class 关键字来定义一个类
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi(): void {
console.log(`Hi, ${this.name}`);
}
}
数组
const flag1: number[] = [1, 2, 3];
const flag2: Array<number> = [1, 2, 3];
函数
函数声明
function add(x: number, y: number): number {
return x + y;
}
函数表达式
const add = function(x: number, y: number): number {
return x + y;
}
接口定义函数
interface Add {
(x: number, y: number): number;
}
可选参数
function add(x: number, y?: number): number {
return y ? x + y : x;
}
默认参数
function add(x: number, y: number = 0): number {
return x + y;
}
剩余参数
function add(...numbers: number[]): number {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。
let x = 1;
x = true; // 报错
上面的代码等价于
let x: number = 1;
x = true; // 报错
通过上述示例我们可以看出,我们没有给 x 指定明确类型的时候,typescript 会推断出 x 的类型是 number。
而如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let x;
x = 1; // 编译正确
x = true; // 编译正确
类型断言
某些情况下,我们可能比typescript更加清楚的知道某个变量的类型,所以我们可能希望手动指定一个值的类型
类型断言有两种方式
- 尖括号写法
let str: any = "xianzao";
let strLength: number = (<string>str).length;
- as 写法
let str: any = "xianzaoe";
let strLength: number = (str as string).length;
非空断言
在上下文中当类型检查器无法断定类型时,可以使用缀表达式操作符 ! 进行断言操作对象是非 null 和非 undefined 的类型,即x!的值不会为 null 或 undefined
let user: string | null | undefined;
console.log(user!.toUpperCase()); // 编译正确
console.log(user.toUpperCase()); // 错误
确定赋值断言
我们定义了变量, 没有赋值就使用,则会报错
通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。
let value:number
console.log(value); // Variable 'value' is used before being assigned.
联合类型
联合类型用|分隔,表示取值可以为多种类型中的一种。
let status:string|number
status='xianzao'
status=1
类型别名
类型别名用来给一个类型起个新名字。它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。
type count = number | number[];
function hello(value: count) {}
交叉类型
交叉类型就是跟联合类型相反,用&操作符表示,交叉类型就是两个类型必须存在。
interface IpersonA{
name: string,
age: number
}
interface IpersonB {
name: string,
gender: string
}
let person: IpersonA & IpersonB = {
name: "xianazo",
age: 18,
gender: "male"
};
person 即是 IpersonA 类型,又是 IpersonB 类型
注意:交叉类型取的多个类型的并集,但是如果key相同但是类型不同,则该key为never类型。
interface IpersonA {
name: string
}
interface IpersonB {
name: number
}
function testAndFn(params: IpersonA & IpersonB) {
console.log(params)
}
testAndFn({name: "xianzao"}) // error TS2322: Type 'string' is not assignable to type 'never'.
类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。
换句话说:类型守卫是运行时检查,确保一个值在所要类型的范围内
目前主要有四种的方式来实现类型保护:
- 1、in关键字
interface InObj1 {
a: number,
x: string
}
interface InObj2 {
a: number,
y: string
}
function isIn(arg: InObj1 | InObj2) {
// x 在 arg 打印 x
if ('x' in arg) console.log('x')
// y 在 arg 打印 y
if ('y' in arg) console.log('y')
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});
- 2、typeof 关键字
function isTypeof( val: string | number) {
if (typeof val === "number") return 'number'
if (typeof val === "string") return 'string'
return 'nothing'
}
- 3、instanceof
function creatDate(date: Date | string){
console.log(date)
if(date instanceof Date){
date.getDate()
}else {
return new Date(date)
}
}
- 4、自定义类型保护的类型谓词
function isNumber(num: any): num is number {
return typeof num === 'number';
}
function isString(str: any): str is string{
return typeof str=== 'string';
}
接口
我们使用接口来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
简单理解就是:为我们的代码提供一种约定
我们使用关键字interface来声明接口
interface Person {
name: string;
age: number;
}
let xianzao: Person = {
name: 'xianzao',
age: 18
};
我们定义了一个接口 Person,接着定义了一个变量 xianzao,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。
接口一般首字母大写。(当然挺多人也习惯 I 大写字母开头,用来表示这是一个接口)
设置接口可选|只读
interface Person {
readonly name: string;
age?: number;
}
- 可选属性,我们最常见的使用情况是,不确定这个参数是否会传,或者存在。
- 只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
索引签名
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
interface Person {
name: string;
age?: number;
[prop: string]: any;
}
const p1:Person = { name: "xianzao" };
const p2:Person = { name: "zaoxian", age: 28 };
const p3:Person = { name: "test", sex: 1 }
我们规定 以 string 类型的值来索引,索引到的是一个 any 类型的值
接口与类型别名的区别
实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
- TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型;
- type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
接口和类型别名都可以用来描述对象或函数的类型,只是语法不同
type MyTYpe = {
name: string;
say(): void;
}
interface MyInterface {
name: string;
say(): void;
}
都允许扩展
- interface 用 extends 来实现扩展
interface MyInterface {
name: string;
say(): void;
}
interface MyInterface2 extends MyInterface {
sex: string;
}
let person:MyInterface2 = {
name:'xianzao',
sex:'male',
say(): void {
console.log("hello xianzao");
}
}
- type 使用 & 实现扩展
type MyType = {
name:string;
say(): void;
}
type MyType2 = MyType & {
sex:string;
}
let value: MyType2 = {
name:'xianzao',
sex: 'male',
say(): void {
console.log("hello xianzao");
}
}
不同点
- type可以声明基本数据类型别名/联合类型/元组等,而interface不行;
// 基本类型别名
type UserName = string;
type UserName = string | number;
// 联合类型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
- interface能够合并声明,而type不行;
interface Person {
name: string
}
interface Person {
age: number
}
// 此时Person同时具有name和age属性
泛型
泛型是指在定义函数、接⼝或类的时候,不预先指定具体的类型,⽽在使⽤的时候再指定类型的⼀种特
性。
举个例⼦,⽐如我们现在有个这样的需求,我们要实现⼀个这样的函数,函数的参数可以是任何值,返
回值就是将参数原样返回,并且参数的类型是 string ,函数返回类型就为 string ?
你很容易写下
function getValue(arg:string):string {
return arg;
}
现在需求有变,需要返回⼀个 number 类型的值,你会说,联合类型就完事了
function getValue(arg:string | number):string | number {
return arg;
}
但是这样⼜有⼀个问题,就是如果我们需要返回⼀个 boolean 类型, string 数组甚⾄任意类型
呢,难道有多少个就写多少个联合类型?
是的,直接⽤ any
function getValue(arg:any):any {
return arg;
}
很多时候 any 也确实能够解决不少问题,但是这样也不符合我们的需求了,传⼊和返回都是 any 类型,
传⼊和返回并没有统⼀
基本使⽤
泛型是指在定义函数、接⼝或类的时候,不预先指定具体的类型,⽽在使⽤的时候再指定类型的⼀种特
性
上⾯的需求,我们如果⽤泛型来解决的话:
function getValue<T>(arg:T):T {
return arg;
}
泛型的语法是尖括号 <> ⾥⾯写类型参数,⼀般⽤ T 来表示第⼀个类型变量名称,其实它可以⽤任
何有效名称来代替,⽐如我们⽤ XIANZAO 也是编译正常的
泛型就像⼀个占位符⼀个变量,在使⽤的时候我们可以将定义好的类型像参数⼀样传⼊,原封不动的输
出
使⽤
我们有两种⽅式来使⽤:
a. 定义要使⽤的类型,⽐如:
getValue<string>('xianzao'); // 定义 T 为 string 类型
b. 利⽤ typescript 的类型推断,⽐如
getValue('xianzao') // ⾃动推导类型为 string
多个参数
其实并不是只能定义⼀个类型变量,我们可以引⼊希望定义的任何数量的类型变量。⽐如我们引⼊⼀个
新的类型变量 U
function getValue<T, U>(arg:[T,U]):[T,U] {
return arg;
}
// 使⽤
const str = getValue(['xianzao', 18]);
泛型约束
在函数内部使⽤泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或⽅
法
function getLength<T>(arg:T):T {
console.log(arg.length); // 报错,不能调⽤ length 属性
}
因为泛型 T 不⼀定包含属性 length ,那么我想 getLength 这个函数只允许传⼊包含 length
属性的变量,该怎么做呢
这时,我们可以使⽤ extends 关键字来对泛型进⾏约束
interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(arg:T):T {
console.log(arg.length);
return arg;
}
使⽤:
const str = getLength('xianzao')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })
这⾥可以看出,不管你是 str , arr 还是 obj ,只要具有 length 属性,都可以
泛型接⼝
在定义接⼝的时候指定泛型
interface KeyValue<T,U> {
key: T;
value: U;
}
const person1:KeyValue<string,number> = {
key: 'xianzao',
value: 18
}
const person2:KeyValue<number,string> = {
key: 20,
value: 'zaoxian'
}
泛型类
class Test<T> {
value: T;
add: (x: T, y: T) => T;
}
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
return x + y;
};
泛型类型别名
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
泛型参数的默认类型
我们可以为泛型中的类型参数指定默认类型。当使⽤泛型时没有在代码中直接指定类型参数,从实际值
参数中也⽆法推测出时,这个默认类型就会起作⽤。有点 js ⾥函数默认参数的意思
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
泛型⼯具类型
- typeof
关键词除了做类型保护,还可以从实现推出类型
//先定义变量,再定义类型
let p1 = {
name: "xianzao",
age: 18,
gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
return p.name;
}
getName(p1);
- keyof
可以⽤来获取⼀个对象接⼝中的所有 key 值
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';
function getValueByKey(p: Person, key: PersonKey) {
return p[key];
}
let val = getValueByKey({ name: "xianzao", age: 18, gender: "male" }, "nam
e");
console.log(val); // xianzao
- in
⽤来遍历枚举类型
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
- infer
在条件类型语句中,可以⽤ infer 声明⼀个类型变量并且对它进⾏使⽤
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
infer R 就是声明⼀个变量来承载传⼊函数签名的返回值类型,简单说就是⽤它取到函数返回值的类
型⽅便之后使⽤。
- extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约
束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适⽤于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
当我们传⼊合法的类型的值,即包含 length 属性的值时
loggingIdentity({length: 10, name: 'xianzao'}); // 编译正确
- 索引访问操作符
使⽤ [] 操作符可以进⾏索引访问
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
内置⼯具类型
- Required
将类型的属性变成必选
interface Person {
name?: string,
age?: number,
hobby?: string[]
}
const user: Required<Person> = {
name: "xianzao",
age: 18,
hobby: ["code"]
}
- Partial
与 Required 相反,将所有属性转换为可选属性
interface Person {
name: string,
age: number,
}
const xianzao:Person = {
name:'xianzao'
} // error Property 'age' is missing in type '{ name: string; }' but requi
red in type 'Person'.
从上⾯知道,如果必传⽽我们少穿传了的话,就会报错
我们使⽤ Partial 将其变为可选
type User = Partial<Person>
const xianzao: User={
name:'xianzao'
} // 编译正确
- Exclude
Exclude<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 | nu
mber
- Extract
和 Exclude 相反, Extract<T,U> 从 T 中提取出 U。
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
- Readonly
把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值。
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
let p: Readonly<Person> = {
name: "hello",
age: 10,
gender: "male",
};
p.age = 11; // error Cannot assign to 'age' because it is a read-only pro
perty.
- Record
Record<K extends keyof any, T> 的作⽤是将 K 中所有的属性的值转化为 T 类型。
type Property = 'key1'|'key2'
type Person = Record<Property, string>;
const p: Person = {
key1: "hello",
key2: "xianzao",
};
- Pick
从某个类型中挑出⼀些属性出来
type Person = {
name: string;
age:number;
gender:string
}
type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }
const user:P1={
name:'xianzao',
age:18
}
- Omit
与 Pick 相反, Omit<T,K> 从T中取出除去K的其他所有属性
interface Person {
name: string,
age: number,
gender: string
}
type P1 = Omit<Person, "age" | "gender">
const user:P1 = {
name: 'xianzao'
}
- NonNullable
去除类型中的 null 和 undefined
type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]
- ReturnType
⽤来得到⼀个函数的返回值类型
type Func = (value: string) => string;
const test: ReturnType<Func> = "1";
- Parameters
⽤于获得函数的参数类型所组成的元组类型
type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
- InstanceType
返回构造函数类型T的实例类型
class C {
x = 0;
y = 0;
}
type D = InstanceType<typeof C>; // C
tsconfig.json
tsconfig.json 是 TypeScript 项⽬的配置⽂件。
tsconfig.json 包含 TypeScript 编译的相关配置,通过更改编译配置项,我们可以让 Type
Script 编译出 ES6 、 ES5 、 node 的代码
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript ⽬标版本: 'ES3
' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使⽤模块: 'commonjs', 'am
d', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库⽂件
"allowJs": true, // 允许编译 javascript ⽂件
"checkJs": true, // 报告 javascript ⽂件中的错误
"jsx": "preserve", // 指定 jsx 代码的⽣成: 'preserv
e', 'react-native', or 'react'
"declaration": true, // ⽣成相应的 '.d.ts' ⽂件
"sourceMap": true, // ⽣成相应的 '.map' ⽂件
"outFile": "./", // 将输出⽂件合并为⼀个⽂件
"outDir": "./", // 指定输出⽬录
"rootDir": "./", // ⽤来控制输出⽬录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不⽣成输出⽂件
"importHelpers": true, // 从 tslib 导⼊辅助⼯具函数
"isolatedModules": true, // 将每个⽂件做为单独的模块 (与 't
s.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启⽤所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型
时报错
"strictNullChecks": true, // 启⽤严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时
候,⽣成⼀个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个
⽂件⾥加⼊ 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使⽤的变量时,抛出错误
"noUnusedParameters": true, // 有未使⽤的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数⾥的代码都有返回值
时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthroug
h 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Nod
e.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // ⽤于解析⾮相对模块名称的基⽬录
"paths": {}, // 模块名到基于 baseUrl 的路径映射
的列表
"rootDirs": [], // 根⽂件夹列表,其组合内容表示项⽬
运⾏时的结构内容
"typeRoots": [], // 包含类型声明的⽂件列表
"types": [], // 需要包含的类型声明⽂件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默
认导⼊。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript
⽂件⽽不是源⽂件的位置
"mapRoot": "./", // 指定调试器应该找到映射⽂件⽽不是
⽣成⽂件的位置
"inlineSourceMap": true, // ⽣成单个 soucemaps ⽂件,⽽不是
将 sourcemaps ⽣成不同的⽂件
"inlineSources": true, // 将代码与 sourcemaps ⽣成到⼀个
⽂件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启⽤装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的⽀持
}
}