深入理解TypeScript

目录

1、编译上下文(tsconfig.json)

基础使用编译选项

通过compileroptions来定制你的编译选项

 命令行手动运行ts编译器

指定文件

2、声明空间

类型声明空间

变量声明空间

3、模块

全局模块

文件模块

文件模块详情

ES模块语法

默认导入/导出

4、TypeScript类型系统

基本类型注解

数组类型注解

接口类型注解

内联类型注解

特殊类型注解

泛型

联合类型

交叉类型

元祖类型

类型别名

5、将 JavaScript 代码迁移至 TypeScript 

6、枚举

7、类型断言

8、Freshness

9、类型保护

10、Readonly


1、编译上下文(tsconfig.json)
基础使用编译选项
{}
通过compileroptions来定制你的编译选项
{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件作为单独的模块 (与 'ts.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 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.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          // 为装饰器提供元数据的支持
  }
}
 命令行手动运行ts编译器
  • 运行 tsc,它会在当前目录或者是父级目录寻找 tsconfig.json 文件。
  • 运行 tsc -p ./path-to-project-directory 。当然,这个路径可以是绝对路径,也可以是相对于当前目录的相对路径。

你甚至可以使用 tsc -w 来启用 TypeScript 编译器的观测模式,在检测到文件改动之后,它将重新编译。

指定文件
{
  "files": [
    "./some/file.ts"
  ], // 显示指定需要编译的文件
 "include": [
    "./folder"
  ], // 需要包含的文件
  "exclude": [
    "./folder/**/*.spec.ts",
    "./folder/someSubFolder"
  ] // 需要删除的文件
}
2、声明空间
类型声明空间
// 类型声明
class Foo {}
interface Bar {}
type Bas = {};

// 可以将 Foo, Bar, Bas 作为类型注解使用
let foo: Foo;
let bar: Bar;
let bas: Bas;

// 注意,尽管你定义了interface Bar,却并不能够把它作为一个变量来使用,因为它没有定义在变量声明空间中
// 我们并不能把一些如 interface 定义的内容当作变量使用
interface Bar {}
const bar = Bar; // Error: "cannot find name 'Bar'"

// 与此相似,一些用 var 声明的变量,也只能在变量声明空间使用,不能用作类型注解
const foo = 123;
let bar: foo; // ERROR: "cannot find name 'foo'"
// 提示 ERROR: "cannot find name 'foo'" 原因是,名称 foo 没有定义在类型声明空间里
变量声明空间
// 变量声明空间包含可用作变量的内容,在上文中 Class Foo 提供了一个类型 Foo 到类型声明空间,此外它同样提供了一个变量 Foo 到变量声明空间
class Foo {}
const someVar = Foo;
const someOtherVar = 123;
3、模块
全局模块
// 在一个新的ts文件中直接声明变量,它处于全局命名空间中,那么其他同项目的ts文件也可以直接使用
const foo = 123;

// 另一个ts文件调用
const bar = foo; // allowed
文件模块
// 在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域,此时全局命名空间里就不再有foo
export const foo = 123;

// 另一个ts文件调用
const bar = foo; // ERROR: "cannot find name 'foo'"

// 如果在其他文件想要对foo进行调用,则必须显示地将其导入
import { foo } from './foo';
const bar = foo; // allow
// 在新的ts文件里使用import时,它不仅允许你使用从其他文件导入的内容,还会将此文件标记为一个模块,文件内定义的声明也不会“污染”全局命名空间
文件模块详情

使用 module: commonjs 选项以及使用 ES 模块语法导入、导出、编写模块

ES模块语法
// 使用 export 关键字导出一个变量或类型
// foo.ts
export const someVar = 123;
export type someType = {
  foo: string;
};

// foo.ts
const someVar = 123;
type someType = {
  type: string;
};
export { someVar, someType };

// 重命名变量的方式
// foo.ts
const someVar = 123;
export { someVar as aDifferentName };

// 使用import导入一个变量或者类型
// bar.ts
import { someVar, someType } from './foo';

// 通过重命名的方式导入变量或者类型
// bar.ts
import { someVar as aDifferentName } from './foo';

// 整体加载
// bar.ts
import * as foo from './foo';
// 你可以使用 `foo.someVar` 和 `foo.someType` 以及其他任何从 `foo` 导出的变量或者类型

// 只导入模块
import 'core-js'; // 一个普通的 polyfill 库

// 从其他模块导入后整体导出
export * from './foo';

// 从其他模块导入后部分导出
export { someVar } from './foo';

// 通过重命名,部分导出从另一个模块导入的项目
export { someVar as aDifferentName } from './foo';
默认导入/导出
// 使用 export default
// 在一个变量之前(不需要使用 let/const/var)
// 在一个函数之前
// 在一个类之前

// some var
export default (someVar = 123);

// some function
export default function someFunction() {}

// some class
export default class someClass {}

// 导入使用 import someName from 'someModule' 语法(你可以根据需要为导入命名)
import someLocalNameForThisFile from './foo';
4、TypeScript类型系统
基本类型注解
let num: number;
let str: string;
let bool: boolean;

num = 123;
num = 123.456;
num = '123'; // Error

str = '123';
str = 123; // Error

bool = true;
bool = false;
bool = 'false'; // Error
数组类型注解
// 可以根据需要补充任何有效的类型注解(如::boolean[])。它能让你安全的使用任何有关数组的操作,而且它也能防止一些类似于赋值错误类型给成员的行为
let boolArray: boolean[];

boolArray = [true, false];
console.log(boolArray[0]); // true
console.log(boolArray.length); // 2

boolArray[1] = true;
boolArray = [false, false];

boolArray[0] = 'false'; // Error
boolArray = 'false'; // Error
boolArray = [true, 'false']; // Error
接口类型注解
// 接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明
interface Name {
  first: string;
  second: number;
  third: boolean;
}

let name: Name;
name = {
  first: 'John',
  second: 2,
  third: true
};

name = {
  // Error: 'Second is missing'
  first: 'John',
  third: true
};

name = {
  // Error: 'Second is the wrong type'
  first: 'John',
  second: false,
  third: false
};
内联类型注解
// 内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦(你可能会使用一个很糟糕的名称)。然而,如果你发现需要多次使用相同的内联注解时,那么考虑把它重构为一个接口(或者是 type alias,它会在接下来的部分提到)是一个不错的主意
let name: {
  first: string;
  second: string;
};

name = {
  first: 'John',
  second: 'Doe'
};

name = {
  // Error: 'Second is missing'
  first: 'John'
};

name = {
  // Error: 'Second is the wrong type'
  first: 'John',
  second: 1337
};
特殊类型注解
// any (能不用就不用,尽量少用)
// any 类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any 能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型
let power: any;

// 赋值任意类型
power = '123';
power = 123;

// 它也兼容任何类型
let num: number;
power = num;
num = power;


// null和undefined
// 在类型系统中,JavaScript 中的 null 和 undefined 字面量和其他被标注了 any 类型的变量一样,都能被赋值给任意类型的变量
let num: number;
let str: string;

// 这些类型能被赋予
num = null;
str = undefined;


// void 使用 :void 来表示一个函数没有一个返回值
function log(message: string): void {
  console.log(message);
}
泛型
// 函数reverse接受一个类型为T(注意在 reverse<T> 中的类型参数)的数组(items: T[]),返回值为类型T的一个数组(注意:T[]),函数reverse的返回值类型与它接受的参数的类型一样。当你传入const sample = [1, 2, 3]时,TypeScript能推断出reverse为number[]类型,从而能给你类型安全。与此相似,当你传入一个类型为string[]类型的数组时,TypeScript能推断reverse为string[]类型
function reverse<T>(items: T[]): T[] {
  const toreturn = [];
  for (let i = items.length - 1; i >= 0; i--) {
    toreturn.push(items[i]);
  }
  return toreturn;
}

const sample = [1, 2, 3];
let reversed = reverse(sample);  // 给予类型安全

console.log(reversed); // 3, 2, 1

// Safety
reversed[0] = '1'; // Error
reversed = ['1', '2']; // Error

reversed[0] = 1; // ok
reversed = [1, 2]; // ok



const strArr = ['1', '2'];
let reversedStrs = reverse(strArr);  // 推断reverse为string[]类型

reversedStrs = [1, 2]; // Error


// reverse方法结构
interface Array<T> {
  reverse(): T[];
}

// 这就意味着当你在数组上调用.reverse方法时,将会获得类型安全
let numArr = [1, 2];
let reversedNums = numArr.reverse();

reversedNums = ['1', '2']; // Error
联合类型
// 属性为多种类型之一,如字符串或者数组。这正是 TypeScript 中联合类型能派上用场的地方(它使用 | 作为标记,如 string | number)
function formatCommandline(command: string[] | string) {
  let line = '';
  if (typeof command === 'string') {
    line = command.trim();
  } else {
    line = command.join(' ').trim();
  }

  // Do stuff with line: string
}
交叉类型
// 可以从两个对象中创建一个新对象,新对象拥有着两个对象所有的功能
function extend<T extends object, U extends object>(first: T, second: U): T & U {
  const result = <T & U>{};
  for (let id in first) {
    (<T>result)[id] = first[id];
  }
  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      (<U>result)[id] = second[id];
    }
  }

  return result;
}

const x = extend({ a: 'hello' }, { b: 42 });

// 现在 x 拥有了 a 属性与 b 属性
const a = x.a;
const b = x.b;
元祖类型
// JavaScript 并不支持元组,开发者们通常只能使用数组来表示元组。而 TypeScript 支持它,开发者可以使用 :[typeofmember1, typeofmember2] 的形式,为元组添加类型注解,元组可以包含任意数量的成员
let nameNumber: [string, number];

// Ok
nameNumber = ['Jenny', 221345];

// Error
nameNumber = ['Jenny', '221345'];


// 将其与ts中的解构一起使用
let nameNumber: [string, number];
nameNumber = ['Jenny', 322134];

const [name, num] = nameNumber;
类型别名
// 类型多样的时候可以自定义别名进行代码优化
type StrOrNum = string | number;

// 使用
let sample: StrOrNum;
sample = 123;
sample = '123';

// 会检查类型
sample = true; // Error


// 与接口不同,你可以为任意的类型注解提供类型别名(在联合类型和交叉类型中比较实用)
type Text = string | { text: string };
type Coordinates = [number, number];
type Callback = (data: string) => void;
5、将 JavaScript 代码迁移至 TypeScript 
  • 添加一个 tsconfig.json 文件;
  • 把文件扩展名从 .js 改成 .ts,开始使用 any 来减少错误;
  • 开始在 TypeScript 中写代码,尽可能的减少 any 的使用;
  • 回到旧代码,开始添加类型注解,并修复已识别的错误;
  • 为第三方 JavaScript 代码定义环境声明。
6、枚举
// 简单定义一个枚举
// 数字型枚举
// 默认情况下,第一个枚举值是 0,然后每个后续值依次递增 1
enum CardSuit {
  Clubs,
  Diamonds,
  Hearts,
  Spades
}

// 简单的使用枚举类型
let Card = CardSuit.Clubs;

// 类型安全
Card = 'not a member of card suit'; // Error: string 不能赋值给 `CardSuit` 类型
card = 90; // success


// 定义一个字符串枚举
export enum EvidenceTypeEnum {
  UNKNOWN = '',
  PASSPORT_VISA = 'passport_visa',
  PASSPORT = 'passport',
  SIGHTED_STUDENT_CARD = 'sighted_tertiary_edu_id',
  SIGHTED_KEYPASS_CARD = 'sighted_keypass_card',
  SIGHTED_PROOF_OF_AGE_CARD = 'sighted_proof_of_age_card'
}
7、类型断言
// 这里的代码发出了错误警告,因为 foo 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 bar 或 bas
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'


// 类型断言
interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';


// 上述类型断言存在一个弊端,就是忘记定义foo的属性时,编译器不会报错
// 可以改成以下方式
interface Foo {
  bar: number;
  bas: string;
}

const foo: Foo = {
  // 编译器将会提供 Foo 属性的代码提示
};


// 类型断言不安全的原因,当当使用者了解传入参数更具体的类型时,类型断言能按预期工作,反之则不能,会报错,解决方法:双重断言
function handler(event: Event) {
  const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}

function handler(event: Event) {
  const element = (event as any) as HTMLElement; // ok
}
8、Freshness
function logName(something: { name: string }) {
  console.log(something.name);
}

const person = { name: 'matt', job: 'being awesome' };
const animal = { name: 'cow', diet: 'vegan, but has milk of own specie' };
const randow = { note: `I don't have a name property` };

logName(person); // ok
logName(animal); // ok
logName(randow); // Error: 没有 `name` 属性

logName({ name: 'matt' }); // ok
logName({ name: 'matt', job: 'being awesome' }); // Error: 对象字面量只能指定已知属性,`job` 属性在这里并不存在。
// 这种错误提示只会发生在对象字面量上


// 用例:React State
// 假设
interface State {
  foo: string;
  bar: string;
}

// 你可能想做:
this.setState({ foo: 'Hello' }); // Error: 没有属性 'bar'

// 因为 state 包含 'foo' 与 'bar',TypeScript 会强制你这么做:
this.setState({ foo: 'Hello', bar: this.state.bar });

// 如果想要避免上述的报错,只需要添加可选链符号?
interface State {
  foo?: string;
  bar?: string;
}
9、类型保护

typeof、instanceof、in、字面量类型保护、自定义类型保护

10、Readonly

标记属性为只读属性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值