TS学习记录

103 阅读12分钟

概述

TypeScript 是一个由微软开发的开源编程语言,它是 JavaScript 的超集,增加了静态类型检查和其他一些语言特性,可以帮助开发人员在大型项目中更好地管理和维护代码。TypeScript 的语法和结构与 JavaScript 相似,因此已经成为前端开发和后端 Node.js 开发中的流行语言。

可以通过npm安装TypeScript:

npm install -g typescript

配置TypeScript

在项目根目录下创建一个名为tsconfig.json的配置文件,并将需要的选项添加到其中。例如,可以指定编译输出目录、目标ECMAScript版本、使用的模块系统等等。

基础语法

变量声明

TypeScript 支持使用 letconst 关键字来声明变量,这两个关键字与 JavaScript 中的用法相同。其中 let 声明的变量是可变的,const 声明的变量是不可变的(类似于 JavaScript 中的常量)。

let x: number = 10; 
const PI: number = 3.14;

TypeScript 可以自动推断变量的类型,也可以使用 :type 来显式指定变量的类型。

let x = 10; // 自动推断为 number 类型 
let y: string = "hello";

数据类型

TypeScript 支持 JavaScript 中的基本数据类型,如 numberstringbooleannullundefinedobjectsymbol,同时还支持 anyvoid 类型。

let x: number = 10; 
let name: string = "Tom"; 
let isDone: boolean = false;
let age: number | string = 20; // 联合类型,可以是 number 或 string 类型
let person: { name: string, age: number } = { name: "Tom", age: 20 }; // 对象类型 
let foo: any = "bar"; // 任意类型 
let bar: void = undefined; // 空类型,通常用于表示函数没有返回值

函数

TypeScript 支持 JavaScript 中的函数语法,并且增加了一些类型注解的功能。

function add(x: number, y: number): number {  
  return x + y;
} 
const sum = (x: number, y: number): number => x + y;

函数可以接受一个函数类型作为参数,并且可以使用 => 来表示返回值类型。

function sayHello(callback: (name: string) => void): void { 
  callback("Tom");
}
sayHello((name: string) => console.log(`Hello, ${name}!`));

数组

TypeScript 支持 JavaScript 中的数组,同时增加了一些类型注解的功能。

let numbers: number[] = [1, 2, 3];
let numbers: number|string[] = [1, 2, 3];
let numbers: [number|string] = [1, 2, 3];
// any类型数组
let list: any[] = ['test', 1, [],{a:1}]
// 数组泛型
let names: Array<string> = ["Tom", "Jerry"];
// 多维数组
let data:number[][] = [[1,2], [3,4]];
// 接口表示数组
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字。

元组

元组是一种特殊的数组类型,它可以存储不同类型的数据,并且数据的数量和类型是固定的。

let tom: [string, number] = ['Tom', 22];
tom[0] = 'Jerry'
/* 
    不能将类型“[string]”分配给类型“[string, number]”。
    源具有 1 个元素,但目标需要 2 个。ts(2322)
*/
// tom = ['aa']
tom=['bbb',18]
console.log(tom);

联合类型

联合类型使用 |来表示

function getLength(something: string | number): number | string {
    return typeof something === 'string' ? something.length : ('Invalid');

}

枚举类型

枚举类型 默认从0开始累加,若中间某个值有默认值,则前置的值不变,后续值累加

enum Sex {
    secret,
    man=90,
    female
}
const c: Sex = Sex.secret
console.log(c); // 0

交叉类型

使用&但需要注意的是

interface可以继承interface ****interface可以继承type ****type无法继承interface

// interface People {
//     age: number,
//     height: number
// }
// type Student={
//     sex: string,
// }
// // 交叉类型:接口interface和类型type都可以使用 & 来表示 
// const zhangsan = (detail: People & Student) => {
//     console.log(detail.height);
//     console.log(detail.age);
//     console.log(detail.sex);
// }
// zhangsan({ sex: '男', age: 18, height: 180 })

// 接口可以继承接口
interface Person{
  name:string
}
interface Student extends Person{
  score:number
}
const stu:Student = {
  name: 'xxx',
  score: 100
}



// 接口可以继承type
// type Person = {
//   name:string
// }
// interface Student extends Person{
//   score:number
// }
// const stu:Student = {
//   name: 'xxx',
//   score: 100
// }

类型断言

语法:  值 as 类型  或  <类型>值

value as string <string>value

类型断言是不具影响力的,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误

interface A {
  run: string
}

interface B {
  build: string
}

const fn = (type: A | B): string => {
  return (type as A).run
}
//可以使用类型断言来推断他传入的是A接口的值

顶级类型

any类型

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型,用来表示允许赋值为任意类型。

const an: any = 666;
const arr:any[]=[1,'str',true,null,undefined]

unknown类型

unknown「未知」,相比「任意」,「未知」强调,必须搞清楚我是谁以后才能用。

let foo: unknown = 1;	// 可以先注解一个“不知道”
foo = 'string';	// 也可以任意赋值,因为在使用前还不知道类型
// 但当你想用的时候,呵呵
foo.length;	// 类型“unknown”上不存在属性“length”
const bar: string = foo;	// 不能将类型“unknown”分配给类型“string”。
// 必须先搞清楚我是谁,通常借助于类型断言
(foo as string).length;	// 可以
const bar: string = (foo as string);	// 可以

内置对象

ECMAScript的内置对象

let b: Boolean = new Boolean(1)
console.log(b)
let n: Number = new Number(true)
console.log(n)
let s: String = new String('哔哩哔哩关注小满zs')
console.log(s)
let d: Date = new Date()
console.log(d)
let r: RegExp = /^1/
console.log(r)
let e: Error = new Error("error!")
console.log(e)

BOM/DOM的内置对象

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
    
});
//dom元素的映射表
interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "applet": HTMLAppletElement;
    "area": HTMLAreaElement;
    "article": HTMLElement;
    "aside": HTMLElement;
    "audio": HTMLAudioElement;
    "b": HTMLElement;
    "base": HTMLBaseElement;
    "bdi": HTMLElement;
    "bdo": HTMLElement;
    "blockquote": HTMLQuoteElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "canvas": HTMLCanvasElement;
    "caption": HTMLTableCaptionElement;
    "cite": HTMLElement;
    "code": HTMLElement;
    "col": HTMLTableColElement;
    "colgroup": HTMLTableColElement;
    "data": HTMLDataElement;
    "datalist": HTMLDataListElement;
    "dd": HTMLElement;
    "del": HTMLModElement;
    "details": HTMLDetailsElement;
    "dfn": HTMLElement;
    "dialog": HTMLDialogElement;
    "dir": HTMLDirectoryElement;
    "div": HTMLDivElement;
    "dl": HTMLDListElement;
    "dt": HTMLElement;
    "em": HTMLElement;
    "embed": HTMLEmbedElement;
    "fieldset": HTMLFieldSetElement;
    "figcaption": HTMLElement;
    "figure": HTMLElement;
    "font": HTMLFontElement;
    "footer": HTMLElement;
    "form": HTMLFormElement;
    "frame": HTMLFrameElement;
    "frameset": HTMLFrameSetElement;
    "h1": HTMLHeadingElement;
    "h2": HTMLHeadingElement;
    "h3": HTMLHeadingElement;
    "h4": HTMLHeadingElement;
    "h5": HTMLHeadingElement;
    "h6": HTMLHeadingElement;
    "head": HTMLHeadElement;
    "header": HTMLElement;
    "hgroup": HTMLElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "i": HTMLElement;
    "iframe": HTMLIFrameElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "ins": HTMLModElement;
    "kbd": HTMLElement;
    "label": HTMLLabelElement;
    "legend": HTMLLegendElement;
    "li": HTMLLIElement;
    "link": HTMLLinkElement;
    "main": HTMLElement;
    "map": HTMLMapElement;
    "mark": HTMLElement;
    "marquee": HTMLMarqueeElement;
    "menu": HTMLMenuElement;
    "meta": HTMLMetaElement;
    "meter": HTMLMeterElement;
    "nav": HTMLElement;
    "noscript": HTMLElement;
    "object": HTMLObjectElement;
    "ol": HTMLOListElement;
    "optgroup": HTMLOptGroupElement;
    "option": HTMLOptionElement;
    "output": HTMLOutputElement;
    "p": HTMLParagraphElement;
    "param": HTMLParamElement;
    "picture": HTMLPictureElement;
    "pre": HTMLPreElement;
    "progress": HTMLProgressElement;
    "q": HTMLQuoteElement;
    "rp": HTMLElement;
    "rt": HTMLElement;
    "ruby": HTMLElement;
    "s": HTMLElement;
    "samp": HTMLElement;
    "script": HTMLScriptElement;
    "section": HTMLElement;
    "select": HTMLSelectElement;
    "slot": HTMLSlotElement;
    "small": HTMLElement;
    "source": HTMLSourceElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "sub": HTMLElement;
    "summary": HTMLElement;
    "sup": HTMLElement;
    "table": HTMLTableElement;
    "tbody": HTMLTableSectionElement;
    "td": HTMLTableDataCellElement;
    "template": HTMLTemplateElement;
    "textarea": HTMLTextAreaElement;
    "tfoot": HTMLTableSectionElement;
    "th": HTMLTableHeaderCellElement;
    "thead": HTMLTableSectionElement;
    "time": HTMLTimeElement;
    "title": HTMLTitleElement;
    "tr": HTMLTableRowElement;
    "track": HTMLTrackElement;
    "u": HTMLElement;
    "ul": HTMLUListElement;
    "var": HTMLElement;
    "video": HTMLVideoElement;
    "wbr": HTMLElement;
}

函数类型

//注意,参数不能多传,也不能少传 必须按照约定的类型来
const fn = (name: string, age:number): string => {
    return name + age
}
fn('张三',18)

函数可选参数?

//通过?表示该参数为可选参数
const fn = (name: string, age?:number): string => {
    return name + age
}
fn('张三')

函数参数的默认值

const fn = (name: string = "我是默认值"): string => {
    return name
}
fn()

接口定义函数

//定义参数 num 和 num2  :后面定义返回值的类型
interface Add {
    (num:  number, num2: number): number
}
 
const fn: Add = (num: number, num2: number): number => {
    return num + num2
}
fn(5, 5)
 
 
interface User{
    name: string;
    age: number;
}
function getUserInfo(user: User): User {
  return user
}

定义剩余参数

const fn = (array:number[],...items:any[]):any[] => {
       console.log(array,items)
       return items
}
 
let a:number[] = [1,2,3]
 
fn(a,'4','5','6')

函数重载

function fn(params: number): void
 
function fn(params: string, params2: number): void
 
function fn(params: any, params2?: any): void {
 
    console.log(params)
 
    console.log(params2)
 
}
 
 
 
fn(123)
 
fn('123',456)

void

用于表示函数没有返回值

const fn1 = (): void => {
  
}
function fn2(): void {

}

never

用于表示函数内永远不会出现的值(死循环/抛出错误)

function loop(): never {
    throw Error('错误信息');
    while (true) {

    }
}

接口interface

一般用于定义对象,使对象满足一种约束,定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的

interface Person {
    name: string,
    age: number
}

重名接口

重名的是可以合并的

//重名interface  可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
//继承
interface A{
    name:string
}
 
interface B extends A{
    age:number
}
 
let obj:B = {
    age:18,
    name:"string"
}

可选属性

可选属性? 的含义是该属性可以不存在。这时仍然不允许添加未定义的属性:

interface Person2 { 
    name: string,
    age?:number
}

任意属性

使用[propName:string]:any

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集****

//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//应为我们定义了[propName: string]: any;
//允许添加新的任意属性
interface Person {
    b?:string,
    a:string,
    [propName: string]: any;
}
 
const person:Person  = {
    a:"213",
    c:"123"
}

只读属性readonly

//这样写是会报错的
//因为a是只读的不允许重新赋值
interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
}
 
const person: Person = {
    a: "213",
    c: "123"
}
person.a = 123

添加函数

interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
    cb:()=>void
}
 
const person: Person = {
    a: "213",
    c: "123",
    cb:()=>{
        console.log(123)
    }
}

类型别名type

type 关键字(可以给一个类型定义一个名字)多用于复合类型

与接口类似,接口可以继承type,但type无法继承接口

type Person = {
  name:string,
  age:number,
  sex?:string,
  [propName:sttring]:any
}
// 定义函数别名
type str = () => string
let s: str = () => "张三"
console.log(s);

泛型

定义类或者函数时,如果遇到类型不明确或执行时确定的时候就可以使用泛型

function fn<T>(a: T): T { 
    return a;
}
fn<string>('zhangsan')
// 泛型继承
function fn<T extends number>(a: T, b: T): number {
    return a + b
}
fn<number>(1, 2)

定义泛型接口

interface MyInter<T> {
   (arg: T): T
}
function fn<T>(arg: T): T {
   return arg
}
let result: MyInter<number> = fn
result(123)

对象字面量泛型

let foo: { <T>(arg: T): T }
 
foo = function <T>(arg:T):T {
   return arg
}
foo(123)

泛型约束

interface Len {
   length:number
}
function getLegnth<T extends Len>(arg:T) {
  return arg.length
}
getLegnth<string>('123')

使用keyof 约束对象

其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型

function prop<T, K extends keyof T>(obj: T, key: K) {
   return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a') 
prop(o, 'd') //此时就会报错发现找不到

泛型类

声明方法跟函数类似名称后面定义<类型>

class Sub<T>{
   attr: T[] = [];
   add (a:T):T[] {
      return [a]
   }
}
// 使用的时候确定类型new Sub<number>()
let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)
 
let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')

namespace命名空间

  1. 内部模块,主要用于组织代码,避免命名冲突。
  2. 命名空间内的类默认私有
  3. 通过 export 暴露
  4. 通过 namespace 关键字定义

嵌套命名空间

namespace a {
   export namespace b {
        export const fn = <T>(arg: T): T => {
            return arg
        }
    }
}
a.b.fn<number>(1)

抽离命名空间

export namespace V {
    export const a = 1
}
import { V } from './a.ts'
console.log(V)

简化命名空间

namespace A  {
    export namespace B {
        export const C = 1
    }
}
 
import X = A.B.C
 
console.log(X);

合并命名空间

namespace A{
  export const name:string='zhangsan'
}

namespace A{
  export const age:number=14
}
a.name // zhangsan
a.age //14

三斜线指令

三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。

三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。

/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。

三斜线引用告诉编译器在编译过程中要引入的额外的文件。

你也可以把它理解能import,它可以告诉编译器在编译过程中要引入的额外的文件

namespace A {
    export const fn = () => 'a'
}
namespace B {
    export const fn2 = () => 'b'
}
///<reference path="./a.ts">
///<reference path="./b.ts">


// 顶层引入之后可以直接使用a.ts中的内容和b.ts中的内容
console.log(A)

声明文件引入

例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。

仅当在你需要写一个d.ts文件时才使用这个指令。

///<reference types="node" />

如果你在配置文件 配置了noResolve 或者自身调用自身文件会报错

定义class

TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明

class Person{
  
  name:string;
  age:number=0;
  constructor(name:string,age:number){
    // 如果了定义了变量不用 也会报错 通常是给个默认值 或者 进行赋值
    this.name=name;
    this.age=age;
  }
  run(){
    
  }
}

类的修饰符

一共有三类public``private``protected

class Person{
  public name:string;
  private age:number;
  protected sex:string;
  constructor(name:string,age:number,sex:string){
    this.name=name;
    this.age=age;
    this.sex=sex;
  }
  run(){
    
  }
}

const zhangsan  = new Person('张三',18,'男')
zhangsan.name // 张三
zhangsan.age //无法访问private变量
zhangsan.sex //属性“sex”受保护,只能在类“Person”及其子类中访问。

static静态方法

class Person{
  public name:string;
  private age:number;
  protected sex:string;
  constructor(name:string,age:number,sex:string){
    this.name=name;
    this.age=age;
    this.sex=sex;
  }
 static run(){
    console.log(this.name)
  }
}

Person.run()

static静态属性

我们用static 定义的属性 不可以通过this 去访问 只能通过类名去调用

class Person {
    public name: string;
    private age: number;
    protected sex: string;
    static st: string = '我是静态属性'
    constructor(name: string, age: number, sex: string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}
console.log(Person.st); // 我是静态属性

如果两个函数都是static 静态的是可以通过this互相调用

class Person {
    public name: string;
    private age: number;
    protected sex: string;
    static st: string = '我是静态属性'
    constructor(name: string, age: number, sex: string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
  static run(){
    return this.aaa()
  }
  static aaa(){
    return 'sfasfdsaf'
  }
}
console.log(Person.run());

interface定义接口类

interface定义类使用关键字implements后面跟interface的名字多个用逗号隔开,继承还是用extends

 
interface PersonClass {
    get(type: boolean): boolean
}
 
interface PersonClass2{
    set():void,
    asd:string
}
 
class A {
    name: string
    constructor() {
        this.name = "123"
    }
}
 
class Person extends A implements PersonClass,PersonClass2 {
    asd: string
    constructor() {
        super()
        this.asd = '123'
    }
    get(type:boolean) {
        return type
    }
    set () {
 
    }
}

abstract抽象类

应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类

或者你也可以把他作为一个基类 => 通过继承一个派生类去实现基类的一些方法

abstract class A {
   name: string
   constructor(name: string) {
      this.name = name;
   }
   print(): string {
      return this.name
   }
 
   abstract getName(): string
}
 
class B extends A {
   constructor() {
      super('小满')
   }
   getName(): string {
      return this.name
   }
}
 
let b = new B();
 
console.log(b.getName());