TS从入门到类型体操

271 阅读3分钟

Ts入门部分

Ts是什么?

它是JavaScript的一个超集,主要在JavaScript的基础上添加了可选的静态类型和基于类的面向对象编程,并支持es的最新特性。

Ts和Js的具体区别?

TypeScript | JavaScript | | :---------------------------: | :-------------------: || | 可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 | | 强类型 | 弱类型 | | 最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 | | 支持模块、泛型和接口 | 不支持模块,泛型或接口 | | 在编写代码时IDE支持自动联想 | 不支持IDE自动联想提示 |

TS有哪些类型?

Sring
Number
Boolean
Symbol
Array
Object
undefined子类型
null子类型
除了以上还有:
Enum
Unknown 顶级类型
unknown 类型只能被赋值给 any 类型和 unknown 类型本身 Void
Any 顶级类型
Tuple

let tupleType: [string, boolean];
tupleType = ["semlinker", true];

Never
never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}

注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:

type Foo = string | number | boolean;

然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保

controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

ts断言有哪些

类型断言
1.<>

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

2.as

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

非空断言 !
注意:它只影响编译过程,不影响最终代码生成产物

const a: number | undefined = undefined;
const b: number = a!;
console.log(b); 

以上 TS 代码会编译生成以下 ES5 代码:

"use strict";
const a = undefined;
const b = a;
console.log(b);

ts类型守卫有哪些

in

function checkObj(obj: T) {
  if ("everyThing" in obj) {
     ...
  }
}

hasOwnProperty

function checkObj(obj: T) {
  if (obj.hasOwnProperty('everyThing')) {
     ...
  }
}

typeof

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

instanceOf

if (child instanceof father) {
  // child的类型收窄为 'father'
}

is

function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

ts复杂类型分类?

联合类型
a| b |c 三选一

交叉类型
a & b & c 三合一

同名基础类型属性的合并(并成never)

interface X { c: string; d: string; } 
interface Y { c: number; e: string } 
type XY = X & Y;
type YX = Y & X;

同名非基础类型属性的合并可以合并成

interface D { d: boolean; } 
interface E { e: string; } 
interface F { f: number; }
interface A { x: D; }
interface B { x: E; } 
interface C { x: F; } 
type ABC = A & B & C;

TypeScript 函数与 JavaScript 函数的区别

TypeScriptJavaScript
函数类型无函数类型
必填和可选参数所有参数都是可选的
函数重载无函数重载

函数重载: 函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
  // type Combinable = string | number;
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}

当 TypeScript 编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。

TypeScript 接口

在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于以外,也常用于对「对象的形状(Shape)」进行描述。

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

const p1 = { name: "semlinker" };
const p2 = { name: "lolo", age: 5 };
const p3 = { name: "kakuqo", sex: 1 }

TypeScript 类

在 TypeScript 中,我们可以通过 Class 关键字来定义一个类:

class Greeter {
  // 私有字段
  private fullName: string;
  #fullName: string;
  // 静态属性
  static cname: string = "Greeter";
  
  get fullName(): string { 
    ...
  } 
  set fullName(newName: string) { 
    ...
  }

  // 成员属性
  greeting: string;

  // 构造函数 - 执行初始化操作
  constructor(message: string) {
    this.greeting = message;
  }

  // 静态方法
  static getClassName() {
    return "Class name is Greeter";
  }

  // 成员方法
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

TypeScript 泛型

泛型可以代指任何类型,甚至可以了解成any,一般用于T来表示,但又不止于T,因为会存在多个泛型

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。 如果没什么关联的话用U代表第二个泛型,如果还有的话可以任意取一个大写字母
    应用:
    1.泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

2 泛型接口

interface IKeyValue<T, U> { key: T value: U } 
const k1:IKeyValue<number, string> = { key: 18, value: 'lin'} 
const k2:IKeyValue<string, number> = { key: 'lin', value: 18}

3 泛型数组

const arr: Array<number> = [1,2,3]

泛型工具类型:
1.keyof

interface Person {
  name: string;
  age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof { [x: string]: Person };  // string | number

在 TypeScript 中支持两种索引签名,数字索引和字符串索引:

interface StringArray {
  // 字符串索引 -> keyof StringArray => string | number
  [index: string]: string; 
}

interface StringArray1 {
  // 数字索引 -> keyof StringArray1 => number
  [index: number]: string;
}

为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引。所以 keyof { [x: string]: Person } 的结果会返回 string | number

2.in

in 用来遍历联合类型:

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

3 extend

K extend keyof T 表示 K 是 T 的子类型,这里是一个类型约束声明。

比如 type T = "a" | "b" | "c";那么 K 可以是 "a",也可以是 "a" | "c" 或者 "a" | "b" | "c" 等

TypeScript装饰器

  • 类装饰器(Class decorators):可以给类传参数,在原型上绑定一些公有方法
  • 属性装饰器(Property decorators):可以给属性做一些限制参考object.defineProperty
  • 方法装饰器(Method decorators):可以在方法的执行前后做一些操作
  • 参数装饰器(Parameter decorators):可以对参数做一些common的校验和处理等

需要注意的是,若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

Ts入门解惑

Ts效能分析

大项目 vs 小项目
业务项目 vs 技术项目
多人项目 vs 单人项目 迭代频繁 vs 很少迭代 个人精通 vs 个人了解

用了Ts就能保证线上不报错吗?

不能

type和interface的区别

功能上:
interface一般用于描述对象,type可以描述出任意类型
扩展上:
接口可以进行继承extend,type可以通过交叉类型聚合。
应用上:
class可以实现接口

interface Point { x: number; y: number; } 
class SomePoint implements Point { x = 1; y = 2; }

type不能

type PartialPoint = { x: number; } | { y: number; }; 
class SomePartialPoint implements PartialPoint { // Error x = 1; y = 2; }

声明上:
接口可以定义多次,会被自动合并为单个接口

interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };

type定义多次会直接报错

使用上:
优先使用interface,interface满足不了的话使用type

接口可以定义多次,会被自动合并为单个接口,

const 和 let 类型推导的区别

let a = 1 // a 为 number 类型 
let b = '2' // b 为 string 类型
const a = 1 // 则 a 的类型就为 1
const d = '2' // 则 a 的类型就是 '2'

如果想使用let生命字面量类型可以如下:

let a = 1 as const 则 a 的类型就为 1

Object和object的区别

Object包含原始类型和非原始类型,不能覆盖原型方法
object只能包含非原始类型,可以覆盖原型方法

let obj: object; 
obj = 1; // Error:Type 'number' is not assignable to type 'object'.
obj = true; // Error: Type 'boolean' is not assignable to type 'object'.
obj = 'a'; // Error: Type 'string' is not assignable to type 'object'.
obj = { toString() { return 123; } } 
console.log(obj.toString()) // 123
let obj: Object; // 或者 let obj = new Object();
obj = "hell oworld";
obj = 1;
obj = true;
obj = undefined; //Error:Type 'undefined' is not assignable to type 'Object'.
obj = {
  	// Type 'number' is not assignable to type 'string'
    toString() {
        return 123;
    }
}

非空断言!和 ?用哪个

建议使用可选链? 这段 ts 代码

const a = {
  c: undefined,
}

const b = a!.c!.d

会被编译为如下 js 代码

use strict;
const a = {
    cundefined,
};
const b = a.c.d;

会被编译为如下 js 代码

"use strict"

var _a$c

const a = {
  c: undefined,
}
const b =
  a === null || a === void 0
    ? void 0
    : (_a$c = a.c) === null || _a$c === void 0
    ? void 0
    : _a$c.d

Ts类型体操

。。。