TypeScript

1、Typescript 基础语法

1.1 类型定义

类型定义的语法很简单

const name: string = 'heyi'
const age: number = 18
enum Color{//枚举
  Red,
  Green,
  Blue
}
const a:Color = Color.Red
//any 任何类型
//void 空值,用在函数返回值或者undefined,null
function test():void{

}
let unuseable:void =undefined
//null
const test1:null =null//没啥意义
//never 永远不存在的,一般抛出异常到不了返回
function error(message:string):never {
  throw new Error(message)
}

1.2 类型联合与交叉

类型联合用于指定一个值的类型可以是多个,我们可以把一个业务化的数据变量指定成既可以是 string 又可以是 number。

let myFavoriteNumber: string | number;

类型交叉是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

type NameProtocal = {name: string}
type PersonLikeProtocal = {age: number; say: () => void}

type Student = NameProtocal & PersonLikeProtocal

1.3 类型断言

let value:number =123
let valueLength:number =(value as unknown as string).length//把value 转成string
let valueLength:number =(<unknown><string>value).length//把value 转成string

2、Typescript 泛型与类型体操

2.1 泛型的概念和使用

2.1.1 泛型的概念

定义:泛型(Generics) 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。泛型,可以用汉语言中的 “ 代词 ” 类比。

为什么要使用泛型?

需求:实现一个函数,传入的函数参数是什么类型的,返回值的类型也要跟函数参数的类型相同,并且函数只能接收一个参数,你会怎么做?

  • 首先,我们先创建一个identity函数,将他的类型和返回值都固定为number类型,但是,该函数只能接收number类型的数据,如果我调用函数的时候传入字符串或者布尔值类型的值,此时就会报错
const identity: (value: number) => number = (value) => value
console.log(identity(10)); // 输出:10,类型是number
console.log(identity('Echo')); // 报错:类型“string”的参数不能赋值给类型“number”的参数
  • 为了让函数能够接收任意类型,可以将参数类型改为any,但是,这样就失去了 TS
    的类型保护,会丢失一些信息:传入的类型与返回的类型应该是相同的。 我们传入一个数字,任何类型的值都有可能被返回。
 const identity: (value: any) => any = (value) => value
   console.log(identity('Echo'));    // 输出:Echo
   console.log(identity(26));        // 输出:26
   console.log(identity(true));      // 输出:true
  • 因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>('Echo'));   // 输出:Echo
console.log(identity<number>(26));       // 输出:26
console.log(identity<boolean>(true));    // 输出:true

上述代码中,给identity添加了类型变量T。 T会捕获用户传入的类型(如:number、string)。 之后再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型

2.1.2 泛型的使用

  • 首先,先定义一个函数
 function identity<T>(value: T): T {
	return value;
}
//const func:<T>(value: T)=> T = identity
  • 第一种使用方法:传入所有的参数,包含类型参数
 let output = identity<string>("myString"); 

明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。

  • 第二种使用方法:利用类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型
let output = identity("myString");

没必要使用尖括号(<>)来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。

2.1.3 泛型类

  • 泛型类(Generic Class) 是指在定义类时使用泛型类型参数的类。它允许我们在类的属性、方法、构造函数以及实例化时使用泛型。
  • 泛型类使用(<>)括起泛型类型,跟在类名后面。
class Container<T> {
  private items: T[] = [];

  addItem(item: T) {
    this.items.push(item);
  }

  getItem(index: number): T {
    return this.items[index];
  }

  getItems(): T[] {
    return this.items;
  }
}

const container = new Container<number>(); // 实例化一个泛型类,指定类型参数为 number
container.addItem(1);
container.addItem(2);
container.addItem('e');//报错
console.log(container.getItems()); // [1, 2]

类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以 类的静态属性不能使用这个泛型类型

2.1.4 泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法

function getLength<T>(arg: T): T {
    console.log(arg.length);// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
    return arg;
}

上例中,泛型 T 不一定包含属性 length,所以编译的时候报错了。

  • 我们通过 extends 关键字进行类型约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:
interface ILength {
  length: number;
}

function getLength<T extends ILength>(value: T): T {
  console.log(value.length);
  return value;
}

getLength([1, 2, 3])                    // 正确,因为数组有 length 属性
getLength('Echo') //                    // 正确,因为字符串有 length 属性
getLength({ length: 10, name: 'Echo' }) // 正确,因为传入的参数有 length 舒心
getLength(10)                           // 报错:类型“number”不能赋值给类型“ILength”的参数,因为数字不具有 length 属性
  • 多个类型参数之间也可以互相约束
function getProperty(obj,key){
   return obj[key]
}

这种情况,我们需要约束,key是obj的键值

function getProperty<T,K extends keyof T>(obj:T,key:K){ //keyof表示,K继承于T的键值
   return obj[key]
}

2.2 interface 接口

我们在JavaScript中定义一个函数,用于获取一个用户的姓名和年龄的字符串:

const getUserInfo = function(user) {
  return `name: ${user.name}, age: ${user.age}`
}

正确的调用方法应该是下面的方式:

getUserInfo({name: "coderwhy", age: 18})

当项目比较大,或者多人开发时,会出现错误的调用方法:需要加上类型限制

const getUserInfo = (user: {name: string, age: number}): string => {
  return `name: ${user.name} age: ${user.age}`;
};

这样确实可以防止出现错误的调用,但是我们在定义函数的时候,参数的类型和函数的类型都是非常长的,代码非常不便于阅读。
于是 参数类型使用接口定义
我们先定义一个IUser接口:

// 先定义一个接口
interface IUser {
  name: string;
  age: number;
}

接下来我们看一下函数如何来写:

const getUserInfo = (user: IUser): string => {
  return `name: ${user.name}, age: ${user.age}`;
};
 
// 正确的调用
getUserInfo({name: "coderwhy", age: 18});
 
// 错误的调用,其他也是一样
getUserInfo();

继续优化,对函数返回值也做出限制

interface IUser {
  name: string;
  age: number;
}
 
interface IUserInfoFunc {
  (user: IUser): string;
}

const getUserInfo: IUserInfoFunc = (user) => {
  return `name: ${user.name}, age: ${user.age}`;
};
 
// 正确的调用
getUserInfo({name: "coderwhy", age: 18});
 
// 错误的调用
getUserInfo();

定义接口中不仅仅可以有属性,也可以有方法:属性可以可选,可以只读

interface Person {
  readonly name: string;//只读
  age?: number;//可选
  run(): void;
  eat(): void;
  study?(): void;//可选
}

2.3 extends 的重要性

2.2.1 接口继承

extends用来做继承功能,相信大家都不陌生,ES6的Class语法也是用它来做类的继承用。在TS中用法也类似,来看示例

T1 {
    name: string
  }
  
  interface T2 {
    sex: number
  }
  
  // 多重继承,逗号隔开
  interface T3 extends T1,T2 {
    age: number
  }
  
  // 合法
  const t3: T3 = {
    name: 'xiaoming',
    sex: 1,
    age: 18
  }

2.2.2 条件判断

extends用来条件判断的语法和JS的三元表达是很相似,如果问号前面的判断为真,则将第一个类型string赋值给A,否则将第二个类型number赋值给A
那么,接下来的问题就是,extends判断条件真假的逻辑是什么?
很简单,如果extends前面的类型能够赋值给extends后面的类型,那么表达式判断为真,否则为假

比如

  // 示例2
  interface A1 {
    name: string
  }

  interface A2 {
    name: string
    age: number
  }
  // A的类型为string
  type A = A2 extends A1 ? string : number
  
  const a: A = 'this is string'

A1,A2两个接口,满足A2的接口一定可以满足A1,所以条件为真,A的类型取string(即A2可以从A1继承而来)
上述A2可以改为

  interface A2 extends A1 {
    age: number
  }

2.4 Infer 推断

infer 表示在 extends 条件语句中待推断的类型变量。

type ParamType<T> = T extends (arg: infer P) => any ? P : T;

这个条件语句 T extends (arg: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

整句表示为:如果 T 能赋值给 (arg: infer P) => any,则结果是 (arg: infer P) => any 类型中的参数 P,否则返回为 T

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

type Func = (user: User) => void;

type Param = ParamType<Func>; // Param = User
//Func是T 他的函数参数 User 是P, 
//Func=(user: User) => void 也就是  T符合(arg: infer P) => any 
//所以返回P ,也就是type ParamType<Func> 返回P,也就是 Param = User

type AA = ParamType<string>; // string
//string 是T,不符合  (arg: infer P) => any ,所以返回T,也就是string 

所以这个案例的作用是:假如我们定义了一个函数,可以获取参数的类型,同时非函数的话返回本身

3、Typescript 综合

interface 和type的区别是什么?

 interface IProps{
    a:number
 }
 type Props={
     a:number
 }

type(类型别名)、interface(接口)

相同点:

1、都可以用来定义对象和函数

type Position = {
	x: number;
	y: number;
};

type SetPosition = (x: number, y: number) => void;

interface Position {
	x: number;
	y: number;
}

interface SetPosition {
   (x: number, y: number): void;
}

2、都可以实现继承
对于 interface 来说,继承是通过 extends 实现的;而 type 是通过 & 来实现的,也可以叫做交叉类型。


// type 继承 type
type Person {
    name: string
}
type Student = Person & { stuId: number }

// type 继承 interface
interface Person {
   name: string
}
type Student = Person & { stuId: number }


// interface 继承 interface
interface Person {
   name: string
}

interface Student extends Person { 
    stuId: number 
}

// interface 继承 type
type Person {
   name: string
}

interface Student extends Person { 
     stuId: number
 }

不同点:

1、type 可以做到而 interface 不能做到

  • type 可以声明基本类型。
type userName = string;
  • type 可以声明联合类型。
type userMsg = string | number;
  • type 可以声明元组类型。
type Data = [number, string];
  • type 可以通过 typeof 操作符来声明
type myType = typeof someObj;

2、interface 可以做到而 type 不能做到

  • interface 可以声明合并。
interface test {
  name: string
}
interface test {
   age: number
}

/*
test实际为 {
    name: string
    age: number
}
*/

如果是 type 的话,就会报重复定义的警告,因此是无法实现声明合并的。

总结:

  • type支持定义非对象类型,如联合类型、交叉类型、映射类型等,而interface只能定义对象类型。
  • interface支持继承和合并,可以通过extends关键字实现接口的扩展,也可以多次声明同一个接口来合并成员,而type不支持继承和合并(type不能声明合并,不过可以通过通过 & 来实现继承,也叫交叉类型)
  • type是类型别名,不会创建新的类型,只是给已有类型命名,而interface是定义了一个接口类型,可以在运行时获取类型信息。
  • type和interface都可以用于定义对象的类型和形状,但interface更适合面向对象编程,而type更适合组合不同类型。

使用建议
1、官方推荐使用 interface,其他无法满足需求的情况下用 type。但是因为联合类型和交叉类型是比较常用的,所以避免不了大量使用 type 的场景,一些复杂类型也需要通过组装后形成类型别名来使用。

2、如果想保持代码统一,还是可选择使用 type。通过上面的对比,type 其实可涵盖 interface 的大部分场景。

3、对于 React 组件中 props 及 state,推荐使用 type,这样能够保证使用组件的地方不能随意在上面添加属性。如果有自定义需求,可通过 HOC(高阶组件)二次封装。

4、编写三方库时使推荐使用 interface,其更加灵活自动的类型合并可应对未知的复杂使用场景。

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值