【青训营】- TypeScript入门

306 阅读8分钟

概念

  • JavaScript超级语言,提供了类型系统和ES6支持,有自己的编译工具
  • TypeScript操作类型集合,集合有集合的运算;JavaScript操作值,值有值的运算

环境

安装nvm

安装和编译

// index.ts
npm i - g typescript
// 编译
tsc index.ts

工具

typescript-play
type-challenges
实用工具类型

配置

  1. 创建配置文件
  2. 设置配置项
  3. 使用配置文件

配置DOC
基本配置
tsconfig参考说明
tsconfig结构

// 初始化配置文件tsconfig.json
node_modules/.bin/tsc --init --locale zh-CN
tsc --init

// 配置说明
* target: ts代码转换成哪个版本的js代码 es5 es3

* module: 使用的模块化的标准是什么

* outDir: 转换后的js代码存放的文件夹路径

* rootDir: 哪个目录中的ts代码进型转换,ts代码的存放路径

* strict: 转换为严格模式的js代码!

// 使用配置文件

tsc -p ./tsconfig.json

// 可以针对不同场景使用基本配置
https://github.com/tsconfig/bases/
// tsconfig参考说明
https://www.typescriptlang.org/tsconfig
// tsconfig结构
http://json.schemastore.org/tsconfig

声明文件

  • 标记某个 js 库里面对象的类型
  • 类似Node模块的采用 CommonJS 模块规范 .d.ts

类型系统

结构类型系统:通过类型的结构确定两个类型是否相等(TypeScript,Haskell,Ocaml,Go...)
名义类型系统:通过类型的名称确定两个类型是否相等(C,C++,Java,C#,Rust...)

类型与集合关系/类型联合和交叉

  • 空集 ---- never
  • 单元素集合 ---- 字面量类型
  • 有限集合 ---- 布尔类型
  • 无限集合 ---- String/BigInt/Symbol/Number
  • 全集 ---- unknown

类型缩小和拓宽:

体现TypeScript在确定性和灵活性上取得平衡 拓宽:let, var typescript将字面量类型拓宽为更宽泛的类型
缩小:某些情况下typescript 将类型推断为更确切的类型
(null check, as const, instanceof, typeof, 属性检查, Tagged Union, 用户类型守卫(User-defined Type Guard), 代码流分析)
type-widening-and-narrowing
字面量类型拓宽
Widening-and-Narrowing-in-Typescript

类型空间与值空间

值空间(运行时的各种类型)

  • let, const, var后的符号
  • 类型操作
  1. 函数参数和默认值
  2. for-in遍历
  3. if-else判断 类型空间(编译期的各种类型)
  • 编译后消失
  • 类型别名/类型注解type T = typeof Person; const p: Person
  • 断言后的符号target as/is HTMLELEMENT
  • 类型操作
  1. 泛型参数和默认值
  2. key-in遍历
  3. 条件类型判断 值空间和类型空间
  • enum, class, namespace后的符号
  • typeof 值空间:返回字符串'string','number','boolean','object','undefined','function','bigint','symbol'
    类型空间:返回标识符类型
  • []索引 值空间:val.field或val[field或val]返回值 类型空间:Type[T]
interface Person { name: string, age: number, id: symbol }
type personName = Person['name'] // name是字面量类型,不是字符串
type personNameAge = Person['name' | 'age'] // 也可以是字面量的集合
值空间
// 类型空间
interface myArray<T> {
    [name: string]: T,
    [age: number]: T,
    [id: symbol]: T
}
type nameArray = myArray<string> // 映射类型
  • this Yehuda Katz --- Understanding JavaScript Function Invocation and "this"
    值空间:this指向在运行时确定
    类型空间:类方法链式调用
  • & | 运算符 值空间:位运算与或非
    类型空间:交叉和联合
  • const 值空间:常量
    类型空间:常量断言,收窄类型
  • extends 值空间:定义子类
    类型空间:类型约束T extends number或接口继承interface A extends B
  • in 值空间:判断属性是否存在
    类型空间:映射类型key的声明

类型定义

Top Type Undefined<--Void<--Unknown<--Null
Number<--NumberEnum/BigInt/Boolean/String<--String Enum/Symbol<--Unique Symbol/Object<--Function/Constructor/Array<--Turple
Bottom Type
never
Top Type | Bottom Type
any

下层可以赋给上层,unknown可以指向任何类型,不存在never类型的变量

日常类型:

basic-types

字符串类型string:
type Dir = 'n | 's' | 'w' | 'e';
type Dir = Dir | Capitalize<Dir> | Uppercase<Dir>; // Dir | 'N' | 'S' | 'W' | 'E'
符号类型symbol:

unique symbol必须const才可行 必须通过typeof 常量的形式引用; unique symbol赋值给灵另一个const 拓宽为symbol,不希望拓宽,注解为常量typeof

let a = Symbol('a');
let a1: unique symbol = a; // Error
const b: unique symbol = Symbol('b'); // const才可行 必须通过typeof 常量的形式引用
const b1: typeof b = b; // 不希望拓宽,注解为常量typeof
数字类型number:

number NaN Infinity 浮点数 BigInt与Number转换运算

let a = 1; // number
let b = Infinity; // number
const PI = 1; // PI
let nan = Infinity; // number
let d: 4.56; // 4.56
// bigint -(2^53 - 1), (2^53 - 1
let a = 1234n; // bigint
const hex = BigInt("0x1fffffffffff");  // bigint
let a = a + BigInt(123); 不混合运算,转换运算
布尔类型boolean:
let a = true; // boolean 拓宽
const a = true; // true 收窄
let d: boolean = true;  // boolean
let d: true = true;  // true
数组类型Array和元祖类型Turple:

const数组不收窄,空数组any[], any[]通过push离开作用域数组确定类型,元组类型支持rest操作符,支持可选操作符

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
(() => {
let e = [];
e.push(1, 'red');
return e; // (string | number)[]
})();
// 元组
let x: [string, number];
type NBS = [boolean, ...string[]]
对象类型Object:
// 类型注解
const Person: { name: string, age: number } = {
    name: '123'
    age: 234
}
// 类型别名
type Person = { name: string, age: number }
type personKeys = keyof Person
枚举类型Enum:

未显示赋值的自增性,显示赋值字符串不存在反反向映射,可以合并,常量枚举被替换为对应的值,不会在值空间创建变量,没有双喜那向映射

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
null | undefined | never | void类型:

undefined未定义,null已声明未赋值
void没有显示返回值,never函数无法返回

function error(msg: string): never {
    throw new Error(msg)
}
unknown | any类型:

无法预知类型用unknown,收窄类型后使用
避免any,隐藏设计细节,开启严格noImplicityAny

泛型 | 接口 | 类 | 函数 | 条件类型 | 模板字面量类型 | 映射类型 | 类型操作

  • 泛型:将两个或多个具有相同类型的值关联起来;创建可重用的组件,一个组件可以支持多种类型的数据;泛型约束
function identity<T>(arg: T): T { return arg; }
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    // Now we know it has a .length property, so no more error
    return arg;
}
  • 接口:结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”,描述函数类型

类型别名更为通用,右侧可包含类型表达式(联合、交叉、条件类型),具有某种结构 接口间可继承,检查二者关系,类型联合最大尝试,不报错 同作用域多个同名接口合并,多个同名类型报错

function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
} 
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
interface SearchFunc { (source: string, subString: string): boolean; }
  • 类:基于类的面向对象的方式,成员:属性、方法、构造函数;继承(extends);公共(public),私有(private),受保护的修饰符(protected),只读(readonly),存取器(get, set),静态属性(static),抽象类型(abstract)
class Greeter {
    greeting: string; 、 属性
    constructor(message: string) { this.greeting = message; } // 构造函数
    greet() { return "Hello, " + this.greeting; } // 方法
}
class AGreeter extends Greeter {} // 继承
let greeter = new Greeter("world");
  • 函数:实现抽象层,模拟类,信息隐藏和模块,区别类,命名空间和模块,函数定义行为;重载

this参数是个假的参数,它出现在参数列表的最前面

let myAdd: (x: number, y: number) => number = function(x: number, y: number): number {
    return x + y; 
};
// `this`参数是个假的参数,它出现在参数列表的最前面
function f(this: void) { // make sure `this` is unusable in this standalone function }
  • 映射类型:从旧类型中创建新类型的一种方式,新类型以相同的形式去转换旧类型里每个属性
type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };
  • 字面量类型

字符串字面量类型还可以用于区分函数重载
数字字面量类型用于缩小范围调试bug

// 字符串字面量类型还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
    // ... code goes here ... 
}
// 数字字面量类型用于缩小范围调试bug
function foo(x: number) {
    if (x !== 1 || x !== 2) {
        // ~~~~~~~ // Operator '!==' cannot be applied to types '1' and '2'. 
    }
}
  • 类型操作 keyof

keyof 获取类型上已知的、public的类型联合
列举法 如对象类型; 公式法 如映射类型 key-in

type k1 = keyof unknown/undefined/void/null/object/never --> never
type k2 = keyof any ---> string | number | symbol

type k3 = keyof bigint ---> 来自interface BigInt
type k4 = keyof string ---> 来自interface String
type k5 = keyof boolean ---> 来自interface Boolean
type k6 = keyof number ---> 来自interface Number
type k7 = keyof number ---> 来自interface Symbol
JSDoc 代码即注释

作用:注释的形式来降低 JavaScript 项目的维护难度,提升可读性,不会对源码产生任何的影响,是一个过渡方案。

重载签名和实现签名

规则:无法预知类型用unknown,收窄类型后使用
规则:避免any,隐藏设计细节,开启严格noImplicityAny 规则:在可能的情况下,始终更喜欢具有联合类型的参数而不是重载
规则:如果可能,使用类型参数本身而不是约束它
规则:始终使用尽可能少的类型参数
规则:如果一个类型参数只出现在一个位置,强烈重新考虑你是否真的需要它
规则:为回调编写函数类型时,切勿编写可选参数,除非您打算在不传递该参数的情况下调用该函数
规则:请注意,目前无法在解构模式中放置类型注释。这是因为以下语法在 JavaScript 中已经意味着不同的东西。

interface Props {
    a?: number;
    b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 };

// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
参考链接

typescriptlang
typescript-book-chinese 掘金
知乎专栏
# Questions tagged [typescript]
源码issues
git-shortlog

Books

TypeScript编程
深入理解TypeScript
Effective TypeScript
Programming with Types

https://git-scm.com/docs/git-shortlog/zh_HANS-CN
typescript git(:main) git shortlog -sn