《TypeScript入门与实战》笔记

TypeScript 入门与实战

  • tsconfig.json 编译配置文件 compilerOptions

    • 启用 --strict 编译选项,开启严格模式
    • 启用 --strictNullChecks,undefined 值只能够赋值给 undefined 类型,null 值只能够赋值给 null 类型,实际上这种表述不完全准确。因为在该模式下,undefined 值和 null 值允许赋值给顶端类型,同时 undefined 值也允许赋值给 void 类型。
    • 启用 --noImplicitAny,禁用隐式 any 类型转换
    • 启用 --noImplicitThis,this 不能随意.任意值
    • 启用 --strictPropertyInitialization,类的成员变量初始化检查,需要与 --strictNullChecks 同时使用
    • 启用 --strictFunctionTypes,严格函数类型检查,false 时 对应位置上的参数只要存在子类型关系即可,而不强调谁是谁的子类型
    • 启用 --noStrictGenericChecks,非严格泛型函数类型检查,即忽略函数的参数类型
    • --importsNotUsedAsValues: remove|preserve|error,如何处理 import type,删除/保留/强制 import type
    • moduleResolution: Classic|Node,默认与--module相关: --module CommonJS --moduleResolution Node, --module ES6 --moduleResolution Classic
  • //#region //#endregion 定义了代码折叠区域的起止位置

  • es6 规范中定义的 7 种数据类型:Undefined、Null、Boolean、String、Symbol、Number、Object

    • unique symbol:ts 中只允许用 const 或 readonly 来定义
    • unique symbol:ts 中只允许用 Symbol()或 Symbol.for()初始化(“确保”其唯一性)
    • unique symbol:ts 中将 Symbol.for('same')赋值给两个变量,编译器会认为它们是两个不同的值,即使他们相同。
  • 枚举:无初始值,从前一个枚举成员的值+1(如果是首个为 0)

    • 变量:数值型枚举 = number ✔︎; 变量:字符串枚举 = string ✘
    • 在带有字符串成员的枚举中不允许使用计算值
    • 枚举成员映射(仅数值型枚举):Bool[Bool.False] // 'False'
    • const enum 编译后会被替换成字面量,非const会被转换成对象调用
  • any 与 unknown 的区别:any 为放弃类型检查,unknown 为未知类型。

    • any 可以给任意类型变量赋值,nuknown 不可。
    • any 可以进行任意操作,nuknown 几乎什么不能干(字符串、对象操作、数学运算等);
  • 数组

    • const arr: (string | number)[] = [0, 'a']const arr: string | number[] = 'a' 不同
    • const arr: string[] = ['0', 'a']const arr: [string] = ['a'] 不同
    • 只读数组:arr: ReadonlyArray<string>arr: readonly string[]。不能 arr: readonly Array<string>
      • 内置只读对象 type Readonly<T> = { readonly [P in keyof T]: T[P] }
      • 所以可以 arr: Readonly<string[]>arr: Readonly<Array<string>>
      • 只能访问不可变更,不可赋值给普通数组,但接受普通数组给自己赋值(这不是个 bug 吗)
  • 对象

    • const obj: object = { foo: 0 } obj.foo // err; 因为 object 类型下并没有 foo 属性
    • const arr12: { x: number } = { x: 0, y: 3 }; // err
      const arr12: { x: number } = { x: 0, y: 3 } as { x: number; y: number }; // ok
      const arr12: { x: number } = { x: 0, y: 3 } as { x: number; [key: string]: number }; //ok
      const temp = { x: 0, y: 3 };
      const arr12: { x: number } = temp; // ok
      
  • 函数

    // 指定 this 结构
    function foo(this: { name: string }, x: number): void {
      console.log(this.name, x);
    }
    foo.prototype.name = "foo";
    new foo(3);
    
    • 可以根据参数个数不同重载方法
  • Interface

    • 索引签名 [prop: string]: Type 任意属性名、任意属性个数
    • 重载的方法必须全部是不可选(可选),不可一些方法可选一些方法不可选
    • 只读属性或方法 readonly count: number
    • 继承多父接口时,父接口们的属性发生冲突,继承会报错,需要在子接口的重写,且重写的属性需要兼容所有父接口(冲突的属性的类型)
      interface A { name: string }
      interface B { name: number }
      interface C extends A, B { name: any }
      
  • 类型别名 Type

    • type A = { name: string }; type B = A & { age: number } 效果与继承一样
    • 与 Interface 的区别:
      • 定义方法:type foo = (x: number) => void;interface foo { (x: number): void }
      • type 可以使用联合类型、元组;interface 可继承其它 interface 或 class;
      • interface 可以声明合并 interface A { name: string }; interface A { age: string };
    • const A = class B {} “B” 只供类内部使用
    • 使用非空类型断言“!”来通知编译器该成员变量已经进行初始化 class { a!: number; constructor() {} },以逃避编译器的检查
    • 可以使用 [key: string]: number; 定义属性,但它不符合 ts 的宗旨
    • 有 public、protected、private,主要依靠编译器的语法检查(ES10 中添加了 #attr 语法做为私有属性)
    • constructor 可以重载
    • 子类重写父类属性,只允许放宽,protected 可转为 public,不可转为 private
    • 子类的构造函数必须先调用 super()再使用 this,且(ts 中)super()必须是第一个语句
    • 实例化子类顺序:父类属性 -> 父类构造函数 -> 子类属性 -> 子类构造函
    • extends 继承类;implements 实现接口。 类只支持单继承;implements 可以多实现
    • 接口继承了含有非 public 属性的类,则此接口只能由该类的派生类实现(书 5.15.9 最后)
    • static 静态属性(ES10),静态属性可继承(且修改其值不影响父类;不改子类改父类,子类也变;都改互不影响)
    • 抽象类 abstract:不可实例化,可继承和被继承,被继承的子类可以实例化。抽象属性不可有具体实现,且不能是 private
  • 泛型

    • function fn<T>(arg: T): T { return arg; }fn<string>('xx')

    • <定义类型 1,定义类型 2...>(arg: 使用类型): 使用类型;T 不能是 undefined 和 null

    • function fn<T1, T2 = string>(arg: T1): T2 {}fn<string, boolean>('xx')

    • 泛型约束(extends):T 可以继承一个类型,若指定的默认类型,默认类型必须符合继承的类型(泛型约束);实际传入的类型也必须符合泛型约束

      function fn<T extends number = 0 | 1>(arg: T)fn<2 | 3>(3)

    • 泛型类描述的是类的实例类型,所以类的静态成员中不允许引用类型参数

      class A<T>{ static tag: T; } // 编译错误!静态成员不允许引用类型参数 访问 tag 时可以使用A.tag,泛型管不到静态属性

  • 联合类型type T = T1 | T2 满足一个就行;有同名不同类型的属性,不可混用。

  • 交叉类型type T = T1 & T2 全部满足才行;有同名不同类型的属性,简单类型为 never,方法则重载。

    • “&”相当于“×”,而“|”相当于“+”

      T = (string | 0) & (number | 'a');
        = (string & number) | (string & 'a') | (0 & number) | (0 & 'a');
        = never | 'a' | 0 | never;
        = 'a' | 0;
      
  • 索引类型 keyof Typekeyof any => string|number|symbolkeyof unknown => never

    • 联合类型keyof(T1 | T2) 共有的属性名。
    • 交叉类型type T = T1 & T2 全部满属性名。
  • 映射对象:type newT = { readonly [K in keyof oldT]?: oldT[K] } 复制 oldT 将所有属性变为只读可选的(同态映射)

    • [K in string]: number 等于 [prop: string]: number
    • 同态映射会默认拷贝源对象类型中所有属性的 readonly 修饰符和“?”修饰符
    • 使用变量中转过的keyof oldT不再属于同态映射,所以不会拷贝原属性的修饰符:type EK = keyof oldT; type newT = { [K in EK]?: oldT[K] }
    • type Required = { +readonly [K in keyof T]-?: T[K] } 删除 ? 修饰符添加 readonly 修饰符;
      type T = {
        a?: string | undefined | null;
        readonly b: number | undefined | null;
      }
      // {
      //     readonly a: string | null;
      //     readonly b: number | undefined | null;
      // }
      type RequiredT = Required<T>; // - 号可以过删除 undefined,but not null
      
    • 更多类型的映射方式参见 6.6.4 同态映射对象类型
  • 条件类型

    • type T = true extends boolean ? string: number; => string
    • 裸类型参数(Naked)即没有任何装饰([]、{}等)的类型参数;
    • 如果extends NakedType(分布式条件类型)则条件展开;
    • 内置工具类型:Exclude<T, U>Extract<T, U>Non-Nullable<T> 从联合类型中过滤、挑选指定类型、创建非空类型;
    • T extends Array<infer U> ? U : never 推断;inferType<number[]> => number
  • 内置工具类型(6.8 那么多,怎么可能记得住)

    • Partial<T> => { [K in keyof T]?: T[K] } 复制一份并全为可选
    • Required<T> => -? 全为必选
    • Readonly<T> => 全为只读
    • Record<AttrNames, AttrType> => { name1: AttrType, name2: AttrType... }
    • Pick<T, AttrNames> => 从 T 中选 AttrNames 这些属性创建新类型
    • Omit<T, AttrNames> => Pick 的互补,AttrNames 为要忽略的属性
    • Exclude<Ks, AttrNames> => 与 Omit 的区别为 Ks = keyof T
    • Extract<Ks, AttrNames> => 与 Pick 的区别为 Ks = keyof T
    • NonNullable<T> => 从类型 T 中剔除 null 类型和 undefined 类型并构造一个新类型
    • Parameters<fnT> => 使用方法类型 fnT 的参数类型构造一个元组类型Parameters<(s: string) => void> // [string]
    • ConstructorParameters<fnT> => 构造函数 ConstructorParameters<new (s: string) => void> // [string]
    • ReturnType<fnT> => 方法类型的返回值类型 ReturnType<(s: string) => void> // void]
    • InstanceType<T> => 获取构造函数的返回值类型,即实例类型
    • ThisParameterType<fnT> => 方法中 this 参数的类型(需要启用“--strictFunctionTypes”编译选项)
    • OmitThisParameter<T> => 剔除 this 参数
    • ThisType<T> => 不创建新类型 用于定义对象字面量的方法中 this 的类型???(需要启用“--noImplicitThis”)
  • 类型查询,对 typeof 进行了扩展 const arg2: typeof arg1 = arg1

  • 类型断言<T>expr

    • <HTMLElement>ele = ele as HTMLElement
    • 强制类型转换 ele as unknown as T,复杂类型之间的类型断言,编译器可能会无法识别出正确的类型,因此错误地拒绝了类型断言操作
    • value as const,只读字面量类型,如果 value 是个多级对象,只读作用于所有深度
    • 非空类型断言“!”
  • 类型细化

    • 类型守卫:typeof、instanceof、in
    • 断言函数:function fn(x): asserts x is T or asserts x
      • 前者,只有 x 是 T 类型时,该函数才会正常返回,且此逻辑要由开发者自己实现;
      • 后者,只有 x 为真时,才会正常返回,逻辑由开发者实现;
      • 断言函数没有返回值。用于控制流程;

类型深入(第 7 章)

  • 字面量类型:原始类型的子类型,比如具体的值

  • never <: undefined <: null <: 其它类型

  • 函数类型重载:S 是 T 的子类型,且 T 存在函数重载,那么 T 的每一个函数重载必须能够在 S 的函数重载中找到与其对应的子类型

  • 结构化子类型:根据对象的属性名和类型,直接判断两对象间的父子关系

  • 实例的父子关系只检查非静态、非构造的成员属性,包含另一个的所有成员属性就是它是子类型。若有 protected 属性要求两者间有继承关系

  • 泛型函数:noStrictGenericChecks 时只检查参数个数不检查类型,否则,两者间互相带入对方的类型进行推断

  • 子类型在理论上可以赋值给父类型,any、数值型枚举除外。

  • ts 命名空间基于自执行函数实现

  • 通过“tsconfig.json”中 files 配置文件能够定义文件间的加载顺序

  • 三斜线指令 /// <reference path="a.ts" /> 定义文件间依赖(a.ts 不在 tsconfig.json 中定义也会被打包)

  • 模块

    • CommonJS:nodejs,同步加载文件,不适用于浏览器
    • AMD:异步模块定义,requirejs
    • UMD:通用模块定义,基于上是 vue 打包后的样子
  • export ... from xxx 其它模块的导出作为当前模块的导出,xxx 中的功能不能在当前文件中使用。

  • 空导出使用是的模块的副作用

  • 类型导入导出import [type|interface] ..., export [type|interface] ... 如果一个模块只导出类型,其副作用在打包时会被删除

  • import(uri) => Promise<Module> 异步引入模块(es 语法,非 ts 独有)。Module 的值相当于 import * as Module from 'uri'

  • 编译 tsc index.ts --module [None|CommonJS|AMD|UMD|ES6|ES2020....]

  • 外部类型声明

    • *.d.ts
    • declare const devicePixelRatio: number;
    • declare module 'io' { export function readFile(uri: string): string; }
    • declare module 'jquery'; 放弃对 jquery 插件进行类型检查
    • 第三方插件没有 d.ts 声明文件时可以在 npm 官网使用@types/插件名搜索,或者于https://www.typescriptlang.org/dt/search?search=jquery处查找
    • package.json 文件中新增了 typings|types 属性指定包的声明文件,未指定默认为 main 同名同位置文件
  • 模块解析 ts --moduleResolution [Classic|Node]

    • --baseUrl: '.' 非相对模块导入的基准路径
    • path: { "b": ["bar/b"], "@utils": ["./src/utils"] } 非相对模块导入的映射路径
    • rootDirs: ['src1', 'src2'] 使用不同的目录创建出一个虚拟目录,相对模块导入的基准路径
    • 外部模块声明 typings.d.ts(vue 中访问不到 d.ts 中的自定义 type,目前看来是 eslint 的问题,没有解决)
  • 声明合并

    • 接口合并:同名 interface 会被合并成一个。属性出现同名不同类型会报错;方法出现同名不同类型会生成重载(入参为字面量的优先级高,后声明的优先级高);
    • 枚举合并:同名 enum 会被合并成一个。有相同值的枚举合并报错;const 与非 const 枚举合并报错;
    • 类合并:同名 class 不能合并。 但 declare class 可以和 interface 合并,当做对 class 类的扩展
  • 扩充模块声明

    import { A } from './a'
    declare module './a' {
      // 正确
      interface A {
        other: string;
      }
      // 错误,不能扩充顶层声明
      interface B {
        name: string;
      }
    }
    const a: A = { other: 'A中没有的属性' }
    

    如果是import './a'无法对其扩充,interface 为必须

  • 扩充全局声明

    declare global {
      interface Window {
        other: string;
      }
    }
    

ts 配置

  • 使用 tsc 指令时:

    • 路径中的空格:tsc 'file name.ts' or tsc file\ name.ts

    • 多个文件:使用空格分割,或者使用通配符

    • -w:文件变更时自动重新编译

    • 严格类型: --strict 为总开关:

      • noImplicitAny 禁用隐式 any 类型转换
      • strictNullChecks 顶上
      • strictFunctionTypes 顶上
      • strictBindCallApply 对 call apply bind 中的 this 参数进行类型检查
      • strictPropertyInitialization 类属性初始化检查
      • noImplicitThis 顶上
      • alwaysStrict 启用 js use strict
  • tsconfig.json 若一个目录中存在此文件,该目录将被编译器视作 TypeScript 工程的根目录(或使用-p/--project tsconfig.json 来指定编译配置)。

    • tsc --init --locale zh-CN 当前目录初始化一个 tsconfig.json

    • compilerOptions > listFiles 打印出参与本次编译的文件列

    • files 不支持模糊匹配,顶上

    • include 定义编译文件列表,常与exclude一起用,以剔除 include 中不需要的文件。它们都支持模糊匹配

    • compilerOptions > typeRoots 默认指向 node_modules/@types;值为相对于 tsconfig.json 的路径列表

    • compilerOptions > types 功能和 typeRoots 相同,但值为具体文件而非目录

    • compilerOptions > isolatedModules 当编译器发现无法被正确处理的语言结构时给出提示

    • tsc --showConfig 不编译代码,只显示当前配置

    • extends 可以继承其它 tsconfig.json

  • 工程引用:使用 references 引用其它工程,被引用的工程需要开启 composite

    {
      "references": [
        { "path": "../dll1" },
        { "path": "../dll2/tsconfig.release.json" }
      ]
    }
    
    {
      "compilerOptions": {
        "composite": true,
        "declaration": true, // 必须
        "declarationMap": true // 必须,对d.ts生成sourceMap,转到定义时跳入真实代码位置而不是d.ts
        // 如果设置了 include 或 files,所有源文件必须都包含在内
      }
    }
    
    • tsc [--build|-b] --watch 查找工程引用,构建引用(如果有更新)

      • --verbose:打印构建日志;
      • --dry:只要日志但不真构建;
      • --clean:清理输出文件;
      • --force:强制重新编译;
      • --noEmit:仅类型检查,不编译;
    • solution 工程:在所以工程的父目前创建 tsconfig.json,references 引用所有工程,include、files 设为空数组。

  • Javascript 类型检查

    • tsc index.js --allowJs --outDir dist 编译 js 文件

    • --allowJs --checkJs 对 js 文件进行类型检查

    • // @ts-nocheck 对当前 js\ts 禁用类型检查

    • // @ts-check 对当前 js\ts 强制开启类型检查,无论有无--checkJs

    • // @ts-ignore 忽略下一行语句的类型检查

    • JSDoc: 基于/** xxx */注释

      • @typedef {(number | string )} CompanyID 生成自定义类型 CompanyID
      • @type {CompanyID} 定义变量类型
      • @param {CompanyID} cpyId - 公司id 定义参数类型
      • @param {CompanyID} [cpyId=0] - 公司id 默认参数
      • @return {CompanyID}
      • @extends {FatherClass} 定义继承的基类
      • @public@protected@private@readonly 等等

TS 人家的例子

  • 转译器 tsc index.js --allowJs --target ES5 --outFile index.es5.js 将 js 文件编译为 es5 语法

  • babel7 提供了内置的 ts 支持

    • @babel/core 核心转译功能
    • @babel/cli 命令行工具
    • @babel/preset-env 按需编译和按需打补丁
    • @babel/preset-typescript ts 支持包
npm init --yes
cnpm i typescript -D
cnpm i @babel/core @babel/cli @babel/preset-env @babel/preset-typescript -D
npx babel [开发目录] --out-dir lib --extensions \".ts,.tsx\" --source-maps inline
// .babelrc.json
{
  "presets": ["@babel/env", "@babel/typescript"]
}

名词

  • 顶端类型:通用类型,超类型,ts 中指 any、unknown

  • 尾端类型:never

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容