even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、配置环境以及项目初始化

typescript的环境配置参考node中的nodemon配置typescript, 并且配置parcel-bundler,具体配置如下

{
  "name": "ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "parcel ./index.html",
    "watch": "nodemon --watch src/ -e ts,tsx --exec ts-node ./src/index.ts",
    "tsc-watch": "tsc --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "parcel-bundler": "^1.12.5",
    "typescript": "^4.4.3"
  }
}

 注意: 在index.html中,需要引入  <script src="./src/index.ts"></script>

2、ts函数的方法重载

ts的函数重载比较特殊, 和很多的其他后端语言的方法重载相比,多了不少的规则。学习函数重载,先要了解什么是函数签名,定义如下

函数签名: 函数签名=函数名称 + 函数参数 + 函数参数类型 + 返回值类型四者合成。在TS函数重载中,包含了实现签名和重载签名,实现签名是一种函数签名, 重载签名也是一种函数签名 

定义: 包含了以下规则的一组函数就是TS函数重载

  1、由一个实现签名 + 一个或多个重载签名合成;

  2、外部调用函数重载定义的函数时,只能调用重载签名, 不能调用实现签名,这看似矛盾的规则, 其实是TS的规定:实现签名下的函数体是给重载签名编写的,实现签名只是在定义时起到了统领所有重载签名的作用, 在执行调用时就看不到实现签名了;

  3:调用重载函数时,会根据位于新人参数来判断你调用的是哪一个函数;

  4、只有一个函数体,只有实现签名配备 了函数体,所有的重载签名都只有签名, 没有配备函数体。

例子:

//方法的重写
function getMessage(info: string): string
function getMessage(info: number): number
function getMessage(info: number | string): string | number {
  if (typeof info === 'number') return info + 1
  return info
}

//类的方法的重写
class Check {
  public sendMsg(msg: string): string
  public sendMsg(msg: Array<string>): string
  public sendMsg(msg: string | Array<string>): string {
    if (Array.isArray(msg)) return msg.join('')
    return msg
  }
}

 构造器的方法重载

type IData = { name: string; age: number }

class Test {
  private name: string
  private age: number

  constructor(name: string, age: number)
  constructor(info: IData)
  constructor(info: string | IData, age: number = 0) {
    if (typeof info === 'string') {
      this.name = info
      this.age = age
    } else {
      let { name, age } = info
      this.name = name
      this.age = age
    }
  }

  public log(): void {
    console.log(this.name, this.age)
  }
}

let first = new Test('aaa', 12)
let second = new Test({ name: 'aaa', age: 13 })
first.log()
second.log()

3、类型断言与类型转换

定义:把两种能有重叠关系 的数据类型进行相互转换的一种TS语法,把其中的一种数据类型转换成另外一种数据类型。类型断言和类型转换产生的效果一样,但语法格式不同。

格式: A数据类型的变量 as B数据类型。娄数据类型和B数据类型必须具有重叠关系

class People {
  public name!: string
  public age!: number
  public say() {
  }
}

class Stu extends People {
  public study() {}
}

//类型断言
let p = (new Stu() as People)   // 这时就不会有学生的方法
let s = new People() as Stu   // 这时断言成学生的类,就会有学生的方法

//类型转换
let p1 = new Stu();
<People>p1   // 这个使用的是类型转换,就不会联想到学生的方法

 注意:需要使用断言与转换时,有几种情况,情况一: 两者之间是互相继承关系; 情况二: 两个类之间的属性是互相重叠或都互为子集的关系; 情况三: 联合类型之间的转换

类型的非空断言

建议打开tsconfig.js中的 "strictNullChecks": true 配置

never类型的三种情况

//情况一
let test = (msg: number | string) => {
  if (typeof msg === 'number') {
    console.log(msg)
  } else if (typeof msg === 'string') {
    console.log(msg)
  } else {
    console.log(msg) //这里的msg是never类型,因为它无法到达
  }
}
//情况二
let test2 = (msg: string): never => {
  throw new Error(msg)
}
//情况三
let test3 = (): never => {
  while (true) {}
}

void 与never的区别:

1、前者可以被赋值为null(strictNullChecks为false时), undefined, 但never不能包含任何类型;

2、返回类型为void的函数还能正常执行, 但是返回never的函数无法正常执行

never是unknown的子集

type isNever = never extends unknown? true: false;  //显示的是true
type keys = keyof unknown  //显示的是never

4、装饰器

在使用类的装饰器前,需要在tsconfig中进行配置experimentalDecorators,将其值改为true, emitDecoratorMetadata的值为true

类装饰器: 在类声明之前声明, 用来监视,修改, 替换类定义

// 注意:这里接收的是类的构造器,与方法装饰器不一样的是方法装饰器接收的是一个实例化后的类, 这是实现的一个日志打印的功能
function
LoggerDecorator<T extends { new (...args: any): any }>(targetClass: T) { // class LoggerClass extends targetClass { // public constructor(...args: any) { // super(...args) // console.log('日志信息...targetClass') // } // } // return LoggerClass // 使用匿名类进行实现 return class extends targetClass { public constructor(...args: any) { super(...args) console.log('日志信息...targetClass') } } } @LoggerDecorator class Person { public constructor(private name: string, private age: number) {} public getInfo(): string { return `${this.name}, ${this.age}` } } let P = new Person('aaa', 12) console.log(P.getInfo())

属性装饰器以及方法装饰器:可以用来装饰方法,或者类里的属性

//装饰属性,target是表示类的一个实例, 第二个参数表示属性的名称
function property(target: any, propertyName: string) {
  let value = target[propertyName]
  const getter = () => value
  const setter = function (newVal: string) {
    //用这种方式的函数,this是指向实例
    value = newVal.toUpperCase()
  }
  if (delete target[propertyName]) {
    Object.defineProperty(target, propertyName, {
      get: getter,
      set: setter,
      configurable: true,
      enumerable: true,
    })
  }
}

//装饰方法, 第一个参数是实例化后的类, 第二个参数表示方法名,第三个参数表示该方法的属性
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  let oldMethods = descriptor.value
  descriptor.value = function (...rest: any[]) {  //使用这种写法,那么该this是指向实例化后的类
    console.log(this)
    return oldMethods.apply(this, rest)
  }
}

class Person {
  @property
  private name: string
  public constructor(name: string, private age: number) {
    this.name = name
  }
  @method
  public getInfo(): string {
    return `${this.name}, ${this.age}`
  }
}

let p = new Person('bbb', 12)
console.log(p.getInfo())

注意:如果修饰的是属性,那么只接收两个参数,第一个参数是实例化后的类,第二个参数是属性的名称,如果修饰的是方法,那么第三个参数是该方法的属性,如果装饰器需要传参数,那么需要返回一个function接收这些参数如下

function method(msg: string) {
  return function (target: any, property: string, descriptor: PropertyDescriptor) {
    let oldMethods = descriptor.value
    descriptor.value = function (...rest: any[]) {
      //使用这种写法,那么该this是指向实例化后的类
      console.log(this)
      return oldMethods.apply(this, rest) + msg
    }
  }
}

class Person {
  private name: string
  public constructor(name: string, private age: number) {
    this.name = name
  }
  @method('addInfo')
  public getInfo(): string {
    return `${this.name}, ${this.age}`
  }
}

let p = new Person('bbb', 12)
console.log(p.getInfo())

 注意:如果修饰的是静态的属性或者静态的方法,那么,第一个参数则不是实例化后的类,而是该类的构造函数

 参数装饰器: 用于装饰类里方法的参数

//接收三个参数,一个是实例化后的类,第二个是方法名,第三个是参数的顺序
function param(...rest: any[]) {
  console.log(rest)
}

class Person {
  private name: string
  public constructor(name: string, private age: number) {
    this.name = name
  }
  public getInfo(@param info: string): string {
    return `${this.name}, ${this.age}, ${info}`
  }
}

 注意:装饰器的执行顺序: a、类装饰器是最后执行,后写的类装饰器先执行; b、参数装饰器先于方法装饰器执行; c、属性装饰器与方法装饰器,谁在前面谁先执行, 所以总体的顺序是 属性装饰器 => 方法参数装饰器(一) => 方法装饰器(一) => 方法参数装饰器(二) => 方法装饰器(二)=>  ...  => 类装饰器

 5、知识点拾遗

泛型

class Person {}

//表示需要传入一个类,那么返回的是一个实例化的类
function test<T>(type: { new (): T }): T {
  return new type()
}

let p = test<Person>(Person)

//泛型在接口中的定义,其中T是表示全局的,U是表示局部的
interface ICheck<T> {
  sum(type: T): T
  test<U>(type: U): U
}

//在类在进行泛型的定义
class Check<U> {
  public constructor(prop: U) {}
}

//泛型继承
interface IWise {
  length: number
}
//这里可以继承一个接口也可以继承一个类
function logger<T extends IWise>(type: T): number {
  return type.length
}

类型

type aa = string | number
type bb = number | boolean
type cc = aa & bb   //这个时候cc的类型就是  number

interface IA {
  name: string
}

interface IB {
  age: number
}

type IC = IA & IB   // 这个时候IC的类型就是IA与IB的交集如下例子

let obj: IC = {
  name: 'yes',
  age: 12,
}

 但是如果遇到下面的情况,那么就需要求内部的交集

interface IA {
  name: string|number,
  age: number
}

interface IB {
  name: string,
  sex: string
}

type IC = IA & IB

let obj: IC = {
  name: 'aaa',  //这个时候name就是上面IA与IB的name的交集,则为string
  age: 1,
  sex: 'man'
}

6、类型守卫

定义:在语句的块级作用域【if语句内或条目运算符表达式内】缩小变量的一种类型推断的行为。

类型判断: typeof

属性或者方法或者函数判断: in

实例判断:instanceof

字面量相等判断: ==, ===, !=, !==

typeof的使用

let obj = {
  name: 'aaa',
  age: 12,
  fav: {
    sport: 'football',
    others: ['computer', 'singing'],
  },
}

let t: typeof obj //这个时候是把obj对象转成一个类型,相当于,{name: string, age: number, fav: {sport: string, other: Array<string>}}

 keyof的使用

type IA = ['aaa', 'bbb', 'ccc', 'ddd']
let ia: keyof IA = 0 //这个时候的keyof相当于下标0, 1, 2, 3

type IB = { name: 'aaa'; age: 12 }
let ib: keyof IB = 'age' //相当于IB类型里的所有key的子集

interface IInfo {
  name: string
  age: number
  fav: string
}

//相当于在IInfo里的所有属性加了?可选标识
type subInfo1 = {
  [key in keyof IInfo]?: IInfo[key] //注意:这里的key也可以用其他的单词代替
}

//该效果与上面的subInfo1是等效的
type subInfo2 = Partial<IInfo>

//typescript系统内置了Partial的声明,相当于如下的效果
// type Partial = {
//   [key in keyof IInfo]?: IInfo[key]
// }

 分布式有条件类型

interface A {
  name1: string
}

interface B {
  name2: string
}

interface AA {
  name3: string
}

interface BB {
  name4: string
}

//分布式有条件类型,如下,即T为裸类型的时候,那么类型会被推导出来
type Condition<T> = T extends AA ? A : B
let t: Condition<AA | B> = { name2: 'aaa' } // let t: A|B

//如果 type Condition<T> = {type: T} extends {type: AA}? A: B  //那么类型t将会变成  let t: B

排除与包含的使用

type Diff<T, U> = T extends U ? never : T
let R: Diff<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let R: never
//针对上面的声明typescript内置了一个方法Exclude, Exclude的声明相当于Diff的逻辑
let T: Exclude<'e' | 'f', 'e' | 'g' | 't'> //这个时候相当于  let T: 'f'

type Filter<T, U> = T extends U ? T : U
let F: Filter<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let F: "a"| "b"| "c"
//针对上面的声明typescript内置了一个方法Extract, Extract的声明相当Filter的逻辑
let E: Extract<'a' | 'b' | 'c', 'a' | 'b' | 'c' | 'd'> //相当于let E: "a"| "b"| "c"

其他内置类型一

// type NonNullable<T> = T extends null | undefined ? never : T
type A = NonNullable<'a' | undefined | null> //相当于 type A = 'a'

// 以函数的返回值作为类型  ReturnType
interface IFun {
  (): { name: string; age: number }
}

let func: IFun = () => {
  return {
    name: 'aaa',
    age: 12,
  }
}

type ReturnTypeCopy<T extends (...args: any) => any> = T extends (...args: any[]) => infer R ? R : any

let res: ReturnType<IFun> = {
  name: 'yes',
  age: 1,
}

//以函数的参数作为返回值 Parameters

interface IFunParam {
  (name: string, age: number): void
}

type ParametersCopy<T extends (...args: any) => any> = T extends (...args: infer R) => any ? R : never

let param: Parameters<IFunParam> = ['aaa', 12]

// 实例化的类型 InstanceType 以及构造函数的参数 ConstructorParameters

class Person {
  public constructor(private name: string, private age: number) {}
  public getInfo() {
    return { name: this.name, age: this.age }
  }
}
type ConstructorParametersCopy<T extends new (...args: any[]) => any> = T extends abstract new (...args: infer P) => any? P: never;
let consParam: ConstructorParameters<typeof Person> = ['name', 12]

type InstanceTypeCopy<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
let ins: InstanceType<typeof Person> = new Person('aaa', 12)

 infer的应用

// infer的应用案例
type Ttuple = [string, number, boolean]
type ElementOf<T extends Array<any>> = T extends Array<infer P> ? P : never // 这里的infer是表示数组里的内容

let test: ElementOf<Ttuple> // 返回的类型是 let test: string|number|boolean

//联合类型转交叉类型
type T1 = { name: string }
type T2 = { age: number }
type ToInterSection<T> = T extends { a: (x: infer P) => void; b: (x: infer P) => void } ? P : never
type T3 = ToInterSection<{ a: (x: T1) => void; b: (x: T2) => void }> // 返回的类型 type T3 = T1 & T2

 其他内置类型二

type TCompany = {
  name: string
  count: number
}

interface json {
  name: string
  age: number
  company: TCompany
}

// 纵向进行递归操作
type DeepPartial<T> = {
  [U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U]
}

let T: DeepPartial<json> = {
  name: 'aaa',
  company: {
    count: 12,
  },
}

//该接口等同于系统内置的Required的接口
type RequiredCopy<T> = {
  [key in keyof T]-?: T[key]
}

//该接口等同于系统内置的Readonly的接口
type ReadonlyCopy<T> = {
  readonly [key in keyof T]: T[key]
}

 注意:上面的RequiredCopy的写法,'-' 表示不可选  ‘+’表示可选的操作

 其他内置类型三

// Pick的用法 这里表示提取指定的属性作为接口
interface json {
  name: string
  age: number
  avatar: string
}
//注意: 这里的extends的作用在实际类型声明的时候,相当于in的作用
type PickCopy<T, K extends keyof T> = {
  [P in K]: T[P]
}
type PartJson = Pick<json, 'name' | 'age'> //相当于提取了两个属性作为自己类型的属性

// Omit的用法, 这里表示排除指定的属性作为接口
type PartJson1<T, K extends keyof T> = Omit<T, K>
let part: PartJson1<json, 'name'> = {
  age: 12,
  avatar: 'avatar',
}

type a = {
  name: string
  age: number
}

type b = {
  name: string
  sex: string
}
//获取两个对象的差异
type Diff<T extends object, K extends object> = Pick<T, Exclude<keyof T, keyof K>> &
  Pick<K, Exclude<keyof K, keyof T>>
//获取两个对象的相同部份
type Intersect<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>

type A = Intersect<a, b>
type B = Diff<a, b>

let bb: B = {
  age: 12,
  sex: 'man',
}

注意: 这里的extends的作用在实际类型声明的时候,相当于in的作用

 7、typescript的命名空间

在使用ts进行编程的时候,除了模块外,还有一个可以使用独立的作用域,那就是命名空间

export namespace A {
  const test = 'ok'  //注意:在命名空间中,如果没有使用export,那么外部是无法访问到该属性或方法的
  export const fun = (): string => {  //在外部可以被访问
    return 'are you ok???'
  }
}

export namespace B {
  export const fun = (): string => {
    return 'today is goods day'
  }
}

外部引用的时候

import { A, B } from './check'

console.log(A.fun())
console.log(B.fun())

8、类型声明

在typescript进行编译的时候,如果需要自动生成d.ts的文件,那么需要配置  "declaration": true

在typescript中,对类型进行声明,declare,但这个只是声明,而不是实现,这个方法可适用于对js项目转化成ts项目中使用

declare let str: string;
declare function func(str: string): void;
declare class Person {
    private name;
    private age;
    constructor(name: string, age: number);
    getInfo(): Record<string, string | number>;
}

 注意:如果需要扩展全局的方法,并且该文件里有类型导出,那么就需要进行全局类型的修饰如下

export const a = 'aaa'

//这个声明的这个方法就是全局的,因为上面的模块的导出,所以需要用global进行修饰
declare global {
  interface String {
    check(): void
  }
}

String.prototype.check = function () {
  return this.toString()
}

注意: 这个时候就会把check这个方法写入到String的原型方法中,以供使用

 9、配置项目访问路径

"baseUrl": "./src",
"paths": {
      "@app": [
              "./"
       ],
       "@app/*": [
              "./*"
        ]
}

这样添加这个配置后,那么在项目中访问就可以用@app来指定到./src目录下

 

posted on 2021-09-12 16:02  even_blogs  阅读(329)  评论(0编辑  收藏  举报