TypeScript 学习之路

一、介绍

  1. 一种由微软开发的自由和开源的编程语言,简称 TS
  2. TypeScriptJavaScript 的超集,即包含 JavaScript 的所有元素,能运行 JavaScript 的代码,并扩展了 JavaScript 的语法。
  3. 相比于 JavaScript,它还增加了 静态类型模块接口类型注解 方面的功能,相对于 JavaScriptTypeScript 属于 强类型 语言,所以对于项目而言,会使代码更加规范,从而解决了大型项目代码的复杂性。 更易于大项目的开发
  4. TS 是不能被浏览器直接识别的,所以在编译的时候,TS 文件会先编译为 JS文件。

TypeScript 与 ES5、ES6+ 之间的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSsLFo1F-1661916600107)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3da14c79cea44a74a92811e19323737d~tplv-k3u1fbpfcp-zoom-1.image)]

TypeScript 与 JavaScript 的区别

在这里插入图片描述

TypeScriptJavaScript
JavaScript的超集,用于解决大型项目的复杂性一种脚本语言,用于创建动态网页
能在编译期间发现错误并纠正错误只能在运行时发现错误
是强类型语言,支持静态类型和动态类型是弱类型语言,没有静态类型
最终是被编译成JavaScript代码,使得浏览器可以理解直接在浏览器使用
支持模块、泛型和接口不支持模块、泛型和接口

二、安装编译环境

安装并查看版本

// 安装
npm install -g typescript
// 或者
yarn global add typescript

// 查看版本
tsc -v  

创建配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F0z53UIz-1661916600108)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d9b641b7d794fa1be770736a7ca26a6~tplv-k3u1fbpfcp-zoom-1.image)]

//  tsc --init

{ 
    "compilerOptions": { 
    /* Language and Environment */ 
    "target": "es6", 
    /* Modules */ 
    "rootDir": "./src", 
    /* Emit */ 
    "outDir": "./dist", 
    /* Type Checking */ 
    "strict": true, 
   } 
 }

创建一个ts文件

function greeter(person) {
    return "Hello, " + person;
}

let user = "Jane User";

document.body.innerHTML = greeter(user);

编译

tsc greeter.ts  // greeter.ts => greeter.js

三、数据类型

类型例子描述
number1 50 1.5任意数字
string‘hello world’ ‘你好’任意字符串
booleantrue false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值或undefined
never没有值不能是任何值
object{id:1, name:“pengyuyan”}任意的JS对象
array[1,2,3]任意js数组
tuple[4,5]元素,TS新增类型,固定长度数组
enumenum(A,B)枚举,TS中新增类型

基础类型

// number    string     boolean
var a: number = 1;  // 添加类型注释来显式指定变量的类型
var a2 = 2;  // // 不需要类型定义--'a2'推断为类型 'number'
let b: string = 'hello';  
//字符串模板
let b1:string=`${b}` // "hello"
const c: boolean = true;

// symbol
// bigint
let f: symbol = Symbol();  // Symbol是es6/es2015才出现的类型
let f1:symbol = Symbol();  // Symbol是es6/es2015才出现的类型
console.log(f === f1)  // false
let g: bigint = 10n   // bigint是es2020新增语法
null 和 undefined

默认情况下它们是所有类型的子类型,即可以赋值给任意类型

let a: string = 'hello'
a = null //right
a = undefined // right
// 这两个类型只有 自己 null 和 undefined 两个类型一旦赋值上,就不能在赋值给任何其他类型
let d: null = null;
let e: undefined = undefined;
let d: string = 'pengyuyan'  // error

当我们在 tsconfig.js 文件中设置 strictNullCheckstrue 时,就不能将 nullundefined 赋值给除 它们自身void 之外的任意类型了。

//  "strictNullChecks": true 

let b: string = 'hello'
b = null // error,不能将类型“null”分配给类型'string'。

let c: string | null = 'hi'
c = null  // right
c = undefined // error,不能将类型'undefined'分配给类型'string | null'。
可选参数 (“strictNullChecks”: true)
//  "strictNullChecks": true 

function f(x: number, y?: number){
    return x + (y || 0)
}
f(1, 2) // 3
f(1) // 1
f(1, undefined) // 1
f(1, null) // error,类型“null”的参数不能赋给类型“number | undefined”的参数。
可选属性 (“strictNullChecks”: true)
//  "strictNullChecks": true 

interface PositionInterface{
    x: number
    y?: number
}

let p: PositionInterface = {x: 10}
p.y = 'abc' // error,不能将类型“"abc"”分配给类型“number | undefined”。

p.y = null // error,不能将类型“null”分配给类型“number | undefined”。

p.y = undefined // right

引用类型

array

TS 中要求数组中的每一项必须是 同一个数据类型
语法:
变量名: 数组中数据的类型[ ] = 数组值
变量名: Array<数组中数据的类型> = 数组值

const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];
const arr3: Array<string> = ['1', '2']
const arr4: Array<number> = [1, 2, '3'] // error 必须是同一个数据类型

//如果想要是数字类型或字符串类型,需要使用 |
const arr5: Array<number | string> = [1, 2, '3']
tuple(元祖)
  1. 元祖可以理解成一个 任意类型并且长度有限的数组 。元组中,允许一个数组中保存多个类型的数据。
  2. *注意:数组中的值与元组类型必须:数量位置类型都要对应
const arr5: [string, number] = ['1', 2];
let arr6: [string, number];
arr6[0] = 'pengyuyan';
arr6[1] = 18;
arr6[0].slice(1);
arr6[1].toFixed(2);
元组越界

ts允许向元组中使用数组的push方法插入新元素(但不允许访问)

const arr7: [string, number] = ['pengyuyan', 2];
arr7.push(3); // 正常运行
console.log(arr7); //  正常运行 ['pengyuyan',2,3]
console.log(arr7[2]); //访问新插入的元素会报错
object
// 方式一:
let obj:object;
// 或
let obj:{};

// object 表示一个js对象
let a :object;

a = {
  name:"pengyuyan",
  age:18
};

// 方式二
let b:{};
b = {
  name:"pengyuyan",
  age:18
}

// 定义对象时,需要定义出对象中有哪些属性,每一个属性的值是什么类型,指定的属性的个数,多一个属性也不行,少一个属性也不行
const obj: { age: number, name: string } = { age: 18, name: 'pengyuyan' };

// 如果想指定属性是可选的(可有可无)
const obj:{
  name:string,
  age?:number
}
obj = {
  name:"pengyuyan"
}
function

声明的函数在调用的时候,参数个数 要和 声明时候的参数 个数 保持一致

//没有返回值的函数可以用void声明
// 参数类型定义
const f1 = (name:string,id:number): void => {
  console.log("我是没有返回值的箭头函数");
};

function f2(name:string,id:number):void{
  console.log("我是没有返回值的普通函数");
}

//有返回值的箭头函数声明是这样的
// 返回类型注释
const f3 = (): string => {
  return "pengyuyan=>"
};

//有返回值的普通函数声明是这样的

function f4():string{
   return "pengyuyan"
}


//函数表达式的双向限定
//上述f1其实只对=右侧做了限制,对左侧并没有
//完善一点,可以这样 => 用来表示函数的定义,左输入类型,需要用括号括起来,右输出类型

const f5:(name:string,id:number)=>void = (name:string,id:number): void => {
  console.log("我是没有返回值的箭头函数");
};

// 函数的可选参数
// 注意可选参数要在确定参数后
function f6(name:string,id?:number):string{
   return "pengyuyan"
}

//函数参数默认值
function f7(name:string,id:number=1):string{
   return `${name}--${age}`
}
//此时可选参数不必一定在确定参数后,但是调用有问题
function f8(name:string,sex?:string,age:number=1):string{
   return `${name}--${age}--${sex}`
}
console.log(f8('pengyuyan','male',18))  // "pengyuyan--18--male"

//剩余参数
function f9(...arr:number[]):number[]{
  return arr
}
console.log(f9(1,2,3,4,5)) // [1,2,3,4,5]

特殊类型

any

可以访问它的任何属性,可以将它分配给赋予 任意类型 的值

let a;  // 声明变量不赋值,等效于  let a: any;
// a 的类型不确定,ts会自动将其定义为隐式 any ,能够变成多种类型
a = 123;  // 未声明类型的变量虽然一开始被识别为 any 类型,但是经过赋值后,TS 会根据赋值类型来标识变量的类型
a = "pyy"
a = true
console.log("这是a: ", a)   // "这是a: ",  true 


// 定义的any类型,类型可以多次改变,可以给任意类型赋值, 也可以被任意类型赋值,还能赋值给其他类型的变量
let a1: any;
a1 = 123;
a1 = "pyy";
a1 = false;
a1 = a;
console.log("这是a1: ", a1)  // "这是a1: ",  true 

let b: string = "pyy";
b = a1;
console.log("这是b: ", b)  // "这是b: ",  true 

unknown

未知的类型TS中所有基础类型的父类型,所有基础类型都能赋值为 unknown类型。但 **unknown ** 是无法给 ** unknown **之外 的(包括 any)其他类型变量赋值的

// 接上文any的例子
let c1: unknown;
c1 = "wyz&pyy";
c1 = c;
console.log("这是c1: ", c1)   // "这是c1: ",  true 
c1 = b;
console.log("这是c1: ", c1)   // "这是c1: ",  true 
unknown类型直接赋值给其他变量的方法
typeof 进行类型判断
let num1: unknown = 666;
if(typeof num1 === "number") {
  let num2 = num1
}
类型断言
let num1: unknown = 666;
let num2 = num1 as number);
// 或
let num3 = <number>num1;

any 和 unkown 的区别

unknown类型会更加严格:在对 unknown类型的值执行大多数操作之前,不能直接赋值给其他变量,必须将这个 unknown类型的变量断言为具体的类型,才可以继续使用。
而在对 any类型的值执行操作之前,会绕过类型检查,直接可用

let foo: any = 123;
console.log(foo.msg); // 符合TS的语法
let value1: unknown = foo;   // OK
let value2: any = foo;      // OK
let value3: string = foo;   // OK


let bar: unknown = 456; // OK 
console.log(bar.msg); // Error
let value4: unknown = bar;   // OK
let value5: any = bar;      // OK
let value6: string = bar;   // Error

因为bar是一个未知类型(任何类型的数据都可以赋给 unknown 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unkown 类型的值也不能将值赋给 any 和 unkown 之外的类型变量

总结: any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量
never 类型

TS中使用 never类型来表示不应该存在的值的类型,例如:

  1. 一个抛出异常的函数
  2. 一个永远不会返回的函数的返回值类型

*注意never 类型是任何类型的子类型,可以赋值给 任意类型。但是没有类型是 never类型的子类型,即使是 any类型 也不能 赋值给 never 类型

const a:never;
a = 123 // error
a = (()=>{
   throw new Error('错误')
}) 
  const fn: ()=>never = () => {
      throw new Error('error')
  }
  const [n,setN] = React.useState<number>(1)
  const add: ()=>never = () => {
      while(true){ setN(i=>i+1) }
  }
Enum(枚举)

可以定义带名字的 常量

  1. 枚举的类型 只能stringnumber
  2. 定义的名称不能为 关键字
按枚举成员分类
数字枚举

*注意:

  1. 数字类型
  2. 如果有 默认值,会影响到后面的值
  3. 支持 反向映射
enum Direction {
    Up = 1, // 1
    Down, // 2
    Left, // 3
    Right // 4
}
enum Direction {
    Up, // 0
    Down = 3, // 3
    Left, // 4
    Right // 5
}
// 如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 
enum Direction {
    Up,  // 0
    Down, // 1
    Left, // 2
    Right // 3
}
// 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
const date: { id: number, name: string, days: Days } = { id: 1, name: '周三', days: Days.Wed }
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true

// 如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的,但建议尽量避免
字符串枚举

在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化
*注意:

  1. 必须要有 默认值
  2. 支持 反向映射
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
const a: Direction = Direction.Up  //a = UP
const b: Direction = Direction.Down   //b = DOWN
// 由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 
// 字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
异构枚举 (不建议)

数字 枚举与 字符串 枚举 混用不建议

反向映射

除了创建一个以 属性名 做为对象成员的对象之外,数字枚举成员(字符串枚举成员没有反向映射)还具有了 反向映射从枚举值到枚举名字

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
 
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
 
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
按声明方式
普通枚举
enum Color {Red, Green, Blue = "blue".length};
// 值由计算所得变为计算所得项 如,"blue".length 就是一个计算所得项
enum Color {Red = "red".length, Green, Blue};
// 上述代码会报错,因为Red是计算项,而Green紧接其后却无法获取初始值.
// 根据官方定义,不带初始化器的枚举要么被放在第一的位置,要么被放在使用了数字常量或其它常量初始化了的枚举后面。
常量枚举 (通过const enum 定义的枚举)
const enum Directions {
    Up,
    Down,
    Left,
    Right
}
 
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
外部枚举

用来描述 已经存在的枚举类型,当前环境中 已经存在的对象 的,这个对象可以存在 任意 的地方,但是一定是 已声明 的。
*注意:不支持 反向映射
参考:https://stackoverflow.com/questions/28818849/how-do-the-different-enum-variants-work-in-typescript

declare enum Enum {
    A = 1,
    B,
    C = 2
}
使用枚举
  1. 通过 枚举的属性 来访问 枚举成员
  2. 通过 枚举的名字 来访问 枚举类型
enum Response {
    No = 0,
    Yes = 1,
}

function respond(recipient: string, message: Response): void {
    // ...
}

respond("Princess Caroline", Response.Yes)
void
  1. 可以用来声明变量,但只能作用在 undefined 身上,null 也不可以。只有 tsconfig.jsonstrictNullChecks 属性设置为 false 时,null 才能赋值给 void
  2. 一般用于当一个函数 没有返回值时,TS 会默认他的返回值是 void 类型
const a = ():void => {} // 等价于 const a = () => {}
const b = ():void => { return 1 }  // error
const c = ():void => { return '2' } // error
const d = ():void => { return true } // error
const e = ():void => { return  } // ok
const f = ():void => { return undefined } //ok 
const g:void = 1   // error
const h:void = undefined // error

其他类型

联合类型 |

由两个或多个其他类型组成的类型,表示可能是这些类 型中的任何一种的值。我们将这些类型中的每一种称为联合类型的成员。

function printId(id: number | string) { 
   console.log("Your ID is: " + id); 
}
printId(101); // ok
printId("202"); // ok
printId({ myID: 22342 });  // error

在联合类型中,unknown 类型会吸收 任何 类型。联合类型中有 unknown,那么最终得到的都是 unknown 类型

type a = unknown | null;       // unknown
type b = unknown | undefined;  // unknown
type c = unknown | string;     // unknown
type d = unknown | number[];   // unknown

// 如果至少一种组成类型是 any,联合类型会相当于 any
type e = unknown | any;  // any   
基础类型联合
let a: string | number;
a = 1; //ok 
a= "a"//ok
对象类型联合

只能访问联合中 所有共同成员

interface Women{
  age: number,
  sex: string,
  cry(): void
}
interface Man{
  age: number,
  sex: string,
}
declare function People(): Women | Man;
let people = People();
people.age = 18; // ok
people.cry();// error 非共同成员
交叉类型 &

多种类型的集合。每种类型都可以赋值给 unknown 类型,所以在交叉类型中包含 unknown 不会改变结果

type u1 = unknown & null;       // null
type u2 = unknown & undefined;  // undefined
type u3 = unknown & string;     // string
type u4 = unknown & number[];   // number[]
type u5 = unknown & any;        // any
interface People {
  age: number
}
interface Man{
  sex: string
}
const pengyuyan = (man: People & Man) => {
  console.log(man.age) // 18
  console.log(man.sex) // male
}
pengyuyan({age: 18,sex: 'male'})
同名基础属性合并
type obj = { a: string, c: number }
type obj2 = { b: number, c: string }
type allObj = obj & obj2

const Info: allObj = {
  a: 'hello world',
  b: 7,
  // c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后 c 的类型为 never
  c: 1, // Type 'number' is not assignable to type 'never'   c: never
  c: 'hello', // Type 'string' is not assignable to type 'never'   c: never
}
同名非基础属性合并
interface obj3 { a: number }
interface obj4 { b: string }

interface A {
  x: obj3
}
interface B {
  x: obj4
}
type obj5 = A & B

const Info: obj5 = {
  x: {
  a: 1,
  b: 'hello'
  }
}
console.log(Info) // { x: { "a": 1, "b": "hello" }}

四、interface 和 type 的区别

interface
  1. 使用 interface 可以定义一个 接口 类型,能 合并 多个类型声明至 一个类型声明。
  2. 接口声明只存在于 编译 阶段,在编译后生成的 JS 代码中不包含任何接口代码
interface obj1 {
	a: number
  b: string
}

interface fn1 {
	() => void
}
type

type 除了能声明 对象函数,还可以为 基础类型 声明 别名

type A = number; // 基本类型
// type:声明类型别名的关键字
// A:类型别名的名称
// Type:类型别名关联的具体类型

let num: test = 10;
type Ojb = {name:string} // 对象
type fun = ()=>string  // 函数
type Tdata = [number,string] // 元组
type numOrStr = number | string  // 联合类型
不同点
  1. type 在声明类型别名后,将 别名类型 关联了起来,也就是说类型别名不会创建出一种新的类型,只是给已有类型命名并直接引用。而 interface 是定义了一个 接口 类型。
  2. type 表示 非对象 类型, 而 interface 表示 对象 类型。
  3. interface 可以 继承 其他的 接口对象 类型, 严格来说 type 支持 继承
  4. interface 可以 声明合并,而 type 不可以,这也意味着我们可以通过声明合并的方式给 interface 定义的类型进行属性扩展。
  5. type 可以通过 typeof 来获取实例的类型从而进行赋值操作
相同点

都可以用来定义 对象 或者 函数 的结构,而严谨的来说,type引用,而 interface定义

五、Class(类)

基本方法

在基本方法中有:静态属性静态方法成员属性成员方法构造器getset 方法

// 类定义方式如下
class obj{
    // 类作用域
    static myName: string = "pengyuyan"  // 静态属性
    name1:string = "男"
    engine:string
    engine3!:string //*在成员属性中,如果不给默认值,并且不使用是会报错的 。不设置默认值的时候加 !  就不会报错 engine3!:string
    // 构造函数
    constructor(engine:string) {
        this.engine = engine
    }
    // 静态方法
    static staticFn = ()=>{
        return '静态方法'
    }
    // 成员方法
    fn2 = () =>{
        return '成员方法'
    }
    // get方法
    get engine2(){
        return this.engine
    }
    // set方法
    set engine2(engine2){
        this.engine = engine2
    }
}
const setObj = new obj('hello')
console.log(obj.myName) //  "pengyuyan"
console.log(obj.staticFn()) // "静态方法"
console.log(setObj.fn2()) // "成员方法"
console.log(setObj.engine2)  // "hello"
console.log(setObj.engine)  // "hello"
// *set方法和get方法时的错误提示:
// error TS1056: Accessors are only available when targeting ECMAScript 5 and higher
// 解决方法:需要编译到 es5 及更高版本时可用

tsc 项目名称  -t es5
tsc Class(类).ts  -t es5
// 类定义方式如下
var obj = /** @class */ (function () {
    // 构造函数
    function obj(engine) {
        // 成员属性
        this.name1 = "男";
        // 成员方法
        this.fn2 = function () {
            return '成员方法';
        };
        this.engine = engine;
    }
    Object.defineProperty(obj.prototype, "engine2", {
        // get方法
        get: function () {
            return this.engine;
        },
        // set方法
        set: function (engine2) {
            this.engine = engine2;
        },
        enumerable: false,
        configurable: true
    });
    // 类作用域
    obj.myName = "pengyuyan"; // 静态属性
    // 静态方法
    obj.staticFn = function () {
        return '静态方法';
    };
    return obj;
}());
var setObj = new obj('hello');
console.log(obj.myName); //  "pengyuyan"
console.log(obj.staticFn()); // "静态方法"
console.log(setObj.fn2()); // "成员方法"
console.log(setObj.engine2); // "hello"
console.log(setObj.engine); // "hello"

只读属性 readonly

  1. 只能在 构造函数 中初始化,并且在 TS 中,只允许将 interfacetypeclass上的属性标识为 readonly
  2. readonly 实际上只是在 编译阶段 进行代码检查。
  3. readonly 修饰的词只能在 constructor 阶段修改,其他时刻不允许修改。
class Person {
    public readonly name:string;  // 字符串 只读
    name2:string

    constructor(name:string){
        this.name = name
        this.name2 = name
    }
    setName(name:string) {
        this.name = name // Cannot assign to 'name' because it is a read-only property.
        this.name2 = name; // ok
    }
}
const name3 = new Person('pengyuyan')
console.log(name3)     // Person: {"name": "pengyuyan", "name2": "pengyuyan"}
console.log(name3.name2)  // pengyuyan
console.log(name3.name)  // pengyuyan

继承 extends

  1. 继承之后,子类会拥有父类的一切 属性方法
  2. 子类也可以自己定义一些方法,如 getTel() 方法
  3. 子类也可以写与父类相同的方法,这样执行方法的时候会执行子类的方法,叫做 方法重写( Child子类中 重写了sayName() 方法)
// 父类
class Person {
  name: string
  age: number

  constructor(name: string, age:number){
    this.name = name
    this.age = age
  }

  getName(){
    console.log(`name是:${this.name}`)
    return this.name
  }

  setName(name: string){
    console.log(`设置name为:${name}`)
    this.name = name
  }
  sayName(){
    console.log(`哈哈哈,我是${this.name}`)
  }
 }

// 子类
class Child extends Person {
  tel: number
  // 如果在子类中写了构造函数,就必须调用父类的构造函数
  constructor(name: string, age: number, tel:number){
   super(name, age)  // super用在子类中,表示当前的父类
      this.tel = tel
   }
  // 子类也可以自己定义一些方法
  getTel(){
    return this.tel
  }
  sayName(){
    // 类的方法中super就表示父类,可以通过super.(父类的方法)调用父类的方法
    super.sayName() 
    console.log("Child")
}
}

let res = new Child("pengyuyan", 18 , 133123456789)
res.setName('pengyuyan') // 设置name为:pengyuyan
console.log(res) // Child {."name": "pengyuyan", "age": 18, "tel": 133123456789 }
console.log(res.age) // 18
console.log(res.getTel()) // 133123456789
res.sayName()
res.getName()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAZwCqZA-1661916600109)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/feff259796cb47029929efa7841c5e26~tplv-k3u1fbpfcp-zoom-1.image)]

修饰符

  1. public:类中、子类内的任何地方、外部 **都能调用 **
  2. protected:类中、子类内的任何地方都能调用,但 外部不能调用
  3. private:类中可以调用,子类内的任何地方、外部 均不可调用
    class Person {
      public name: string   // 类中、子类内的任何地方、外部 都能调用  
      protected age: number  // 类中、子类内的任何地方都能调用,但外部不能调用 
      private tel: number   // 类中可以调用,子类内的任何地方、外部都不可调用

      constructor(name: string, age:number, tel: number){
        this.name = name
        this.age = age
        this.tel = tel
      }
    }

    class Child extends Person {
      constructor(name: string, age: number, tel: number) {
        super(name, age, tel);
      }

      getName(){
        console.log(`我的name是${this.name},年龄${this.age}`) // "我的name是pengyuyan,年龄18" 
        console.log(`电话是${this.tel}`) // 类中可以调用,子类内的任何地方、外部都不可调用
        console.log(`年龄是${this.age}`)  // 类中可以调用  "年龄是18"   
      }
    }


    const res = new Child('pengyuyan', 18, 133123456789)
    console.log(res.name) // Domesy
    console.log(res.age) // 类中、子类内的任何地方都能调用,但外部不能调用 
    console.log(res.tel) // 类中可以调用,子类内的任何地方、外部都不可调用
    res.getName()

重写和重载

重写

继承父类之后 重写父类 的方法

class Person{
  setName(name: string){
    return `我的名字是${name}`
  }
}

class Child extends Person{
  setName(name: string){
    return `我的名字是${name}`
  }
}

const newInfo = new Child()
console.log(newInfo.setName('pengyuyan')) // "我的名字是pengyuyan" 
重载
class double{
  setNum(num: string);
  setNum(num: number);
  setNum(num:string | number){
    if(typeof num === 'string'){
      console.log(num + num)
    }else{
      console.log(num*2)
   }
  };
}

const res = new double()
res.setNum('1') // "11" 
res.setNum(1) // 2

abstract

abstrac 关键字来声明一个 抽象类,抽象类中的方法声明的方法叫做 抽象方法。抽象类 不能被直接实例化,只能 被子类继承,而且需要重新实现抽象类中的抽象方法。abstract 还可以修饰 属性存取器getset

    abstract class Person {
      constructor(public str: string){}
      // 抽象类中的方法也必须是抽象方法
      abstract numMethod(id: number) :void;
    }

    class Child extends Person {
      constructor(str: string) {
        super(str);
        this.str = str
      }
      // 抽象类中的函数
      numMethod(id: number): void {
        console.log(`str 是${this.str},id 是${id}`);
      }
    }

    let a = new Person("pengyuyan") // Cannot create an instance of an abstract class
    let b = new Child("pengyuyan");

    b.numMethod(7) // str 是pengyuyan,id 是7

六、断言

类型断言

可以手动指定一个值的类型,不需要 ts 去判断。
*注意:在 tsx 中必须使用 值 as 类型 的语法。

// 值 as 类型   
let str: any = 'pengyuyan';
let num: number = (str as string).length;

// <类型>值
let str2:any = 'pengyuyan'
let num2: number = (<string>str2).length; // react中会报错 
将任何一个类型断言为 any ( 不能滥用 as any)
let obj:object = {};
obj.num = 1;   // error  obj并没有num属性,所以就需要将 obj 断言为any
// 改为
(obj as any).num = 1;  
// *注意:不能滥用 as any
将any断言为一个具体的类型
let obj:object = {};
function fn1(key: string): any {
    return (obj as any).name = key;
}
// fn1()方法执行以后返回的是any类型

interface Animal  {
    name:string;
    fn2():void;
}
let a = fn1("pengyuyan") as Animal;
// 将其断言为Animal类型
将一个联合类型断言为其中一个类型

*注意:类型断言只是欺骗编译器,让编译器可以编译通过,但是如果强制类型转换,在 执行 的过程中会报错

interface obj1 {
    name: string;
    fn1(): void;
}
interface obj2 {
    name: string;
    fn2(): void;
}

function isObj(obj3: obj1 | obj2):string {
    return obj3.name   // 只能访问联合属性中共有的属性和方法
    
    // 断言,强制类型转换会报错
     return (typeof (obj3 as obj2).fn2) === 'function')
}

const obj4:obj1 = {
    name:"obj1",
    fn1(){
        console.log("fn1xxxxx");
    }
}
function fn3(animal:obj1|obj2){
    (animal as obj2).fn2();
}
fn3(obj4);  // animal.fn2 is not a function 
将一个父类断言为更加具体的子类
//类有继承关系
class Father extends Error {  //抽象的父类 Error,这样这个函数就能接受 Error 或它的子类作为参数了
  num1: number = 1;  
}

class Child extends Error {
  num2: number = 2;  
}

function fnType (error: Error) {
// 父类 Error 中没有 num1 属性,所以需要使用类型断言获取 
  if (typeof (error as Father).num1 === 'number') {
    return true;      
  }  
  return false;
}

非空断言

在上下文中当类型检查器 无法断定类型 时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是 非 null非 undefined 类型

// 忽略 undefined 和 null 类型
function fn(name: string | undefined | null) {
  const youName: string = name; // Error
  const myName: string = name!; // right
}

// 调用方法时忽略 undefined  类型
let myName:string = 'pengyuyan'
console.log(myName.trim())  // 为undefined时候报错
// 改为
console.log(myName!.trim())

类型断言 和 类型转换的区别

function toBoolean(something: any): boolean {
    return something as boolean;
}
toBoolean(1);
// 返回值为 1
而类型转换则会影响编译结果:
 
function toBoolean(something: any): boolean {
    return Boolean(something);
}
toBoolean(1);
// 返回值为 true

七、类型保护

function f(sn: string | null): string{
    if(sn === null){
        return 'null'
    } else {
        return sn
    }
}
// #或者 使用运算符
function f(sn: string | null): string{
   return sn || 'null'
}

interface Bird {
  fly: boolean;
  sing: () => {};
}
interface Dog {
  fly: boolean;
  bark: () => {};
}

function trainAnimal(animal: Bird | Dog){
  if(animal.fly){
    (animal as Bird).sing()
  }else {
    (animal as Dog).bark()
  }
}

typeof

用来确定 变量的类型
NumberStringBigintSymbolUndefinedFunctionBoolean
除此以外,typeof 只返回 object

const StudentId = (data: number | string | undefined ) => {
   if(typeof data === "string"){
      console.log(`我是字符串:${data}`)
   }

   if(typeof data === "number"){
       console.log(`我是数字:${data}`)
   }

   if(typeof data === "undefined"){
       console.log(data)
   }
   if(typeof data !== "object"){
       console.log('我不是object')
   }
  }
StudentId('pengyuyan') // 我是字符串:pengyuyan
StudentId(100) // 我是数字:100
StudentId(undefined) // undefined
function add(first: string | number, second: string | number){
  if(typeof first === 'string' || second === 'string'){
    return `${first}${second}`
  }else {
    return false
  }
}
console.log(add(1,'2'))  // false
instanceof

检查一个值是否是 给定构造函数 的实例

class NumberObj{
  count: number = 1
}
class NumberSecondObj{
  count: number = 1
}
function addSecond (first : object | NumberObj, second: object | NumberSecondObj){
  if(first instanceof NumberObj && second instanceof NumberSecondObj){
    return first.count + second.count
  }
  return 0
}
console.log(addSecond(new NumberObj(),new NumberSecondObj()))  // 2
in
interface Bird {
  fly: boolean;
  sing: () => {};
}
interface Dog {
  fly: boolean;
  bark: () => {};
}
function trainAnimalSecond(animal: Bird | Dog){
  if('sing' in animal){
    animal.sing()
  }else {
    animal.bark()
  }
}
console.log({fly:true,sing:() => {}})

接口

在面向对象语言中表示 行为抽象,也可以用来描述 对象的形状
使用 interface 关键字来定义接口

对象的形状

接口可以用来描述 对象,包括以下数据:可读属性只读属性任意属性

  1. 可读属性:当我们定义一个接口时,我们的属性可能 不全都要,这是就需要 **? ** 来解决
  2. 只读属性:用 readonly 修饰的属性为 只读属性,意思是指 允许定义,不允许之后进行更改
  3. 任意属性:可以用作 没有定义,也可以使用,比如 [data: string]: any 。比如说我们对组件进行封装,而封装的那个组件并没有导出对应的类型,然而又想让他不报错,这时就可以使用 任意属性
// 使用 interface 关键字来定义接口
interface Info {
    name: string;
    age: number;
    bool: boolean;
    realAge?: number; // 可选属性、代表属性可用或不用
    readonly readType: string; //只读属性
    [anyType: string]: any //任意属性
}
let res: Info = {
    name: 'pengyuyan',
    age: 18,
    bool: true,
    realAge:20, // 可选
    readType: 'peng',  // 只读
    anyType: 2 // 任意
}

res.readType = 'Bai' // error, 只读属性不可更改

继承

interface Parent {
    name: string
}

interface Child extends Parent{
    id: number
}

const info: Child = {
    name: 'pengyuyan',
    id: 0
}

泛型

在定义 函数接口 的时候,不预先指定具体的类型,而在 使用的时候再指定类型 的一种特性,使 函数接口 更加通用

在函数中使用
// T:数据类型,也可以换成任意字符串。(占位符,<A>|<B> 也可以)
function test <T> (arg:T):T{
  console.log(arg);
  return arg;
}
test<number>(111);// 返回值是number类型的 111
test<string | boolean>('hahaha')//返回值是string类型的 hahaha
test<string | boolean>(true);//返回值是布尔类型的 true
在接口中使用
// 定义方法
interface Search {
  <T,Y>(name:T,age:Y):T
}
//  <泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量
let fn:Search = function <T, Y>(name: T, id:Y):T {
  console.log(name, id)
  return name;
}
fn('li',11);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型
在类中使用
class Animal<T> {
  name:T;
  constructor(name: T){
  this.name = name;
 }
 action<T>(say:T) {
   console.log(say)
 }
}
let cat = new Animal('cat');
cat.action('mimi')
泛型约束
interface isArray {
   id: number
}

function logIndex<T extends isArray>(arg: T): void {
  for (let i = 0; i < arg.length; ++i) {
    console.log(arg[i])
  }
}

let arr = [1, 2, 3, 4]
logIndex<number>(arr) // error
logIndex<number[]>(arr) // ok
logIndex(arr) // ok
泛型工具类
Partial

将某个类型中的属性全部改为 可选项 ?

// 语法 
type Partial<T> = {
    [P in keyof T]?: T[P];   // keyof,即 索引类型查询操作符,我们可以将 keyof 作用于泛型 T 上来获取泛型 T 上的所有 public 属性名构成的 联合类型
};

interface User {
    name: string,
    age: number
}

type PartialUser = Partial<User>
// 相当于: type PartialUser = { name?: string; age?: number;}
Required

将所有属性改为 必选 的,与 Partial 相反

// 语法 
type Require<T> = {
    [p in keyof T]-?: T[P]
}

interface Props {
    name: string,
    age: number,
    isMale?: boolean
}

const info: Props = {
    name: 'pengyuyan',
    age: 18
}

const info1: Required<Props> = { 
    name: 'pengyuyan',
    age: 18,
    isMale: true
}
Record

将给定的 对象属性名 类型 和 对象属性 类型,创建一个 新的对象类型

// 语法 
type Record<K extends keyof any, T> = {
  [P in K]: T;
};


type a = 'x' | 'y';
type b = number;
type c = Record<K, T>; // {x:number; y:number}} 
const d: c = { x: 0, y: 0 }


interface PageInfo {
  title: string
}
type Info = 'a'|'b'|'c';
const e: Record<Info, RecordInfo> = {
  a: { title: "aaa" },
  b: { title: "bbb" },
  c: { title: "ccc" },
};
Readonly

可以构造一个 新的类型, 并将类型参数 T 中的 所有属性 变成 只读属性

// 语法 
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

interface Info {
    name: string
    age: number
}
let readInfo: Readonly<Info> = {
    name: 'pengyuyan',
    age: 18
}

readInfo.age = 19 //error 无法分配到 "age", 是只读属性

// 或者

type T = Readonly<Info>; // {name:string; age:number}}
const a: T = { name: 'pengyuyan', age: 18 }
// a.name = 'wuyanzu'  无法分配到 "name" ,因为它是只读属性
// a.age = 20  无法分配到 "age" ,因为它是只读属性
Pick

从一个 复合类型 中,取出几个想要的 类型的组合

// 语法
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

interface Todo {
  name: string
  description: string
  completed: boolean
}

type TodoPreview = Pick<Todo, 'name' | 'completed'>
// 相当于: type TodoPreview = { name: string; completed: boolean; }

const todo: TodoPreview = {
    name: 'pengyuyan',
    completed: false,
}
Exclude

某个类型中 属于另一个类型的属性** 移除

// 语法  
type Exclude<T, U> = T extends U ? never : T;

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
const t:T0 ='b';
ReturnType
获取 函数 T 的返回类型
// 语法 
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
NonNullable

从类型 T 中剔除 nullundefined 类型,并构建一个 新的类型

// 语法 
type NonNullable<T> = T extends null | undefined ? never : T;

type T0 = NonNullable<string | number | null | undefined>; // string | number

Extract
和 Exclude 类型互补, 它能从类型  T 中获取所有可以赋值给类型 U 的类型
// 语法
type Extract<T, U> = T extends U ? T : never;

type T0 = Extract<"a" | "b", 'a'>;  // "a"
type T1 = Extract<string | (() => void), Function>; // () => void
type T2 = Extract<"a" | "b", 'c'>;  // never

欢迎留言指正

学习资源:TypeScript 入门教程 一篇让你完全够用TS的指南
学习的代码整理好会同步到 gitlab

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值