TypeScript速成指南-ts简要介绍

2024-01-09 11:25:33

前言

本文根据ts官方文档(原版与冴羽的 JavaScript 博客翻译版)归类总结提取,大致描述了ts的常用使用方式,如若文中某些措辞不当,欢迎大家评论指正。

正文

概念理解

ts在js的基础上对元素属性等做了类型限制,它要求number类型的元素属性只能被number类型的值赋值,否则会报错;ts是静态类语言,可以做到声明即文档,当我们看到某个变量的类型,就可以大致知道这个变量即将获得的数据结构是如何的,让开发者对变量的具体值更清楚;

js是动态类语言,他没有具体规定某个变量值的类型,甚至不同变量类型之间会相互转换。

Ts的基本写法

ts的类型声明写法如下,类型名通常在变量名与“ :”后书写

//  变量名 : 类型名 等号 具体值 
let myName: string = "Alice";

?Ts的常见基础类型

原始类型:string number boolean bigInt?symbol

string:?表示字符串,比如 "Hello, world"

number :表示数字,比如 1

boolean:?表示布尔值,true 或 false

bigInt:?表示非常大的整数

symbol:?通过函数?Symbol(),我们可以创建一个全局唯一的引用

类型名?String?,Number?和?Boolean?(首字母大写)也是合法的,但它们是一些非常少见的特殊内置类型。Number是基于number进行进一步封装的ObjectNumber,是用来声明包装对象的类型

大小写的类型声明区别如下:

const numberUpCase: Number = new Number(1)
// 打印结果为:[Number: 1]
console.log(numberUpCase)

const numberLowerCase:number = 1
// 打印结果为:1
console.log(numberLowerCase)

bigint和symble的声明方式如下:


const oneHundred: bigint = BigInt(100);


const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
  // This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  // Can't ever happen
}

null?和?undefined

JavaScript 有两个原始类型的值,用于表示空缺或者未初始化(?null?、undefined?)

TypeScript 有两个对应的同名类型。它们的行为取决于是否打开了?strictNullChecks?选项。

strictNullChecks?关闭

null?或者?undefined,依然可以被正确的访问,或者被赋值给任意类型的属性。

strictNullChecks?打开

null?或者?undefined,需要在用它的方法或者属性之前,使用类型收窄先检查,就像用可选的属性之前,先检查一下 是否是?undefined

数组(Array)

有两种定义方式:number[]? 和 Array<number>

// 声明方式一 数组子项的类型[]
let testArray1:number[] = [1,2,3]
console.log(testArray1)

// 声明方式二 Array<数组子项的类型>
let testArray2:Array<number> = [1,2,3,4]
console.log(testArray2)

any

是一个万能类型,不做属性校验,所有属性都可以赋值上去,当你不确定数据是什么类型的时候可以用此类型,不能全部都使用,否则和js又有什么大的区别呢?

let obj: any = { x: 0 }
let str: any = '1234567'
let num: any = 1
let arr: any = []

void

void?表示一个函数并不会返回任何值,当函数并没有任何返回值,或者返回不了明确的值的时候,就应该用这种类型。

// The inferred return type is void
function noop():void {
  console.log('123')
}

// 打印结果为undefined
console.log(typeof noop()) 

unknown

unknown?类型可以表示任何值。有点类似于?any,但是更安全,因为对?unknown?类型的值做任何事情都是不合法的:

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
  // Object is of type 'unknown'.
}

never

never?类型表示一个值不会再被观察到 (observed)。

作为一个返回类型时,它表示这个函数会丢一个异常,或者会结束程序的执行。

function fail(msg: string): never {
  throw new Error(msg);
}

当 TypeScript 确定在联合类型中已经没有可能是其中的类型的时候,never?类型也会出现:

function fn(x: string | number) {
  if (typeof x === "string") {
    // do something
  } else if (typeof x === "number") {
    // do something else
  } else {
    x; // has type 'never'!
  }
}

Function

在 JavaScript,全局类型?Function?描述了?bindcallapply?等属性,以及其他所有的函数值。

它也有一个特殊的性质,就是?Function?类型的值总是可以被调用,结果会返回?any?类型:

function doSomething(f: Function) {
  f(1, 2, 3);
}

这是一个无类型函数调用 (untyped function call),这种调用最好被避免,因为它返回的是一个不安全的?any类型。

如果你准备接受一个黑盒的函数,但是又不打算调用它,() => void?会更安全一些。

?Ts特殊属性

可选属性(?)

?代表这个属性可能存在也可能不存在,一般放在属性赋值符号?冒号 的前面

function printName(obj: { first: string; last?: string }) {
  // 如果last没有被传值,则打印获取的是undefined,而不是ts的报错
  console.log(last)
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

联合类型(|)

一个联合类型是由两个或者更多类型组成的类型,表示值可能是这些类型中的任意一个。这其中每个类型都是联合类型的成员,成员间用 | 分隔

// 多类型间用 | 分隔
// 本示例表示传入参数的类型可能是number或者string中的任意一个

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

printId(101); // OK

printId("202"); // OK

printId({ myID: 22342 }); // Error 不能传入number与string以外的其他类型

使用联合类型的时候记得根据需求收窄判断每个具体类型,有些类型的属性不共用,比如array有join属性,number类型没有join属性,就不能统一写,否则会报错

function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    // Here: 'x' is 'string[]'
    console.log("Hello, " + x.join(" and "));
  } else {
    // Here: 'x' is 'string'
    console.log("Welcome lone traveler " + x);
  }
}

类型断言(as)

使用类型断言将其指定为一个更具体的类型

// 用as的方式
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
// 使用尖括号语法(注意不能在 .tsx 文件内使用)
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

TypeScript 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。这个规则可以阻止一些不可能的类型指定

const x = "hello" as number;
// err: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

双重断言,先断言为?any?(或者是?unknown),然后再断言为期望的类型

const a = (expr as any) as T;

非空断言操作符(后缀!)

一个有效的类型断言,表示你确定它的值不可能是?null?或者?undefined

从而告诉ts不再对此参数属性进行null和undefined的校验

function liveDangerously(x?: number | null) {
  // No error
  console.log(x!.toFixed());
}

交叉类型(&)

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
 
type ColorfulCircle = Colorful & Circle;

枚举(enum)

enum UserResponse {
  No = 0,
  Yes = 1,
}
 
function respond(recipient: string, message: UserResponse): void {
  // ...
}
 
respond("Princess Caroline", UserResponse.Yes);

模板字面量类型(`${}`)

模板字面量类型以字符串字面量类型为基础,可以通过联合类型扩展成多个字符串。

type World = "world";
 
type Greeting = `hello ${World}`;
// type Greeting = "hello world"

内置字符操作类型

Uppercase:把每个字符转为大写形式

Lowercase:把每个字符转为小写形式

Capitalize:把字符串的第一个字符转为大写形式

Uncapitalize:把字符串的第一个字符转换为小写形式

//把每个字符转为大写形式
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>        
// type ShoutyGreeting = "HELLO, WORLD"
 
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
// type MainID = "ID-MY_APP"

//把每个字符转为小写形式
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>       
// type QuietGreeting = "hello, world"
 
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">    
// type MainID = "id-my_app"

//把字符串的第一个字符转为大写形式
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// type Greeting = "Hello, world"

//把字符串的第一个字符转换为小写形式:
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; 

?readonly

字段可以添加一个?readonly?前缀修饰符,这会阻止在构造函数之外的赋值。

class Greeter {
  readonly name: string = "world";
 
  constructor(otherName?: string) {
    if (otherName !== undefined) {
      this.name = otherName;
    }
  }
 
  err() {
    this.name = "not ok";
		// Cannot assign to 'name' because it is a read-only property.
  }
}

const g = new Greeter();
g.name = "also not ok";
// Cannot assign to 'name' because it is a read-only property.

public

类成员默认的可见性为?public,一个?public?的成员可以在任何地方被获取

class Greeter {
  public greet() {
    console.log("hi!");
  }
}
const g = new Greeter();
g.greet();

protected

protected?成员仅仅对子类可见

class Greeter {
  public greet() {
    console.log("Hello, " + this.getName());
  }
  protected getName() {
    return "hi";
  }
}
 
class SpecialGreeter extends Greeter {
  public howdy() {
    // OK to access protected member here
    console.log("Howdy, " + this.getName());
  }
}
const g = new SpecialGreeter();
g.greet(); // OK
g.getName();

// Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.

受保护的成员也可以在子类中被公开,只要在子类方法里面是公开的状态

class Base {
  protected m = 10;
}
class Derived extends Base {
  // No modifier, so default is 'public'
  m = 15;
}
const d = new Derived();
console.log(d.m); // OK

private

private?不允许访问成员,即便是子类。

class Base {
  private x = 0;
}

const b = new Base();
// Can't access from outside the class
console.log(b.x);
// Property 'x' is private and only accessible within class 'Base'.

class Derived extends Base {
  showX() {
    // Can't access in subclasses
    console.log(this.x);
		// Property 'x' is private and only accessible within class 'Base'.
  }
}

keyof类型操作符

keyof对一个对象类型使用

keyof?操作符会返回该对象属性名组成的一个字符串或者数字字面量的联合。

type Point = { x: number; y: number };
type P = keyof Point;
// P 的类型就类似于 x|y

?typeof类型操作符

获取一个变量或者属性的类型

let s = "hello";
let n: typeof s;
// let n: string

const person = { name: "kevin", age: "18" }
type Kevin = typeof person;
// type Kevin = {
// 		name: string;
// 		age: string;
// }

function identity<Type>(arg: Type): Type {
  return arg;
}
type result = typeof identity;
// type result = <Type>(arg: Type) => Type

Ts的复合类型

函数(function)赋类型方式

参数类型注解
// 在获取的参数值后 加 :限制类型
function logName(name: string) {
  console.log("获取的名字是 " + name);
}
返回值类型注解
// 在整个方法后 添加 :限制类型,此示例表明函数返回的类型是number
// 如果没有返回值,则可限制为void
function getAge(): number {
  return 27;
}
函数重载
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.

对象类型

//你可以使用 , 或者 ; 分开属性,最后一个属性的分隔符加不加都行
let p: { x: number; y: number } = {x:12,y:14}
let t: { x: number, y: number } = {x:13,y:17}

//也可以使用接口的形式定义对象
interface Point {
  x: number;
  y: number;
}
let i: Point = {x:15,y:16}

//也可以使用别名来定义
type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

类型别名(type)

别名声明方式

类型别名是一个可以指代任意类型的名字,这个名字就代表你自定义的此种类型

type Point = {
  x: number;
  y: number;
};
type ID = number | string;

// 赋类型的时候直接用别名即可
let myPoint: Point = {x:1,y:2}
let myId: ID = 23
别名扩展方式
不可直接更改原类型

类型别名不可以添加新的属性,自从定义之后就不可改变

type a = number
type a = string //error 开始报错

// script.ts(1,6): error TS2300: Duplicate identifier 'a'.
// script.ts(2,6): error TS2300: Duplicate identifier 'a'.
?交集扩展类型
// Type
// 通过交集扩展类型
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;

接口(interfaces)

接口声明方式

接口声明(interface declaration)是命名对象类型的另一种方式

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {
  console.log(pt.x);
  console.log(pt.y);
} 
printCoord({ x: 100, y: 100 });
接口扩展方式

接口是可以扩展添加新的属性的

继承扩展类型
// Interface
// 通过继承扩展类型
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey

//接口继承可以添加新类型扩展,但是不可以对原类型进行更改
interface Bird extends Animal {
  name: boolean
}
//error TS2430: Interface 'Bird' incorrectly extends interface 'Animal'.
//Types of property 'name' are incompatible.
//Type 'boolean' is not assignable to type 'string'.

//可以使用& 扩展
type Bird = Animal & {
  name: boolean
}
已存在的接口添加新字段
// Interface
// 对一个已经存在的接口添加新的字段
interface testInf {
  name: string
}
interface testInf {
  age : number
}


function test(arg:testInf){
	console.log(arg)
}

// 打印结果为: { name: '青提柚柚', age: 18 }
test({name:'青提柚柚',age:18})

// err 如果传入不存在的属性 则报错
// error TS2345: Argument of type '{ name: string; age: number; phone: string; }' is not assignable to parameter of type 'testInf'.
// Object literal may only specify known properties, and 'phone' does not exist in type 'testInf'.
test({name:'青提柚柚',age:18,phone:'1234567'})

类(class)

类声明
class Point {
  x: number;
  y: number;
}
 
const pt = new Point();
pt.x = 0;
pt.y = 0;
类继承
implements

1.类也可以实现多个接口,比如?class C implements A, B {

2.implements?语句仅仅检查类是否按照接口类型实现,但它并不会改变类的类型或者方法的类型。

interface Pingable {
  ping(): void;
}
 
class Sonar implements Pingable {
  ping() {
    console.log("ping!");
  }
}
 
class Ball implements Pingable {
  // Class 'Ball' incorrectly implements interface 'Pingable'.
  // Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
  pong() {
    console.log("pong!");
  }
}
?extends

1.类可以?extend?一个基类。一个派生类有基类所有的属性和方法,还可以定义额外的成员

2.一个派生类可以覆写一个基类的字段或属性。你可以使用?super?语法访问基类的方法

3.TypeScript 强制要求派生类总是它的基类的子类型

class Animal {
  move() {
    console.log("Moving along!");
  }
  greet() {
    console.log("Hello, world!");
  }
}
 
class Dog extends Animal {
  woof(times: number) {
    for (let i = 0; i < times; i++) {
      console.log("woof!");
    }
  }
  
  // 覆写属性
  greet(name?: string) {
    if (name === undefined) {
      super.greet();
    } else {
      console.log(`Hello, ${name.toUpperCase()}`);
    }
  }
}
 
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);

d.greet();
d.greet("reader");

索引签名

类可以声明索引签名,它和对象类型的索引签名是一样的

class MyClass {
  [s: string]: boolean | ((s: string) => boolean);
 
  check(s: string) {
    return this[s] as boolean;
  }
}
const APP = ['TaoBao', 'Tmall', 'Alipay'] as const;
type app = typeof APP[number];
// type app = "TaoBao" | "Tmall" | "Alipay"

function getPhoto(app: app) {
  // ...
}
  
getPhoto('TaoBao'); // ok
getPhoto('whatever'); // not ok

?泛型对象类型(<>)

泛型对象类型 T 就像是给类型传入的一个参数,它相当于一个占位符,具体的类型是根据传入的参数的类型所决定的,写法一般是 <你设置的类型占位名>,它可以和其他类型组合,泛型的写法是多变的,这里以最简单的举例子,方便理解

interface Box<Type> {
  contents: Type;
}
let box: Box<string>;



interface Box<Type> {
  contents: Type;
}
interface Apple {
  // ....
}
// Same as '{ contents: Apple }'.
type AppleBox = Box<Apple>;


interface GenericIdentityFn {
  <Type>(arg: Type): Type;
}

稍复杂点的例子,使用原型属性推断和约束,构造函数和类实例的关系

class BeeKeeper {
  hasMask: boolean = true;
}
 
class ZooKeeper {
  nametag: string = "Mikle";
}
 
class Animal {
  numLegs: number = 4;
}
 
class Bee extends Animal {
  keeper: BeeKeeper = new BeeKeeper();
}
 
class Lion extends Animal {
  keeper: ZooKeeper = new ZooKeeper();
}
 
function createInstance<A extends Animal>(c: new () => A): A {
  return new c();
}
 
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

映射类型?

映射类型,就是使用了?PropertyKeys?联合类型的泛型,其中?PropertyKeys?多是通过?keyof?创建,然后循环遍历键名创建一个类型

// 这个例子中,OptionsFlags 会遍历 Type 所有的属性,然后设置为布尔类型
type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

Ts的特殊注意知识点

类初始化的顺序:

  • 基类字段初始化
  • 基类构造函数运行
  • 派生类字段初始化
  • 派生类构造函数运行
class Base {
  name = "base";
  constructor() {
    console.log("My name is " + this.name);
  }
}
 
class Derived extends Base {
  name = "derived";
}
 
// Prints "base", not "derived"
const d = new Derived();

?文章参考相关链接

TypeScript手书:TypeScript: Documentation - 面向编程初学者的 TypeScript

冴羽的 JavaScript 博客翻译版:TypeScript中文文档_入门进阶必备

文章来源:https://blog.csdn.net/qq_41437448/article/details/135416502
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。