泛型

什么是泛型

  • 泛型简单来说就是不确定的类型变量可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数接口class 中。,在ts中存在类型,如number、string、boolean等。泛型就是使用一个类型变量来表示一种类型,类型值通常是在使用的时候才会设置。泛型的使用场景非常多,可以在函数interface接口中使用

使用泛型的场景

  • 当我们定义一个函数或类时,某些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。
  • 当然解决问题的方法不止一个,我们也可以使用any来定义变量类型,但是我们都知道,使用any定义时是存在一些问题的:虽然以知道传入值的类型但是无法获取函数返回值的类型;另外也失去了ts类型保护的优势
  • 这时我们就可以使用泛型来定义变量

泛型的基本使用

1. 在函数中使用泛型

// 1.在函数中使用泛型
// 我们预先不知道函数的传参规定(不知道形参的输入类型),就可以使用泛型
function test <T> (a:T):T{
console.log(a);
return a;
}

// 泛型可以同时指定多个
function test1<T, K>(a: T, b: K):T{
console.log(b);
return a;
}

// 自动判断输出类型
test(111);// 返回值是number类型的 111

// 设置类型判断输出(推荐使用)
test<number>(111);// 返回值是number类型的 111
test<string | boolean>('芜湖!')//返回值是string类型的 hahaha
test<string | boolean>(true);//返回值是布尔类型的 true
test1<number, string>(123, 'hello');//多个泛型输出
  • 使用方式类似于函数传参,传什么数据类型,T就表示什么数据类型, 使用表示,T也可以换成任意字符串。

2. 在接口中使用泛型

// 2.在接口中使用泛型
// 注意,这里写法是定义的方法哦
interface Search {
// 设置一个函数接口,使用泛型定义形参的输入和返回值
<T,Y>(name:T,age:Y):T
}

let fn:Search = function <T, Y>(name: T, id:Y):T {
console.log(name, id)
return name;
}
fn('li',11);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型

/* ------------------ 同样可以使用接口来限制函数形参 ------------------------*/
// 定义一个接口
interface Inter{
length: number;
}

// T extends Inter 表示泛型T必须时Inter实现类(子类)
// 这表示输入到函数里面的形参必须有接口里面的属性,也就是必须有length属性(数组,字符串等..)
function fn3<T extends Inter>(a: T): number{
console.log(a.length);
return a.length;
}

// 输出结果
fn3('12345')//输出5
fn3([1,2,3])//输出3

3. 在中使用泛型

// 3.在类中使用泛型
class Animal<T> {// 提前设置一个未知的泛型类型
name:T;//属性可以使用泛型

constructor(name: T){//构造函数也可以使用泛型
this.name = name;
}

action<T>(say:T) {//方法可以使用泛型
console.log(say)
}
}
let cat = new Animal('dog');
cat.action('汪汪汪!')

4. 在数组中使用泛型

// 4.数组中使用泛型
let arr1:Array<number> = [1,2,3]//设置为数值型数组
let arr2:number[] = [1,2,3] //设置为数值型数组
let arr3:string[] = ['1','2','3'] //设置为字符型数组

泛型工具类型

  • TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作。

  • 它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:

1. Partial

  • Partial<type>: 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
// 1.Partial<type>
// 作用:接收一个类或接口等变量,并且以它为模板生成一个一模一样的变量,但它内部的属性变成可选
// 语法:Partial<模板类或对象>
interface Person {
name:string;
age:number;
}

// 设置一个函数来接收以Person接口为模板的形参并输出它(里面的属性是可选的)
// 即接收一个对象作为形参,该对象里面的属性最多只能包含Person里面的属性(不能有其他属性)
function student<T extends Person>(arg: Partial<T>):Partial<T> {
return arg;
}

// 结果:形参是只有name属性的对象,同样可以输出
console.log(student({name:'张三'}));//{name:'张三'}

2. Record

  • Record<keys,type>: 构造一个对象类型,属性键为 Keys,属性类型为 Type
  • Record 工具类型有两个类型变量:1 表示对象有哪些属性 2 表示对象属性的类型。
// 2.Record<Keys, Type>
// 作用:是将K中所有的属性转换为T类型;
// 语法:Record<'将来的对象类型中有什么样的键名',键值的类型>
interface PageInfo {
title: string
}

type Page = 'home'|'about'|'other';
// 这表示x里面有3个属性,键名对应page的3个,键值(属性值)则是接口对象,里面必须包含title属性
const x: Record<Page, PageInfo> = {
home: { title: "xxx" },
about: { title: "aaa" },
other: { title: "ccc" },
};
console.log(x);//成功输出结果!

3. Pick

  • Pick<Type, Keys>Type 中选择一组属性来构造新类型。
  • Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性。
  • 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
  • 创造出来的新类型变量传入的属性只能是模板类型变量中存在的属性。(简单点说就是你只能拿他有的)
// 3.Pick<Type, Keys>
// 作用:是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型
// 语法:Pick<'指定的接口或者类', '该类中的某些属性'>
interface Todo {
title:string,
desc:string,
time:string
}

// 表示以Todo接口为模板创建一个新的类型,里面包含Todo模板里面的两个属性(title和time)
type TodoPreview = Pick<Todo, 'title'|'time'>;
const todo: TodoPreview ={
title:'吃饭',
time:'明天'
}

// 结果输出
console.log(todo);//{ title: '吃饭', time: '明天' }

4. readonly

  • Readonly<Type>:用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)
// 4.readonly<Type>
// 作用:顾名思义就是将一个类型(接口或者类)变成只读
// 语法:readonly<'指定的类或接口的模板'>
type todo1 = Readonly<Todo>//以Todo为模板设置一个新的类型,但是里面的属性是只读的
const todo2:todo1 = {title:'1',desc:'2',time:'3'}
console.log(todo2);//成功输出{ title: '1', desc: '2', time: '3' }
// todo2.title = '123'//此时进行赋值操作是会报错的