TS

快速入门手册

安装

npm install -g typescript

项目初始化

// 项目初始化
npm init -f

// tsconfig 初始化
tsc -init

// 使用 shift + ctrl + B 监视文件 or shift + command + B

tsc -init 是用来初始化 tsconfig.json 文件。

如果一个目录下有 tsconfig.json 文件,那么就意味着这个目录是 ts 项目的根目录。tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。

{
  /* 编译选项:可以被忽略,这时编译器会使用默认值 */
  "compilerOptions": {
    "module": "commonjs" /* 生成代码的模板标准 */,
    "noImplicitAny": true /* 不允许隐式的 any 类型 */,
    "removeComments": true /* 删除注释 */,
    "preserveConstEnums": true /* 保留 const 和 enum 声明 */,
    "sourceMap": true /* 生成相应的 .map 文件 */,
    "outDir": "./dist" /* 指定输出目录 */
  },
  "include": ["src/**/*"] /* 指定编译目录 */,
  "exclude": ["node_modules", "**/*.spec.ts"] /* 指定不编译目录 */
}

compilerOptions 配置说明:

{
  "compilerOptions": {
    /* Basic Options */
    // "incremental": true,                   /* TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度 */
    "target": "es5",                          /* 目标语言的版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* 生成代码的模板标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], /* TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array" */
    // "allowJs": true,                       /* 允许编译器编译 JS,JSX 文件 */
    // "checkJs": true,                       /* 允许在JS文件中报错,通常与allowJS一起使用 */
    // "jsx": "preserve",                     /* 在 .tsx 文件里支持 JSX: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* 生成声明文件,开启后会自动生成声明文件 '.d.ts'. */
    // "declarationMap": true,                /* 为声明文件生成 sourceMap */
    // "sourceMap": true,                     /* 为目标文件生成 sourceMap */
    // "outFile": "./",                       /* 将多个相互依赖的文件生成一个文件,如果以 AMD 作为标准,即开启时应设置 "module": "AMD" */
    // "outDir": "./",                        /* 指定输出目录 */
    // "rootDir": "./",                       /* 指定输出文件目录(用于输出),用于控制输出目录结构 */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* 增量编译文件的存储位置 */
    // "removeComments": true,                /* 删除注释 */
    // "noEmit": true,                        /* 不输出文件,即编译后不会生成任何 js 文件 */
    // "importHelpers": true,                 /* 通过 tslib 引入 helper 函数,文件必须是模块 */
    // "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,                           /* 开启所有严格的类型检查 */
    // "noImplicitAny": true,                 /* 不允许隐式的 any 类型 */
    // "strictNullChecks": true,              /* 不允许把 null、undefined 赋值给其他类型的变量 */
    // "strictFunctionTypes": true,           /* 不允许函数参数双向协变 */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* 类的实例属性必须初始化 */
    // "noImplicitThis": true,                /* 不允许 this 有隐式的 any 类型 */
    // "alwaysStrict": true,                  /* 在代码中注入 'use strict' */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* 检查只声明、未使用的局部变量(只提示不报错) */
    // "noUnusedParameters": true,            /* 检查未使用的函数参数(只提示不报错) */
    // "noImplicitReturns": true,             /* 每个分支都会有返回值 */
    // "noFallthroughCasesInSwitch": true,    /* 防止 switch 语句贯穿(即如果没有 break 语句后面不会执行) */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* 模块解析策略,ts 默认用 node 的解析策略,即相对的方式导入 */
    // "baseUrl": "./",                       /* 解析非相对模块的基地址,默认是当前目录 */
    // "paths": {},                           /* 路径映射,相对于 baseUrl */
    // "rootDirs": [],                        /* 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟 src 和 out 在同一个目录下,不用再去改变路径也不会报错 */
    // "typeRoots": [],                       /* 声明文件目录,默认时 node_modules/@types */
    // "types": [],                           /* 加载的声明文件包 */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* 允许 export = 导出,由import from 导入 */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* 允许在模块中全局变量的方式访问 umd 模块 */

    /* 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. */

    /* Advanced Options */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

以上是 tsconfig.json 文件中的一些常见配置项

基本数据类型

TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。

boolean

let isDone: boolean = false;

number

JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

string

JavaScript 一样,可以使用双引号( ")或单引号(')表示字符串。

let name: string = "bob";
name = "smith";

同样也可以使用 字符串模板

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`;

array

有两种方式定义数组,第一种,在数组元素类型后面使用 []

let list: number[] = [1, 2, 3];

第二种,使用数组泛型,Array<元素类型>

let list: Array<number> = [1, 2, 3];

Tuple

Tuple 类型也是一个数组,我们可以用它来表示一个已知元素数量元素类型的数组。 比如,你可以定义一对值分别为 stringnumber类型的元组。

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'

使用索引进行越界访问:

x[3] = 'world'; // Error, Tuple type '[string, number]' of length '2' has no element at index '2'.

调用数组的方法:

x.push("world"); // OK
x.push(true); // Error, Argument of type 'true' is not assignable to parameter of type 'string | number'.

1、使用索引来访问越界元素,编译器会报错误

2、使用 push 方法新增元素,元素的类型必须满足其联合类型

enum

enum 类型是对 javascript 标准数据类型的一个补充。

enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

默认情况下,枚举成员从 0 开始赋值,每次递增步长为 1,同时,可以从值到名进行反向映射:

// key -> value
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

// value -> key
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

同时,我们也可以对枚举项进行手动赋值,当值为 number 类型时,未赋值的枚举项会接着上一个枚举项依次赋值。

enum Days { Sun = 2, Mon, Tue = 5, Wed, Thu, Fri, Sat };

console.log(Days.Sun);  // 2
console.log(Days.Mon);  // 3
console.log(Days.Tue);  // 5
console.log(Days.Wed);  // 6
console.log(Days.Thu);  // 7

如果枚举项的值有重复的话,typescript 不会提示错误,但是通过 value 获取 key 的话,key 是最后一次的枚举项:

enum Days { Sun = 2, Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };
console.log(Days[2]); // Wed

在使用的时候,最好不要出现覆盖的情况。

手动赋值的枚举项可以不是 number 类型,但是,紧跟着的枚举项必须给初始值,否则会报错。

enum Days { Sun = "s", Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };

any

any 表示可以赋值为任意类型。

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

针对未声明类型的变量,它会被识别为 any

let something;
something = 'seven';
something = 7;

void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void

function bar(): void {}

类型推论

什么是类型推论

以下代码虽然没有指定类型,但是会在编译的时候报错:

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // error TS2322: Type '7' is not assignable to type 'string'.

事实上,它等价于:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

联合类型使用 | 分隔每个类型。

访问联合类型的属性和方法

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

function getLength(something: string | number): number {
  return something.length;
}
// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

上例中,length 不是 stringnumber 的共有属性,所以编译器报错。

访问 stringnumber 的共有属性是没问题的:

function getString(something: string | number): string {
  return something.toString();
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length);
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // error TS2339: Property 'length' does not exist on type 'number'.

在上例中,第 2 行 myFavoriteNumber 被推断成 string 类型,因此访问其 length 属性不会报错。而第 4 行被推断成 number,访问 length 就报错了。

类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

语法

<type> value 

// or

value as type

tsx 中必须使用后面一种。

前面在联合类型中我们提到过,当 Typescript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

function getLength(something: string | number): number {
  return something.length;
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

g:

function getLength(something: string | number): number {
  if (something.length) {
    return something.length;
  } else {
    return something.toString().length;
  }
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

在上例中,访问 something.length 的时候会报错,因为 length 并不是公共属性。此时,我们就可以使用类型断言,将 something 断言成 string

function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (something as string).length;
  } else {
    return something.toString().length;
  }
}

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的

function toBoolean(something: string | number): boolean {
  return <boolean>something;
}

// error TS2352: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'number' is not comparable to type 'boolean'.

类型别名

类型别名用来给一个类型起个新名字,常用于联合类型。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
} 

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element | null , event: EventNames) {
  // do something
}

handleEvent(document.querySelector('hello'), 'scroll');
handleEvent(document.querySelector('world'), 'dbclick'); // error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

上例中,我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。

类型别名与字符串字面量类型都是使用 type 进行定义。

函数

声明式函数

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:

function sum(x: number, y: number): number {
  return x + y;
}

输入多余的(或者少于要求的)参数,都是不被允许的。

sum(1, 2, 3); // error TS2554: Expected 2 arguments, but got 3.
sum(1); //Expected 2 arguments, but got 1.

函数表达式

如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:

const sum = (x: number, y: number): number => x + y;

这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行类型定义,而等号左边的 sum,是通过赋值操作进行 类型推论 推断出来的。如果我们需要手动给 sum 添加类型,则应该是这样:

const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;

不要混淆了 TypeScript 中的 =>ES6 中的 =>

TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

使用接口定义函数类型

我们可以通过接口来定义函数的类型:

interface ISum {
  (x: number, y: number): number
}

const sum: ISum = (x, y) => x + y;

可选参数

前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用 ? 表示可选的参数:

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let tomcat: string = buildName('Tom', 'Cat');
let tom: string = buildName('Tom');

需要注意的是,可选参数必须接在确定参数后面。换句话说,可选参数后面不允许再出现确定参数

function buildName(firstName?: string, lastName: string) {
  if (firstName) {
    return firstName + ' ' + lastName;
  } else {
    return lastName;
  }
}
// error TS1016: A required parameter cannot follow an optional parameter.

参数默认值

ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数

function buildName(firstName: string, lastName: string = 'Cat') {
  return firstName + ' ' + lastName;
}

此时就不受「可选参数必须接在必需参数后面」的限制了:

function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName;
}

剩余参数

ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数)

function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

事实上,items 是一个数组,所以我们可以用数组的类型来定义:

function push<A, B>(array: A[], ...items: B[]): void {
  items.forEach(item => {
    console.log(item);
  })
}

重载

重载允许一个函数接收不同数量或类型的参数时,作出不同的处理。

比如,我们需要实现一个函数 reverse,输入数字 123 时,返回反转的数字 321,输入字符串 hello 时,返回反转的字符串 olleh,利用联合类型,我们可以这样实现:

type Reverse = string | number;

function reverse(x: Reverse): Reverse {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

然而这样做有一个缺点,就是不能 精确 的表达,输入数字的时候,返回也是数字,输入字符串的时候,也应该返回字符串。这时,我们可以使用重载定义多个 reverse 函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string) {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

以上代码,我们重复多次定义了 reverse 函数,前几次都是函数的定义,最后一次是函数的实现,这时,在编译阶段的提示中,就可以正确的看到前两个提示了。

TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

接口

typescript 中,我们可以使用 interface 来定义复杂数据类型,用来描述形状或抽象行为。如:

interface IPerson {
  name: string;
  age: number;
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

接口名称首字母大写,同时加上 I 前缀。

变量 p 的类型是 IPerson,这样就约束了它的数据结构必须和 IPerson 保持一致,多定义和少定义都是不被允许的。

赋值的时候,变量的形状必须和接口的形状保持一致

可选属性

有时,我们希望不要完全匹配接口中的属性,那么可以用可选属性:

interface IPerson {
  name: string;
  age: number;
  gender?: string; // 可选属性
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

在进行赋值时, gender 属性是可以不存在的。当然,这时仍然不允许添加接口中未定义的属性。

只读属性

有时候我们希望对象中的一些属性只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:

interface IPerson {
  readonly id: number;	// 只读属性
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
}

只读约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。 因此,在对象初始化的时候,必须赋值,之后,这个属性就不能再赋值。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

const vs readonly:变量用 const,对象属性用 readonly

任意属性

有时候,我们希望一个接口允许有任意属性:

interface IPerson {
  readonly id: number;
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
  [propsName: string]: any; // 任意属性
}

[propsName: string]: any;通过 字符串索引签名 的方式,我们就可以给 IPerson 类型的变量上赋值任意数量的其他类型。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  email: "102376640@qq.com", // 任意属性
  phone: 1234567890, // 任意属性
  sayName() {
    console.log(this.name);
  },
};

emailphone 属性没有在 IPerson 中显性定义,但是编译器不会报错,这是因为我们定义了字符串索引签名。

一旦定义字符串索引签名,那么接口中的确定属性和可选属性的类型必须是索引签名类型的子集。

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

// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.ts(2411)
// (property) IPerson.age?: number | undefined

[propName: string]: string;字符串索引签名类型为 string,但是可选属性 agenumber 类型,number 并不是 string 的子集, 因此编译报错。

表示数组

接口除了可以用来描述对象以外,还可以用来描述数组类型,也就是数字索引签名:

interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

变量 fibonacci 的类型是 NumberArray,如果还想调用数组的方法,则:

interface NumberArray<T> extends Array<T> {
  [index: number]: T;
}
let fibonacci: NumberArray<number> = [1, 1, 2, 3, 5];

表示函数

接口还可以用来描述函数,约束参数的个数,类型以及返回值:

interface ISearchFunc {
  (source: string, subString: string): boolean
}

let mySearch: ISearchFunc = (source, subString) => {
  let result = source.search(subString);
  return result > -1;
}

额外的类型检测

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值