TYPESCRIPT 工具类型

更新日期: 2022-06-27阅读: 819标签: 类型

类型是 TypeScript 的灵魂,很多时候我们需要种种新的类型。工具类型是 TypeScript 的一种特殊类型,为了解决某一特定的类型问题,得到一种新的类型。有的就是一种通用类型;有的则可以对现有类型进行一定的转换,从而得到一种新的类型。

TypeScript 内置了很多工具类型,熟练运用它们,可以让我们开发工作事半功倍。

下面我们将逐一介绍这些内置工具类型。


    Record

    Record是一种基于键值对的类型,可以创建固定格式的数据结构,用于组合复杂的数据类型。

    假设有一个数据集如下:

    const myData = {
    "123-123-123" : { firstName: "John", lastName: "Doe" },
    "124-124-124" : { firstName: "Sarah", lastName: "Doe" },
    "125-125-125" : { firstName: "Jane", lastName: "Smith" }
    }

    这个数据集有一个string类型的 ID 作为键,所有值类型都含有string类型的firstName和string类型的lastName两个字段。

    对于这种数据类型,Record是最适合的。我们可以这么定义类型:

    type User = {
    firstName: string,
    lastName: string
    }

    const myData:Record<string, User> = {
    "123-123-123" : { firstName: "John", lastName: "Doe" },
    "124-124-124" : { firstName: "Sarah", lastName: "Doe" },
    "125-125-125" : { firstName: "Jane", lastName: "Smith" }
    }

    Record类型格式是Record<K, T>,其中,K是键的类型,T是值的类型。

    上面代码中,我们定义了一种新的类型User,用于描述值类型,将键的类型指定为string。


    Record和Union

    有时候,我们的键值只是某些可选值的集合。例如:

    const myData = {
    "uk" : { firstName: "John", lastName: "Doe" },
    "france" : { firstName: "Sarah", lastName: "Doe" },
    "india" : { firstName: "Jane", lastName: "Smith" }
    }

    我们假设数据集如上,所以键值只允许是uk、france和india之一。这样的仅有有限值组成的集合类型叫做联合 union

    在这个例子中,我们可以定义User类型以及一个联合类型作为键:

    type User = {
    firstName: string,
    lastName: string
    }
    type Country = "uk" | "france" | "india";

    const myData:Record<Country, User> = {
    "uk" : { firstName: "John", lastName: "Doe" },
    "france" : { firstName: "Sarah", lastName: "Doe" },
    "india" : { firstName: "Jane", lastName: "Smith" }
    }

    利用联合类型,我们就可以确保Record的键为三个可选值之一。


    Required

    有时我们需要确保对象有一些属性是必须的,甚至这些属性是可选的,也必须给值。为了达到这一目的,TypeScript 提供了一个工具类型Required。

    默认情况下,我们在 TypeScript 中定义的新类型,所有属性都自动成为必须的:

    type User = {
    firstName: string,
    lastName: string
    }

    let firstUser:User = {
    firstName: "John"
    }

    上面代码中,firstUser只有一个firstName属性,没有lastName,那么,TypeScript 会报错:

    Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'User'.

    如果我们希望这个属性是可选的,那么,我们需要在类型定义中添加?标注:

    type User = {
    firstName: string,
    lastName?: string
    }

    let firstUser:User = {
    firstName: "John"
    }

    例如上面的代码,我们把lastName改成lastName?,此时,lastName就成为可选的,firstUser也就能编译通过了。

    至此,一切都很好。但是,现在又有一个问题:虽然在类型定义中,lastName是可选的,但在某些情况下,我们需要User类型必须提供lastName,才能进行接下来的操作。为达到这一目的,我们可以使用下面的代码:

    type User = {
    firstName: string,
    lastName?: string
    }

    let firstUser:User = {
    firstName: "John",
    }

    let secondUser:Required<User> = {
    firstName: "John"
    }

    在这个例子中,secondUser会报错:

    Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'Required'.

    所以,当我们使用了Required类型的时候,我们必须添加lastName属性,这样就没有错误了:

    type User = {
    firstName: string,
    lastName?: string
    }

    let secondUser:Required<User> = {
    firstName: "John",
    lastName: "Doe"
    }

    这种机制看似多此一举,但带给我们更多的灵活性:我们可以针对系统中的某些函数进行特殊的建模,强制某些属性仅在某些场景是必须的。与其它工具类型一样,Required可以针对interface或对象类型使用,因为它是针对类型的,但不能对变量使用,不过这也没多大关系,因为对象不可能是空值(undefined毕竟也是一种类型)。


    Partial

    Partial与Required正好相反,它的目的是把一种类型的所有属性都变成可选的。

    我们还是使用上面的例子:

    type User = {
    firstName: string,
    lastName: string
    }

    let firstUser:User = {
    firstName: "John"
    }

    这段代码会报错:

    Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'User'.

    因为firstUser缺少了必须的属性lastName。但是,如果在某些场景中,lastName就是缺失的呢?我们就可以使用Partial类型:

    type User = {
    firstName: string,
    lastName: string
    }

    let firstUser:Partial<User> = {
    firstName: "John"
    }

    Partial实际是把User类型转换成了:

    type User = {
    firstName?: string,
    lastName?: string
    }

    与创建一个这样的类型不同,使用Partial类型,我们可以同时拥有普通的User类型以及完全可选的Partial<User>类型。


    Readonly

    顾名思义,Readonly类型将一个类型变成只读的。

    例如,在下面的代码中,我们不想任何人修改firstUser对象的值,就可以将firstUser类型设置为Readonly<User>:

    type User = {
    firstName: string,
    lastName: string
    }

    let firstUser:Readonly<User> = {
    firstName: "John",
    lastName: "Doe"
    }

    这样,如果你要修改firstUser.firstName或者firstUser.lastName,就会直接报错:

    Cannot assign to 'firstName' because it is a read-only property.

    需要注意的是,Readonly只针对于interface或对象类型。一个变量的类型是Readonly,只是这个对象的属性值是只读的,并不意味着这个对象是不可变的。例如:

    let myVariable:Readonly<string> = "Hello World";
    myVariable = "Goodbye World";
    console.log(myVariable); // 输出 "Goodbye World"

    虽然myVariable类型是Readonly,但我们仍旧可以给myVariable赋一个新值。为了将myVariable的引用本身设置为只读,我们需要使用const关键字:

    const myVariable:string = "Hello World";
    // 错误: Cannot assign to 'myVariable' because it is a constant.
    myVariable = "Goodbye World";


    Exclude

    前面我们说过,联合就是有限值的集合。我们可以直接定义一个联合:

    type MyUnionType = "A" | "B" | "C" | "D"

    上面的例子中,我们定义了一个联合类型MyUnionType,其可选值只有四个:A、B、C、D。我们可以使用这种类型:

    type MyUnionType = "A" | "B" | "C" | "D"
    // 可以这么实用
    let firstString:MyUnionType = "A"
    // 错误:Type '"some-string"' is not assignable to type 'MyUnionType'.
    let secondString:MyUnionType = "some-string"

    理解了联合类型,我们就可以看看Exclude了。

    假设我们有一个MyUnionType类型,它包含四个可选值:A、B、C、D。但是,在某些场景中,我们不希望值A出现,那么就可以使用Exclude类型。Exclude语法如下:

    Exclude<UnionType, ExcludedMembers>

    第一个泛型参数是一个普通的联合类型,第二个泛型参数是需要排除的值。例如:

    type MyUnionType = "A" | "B" | "C" | "D"
    // 可以这么使用
    let firstString:MyUnionType = "A"
    // 错误:Type '"A"' is not assignable to type '"B" | "C" | "D"'.
    let secondString:Exclude<MyUnionType, "A"> = "A"

    注意上面的secondString变量,其类型是排除了A之后的MyUnionType,因此,我们不能将A赋值给它。

    如果需要排除多个值,可以使用|运算符,例如:

    type MyUnionType = "A" | "B" | "C" | "D"
    // 可以这么使用
    let firstString:MyUnionType = "A"
    let secondString:Exclude<MyUnionType, "A"> = "D"
    // ^
    // └ - - 类型是 "B" | "C" | "D"
    let thirdString:Exclude<MyUnionType, "A" | "B"> = "D";
    // ^
    // └ - - 类型是 "C" | "D"
    let forthString:Exclude<MyUnionType, "A" | "B" | "C"> = "D";
    // ^
    // └ - - 类型是 "D"
    let lastString:MyUnionType = "A"
    // ^
    // └ - - 类型是 "A" | "B" | "C" | "D"

    Exclude类型并不会改变原始的联合类型,这带给我们一种灵活性,即在某些场景中可以排除掉联合类型的某些值,但在另外的场景又可以使用完整的联合类型。


    Extract

    与Exclude类似,Extract类型同样适用于联合。Exclude是排除联合中的某些值,Extract则是选取其中的某些值。Extract语法如下:

    Extract<Type, Union>

    我们来看一个例子:

    type MyUnionType = "A" | "B" | "C" | "D"
    let firstString:Extract<MyUnionType, "A" | "B"> = "A"
    // ^
    // └ - - 类型是 "A" | "B"

    当我们使用Extract类型时,Extract会根据检查MyUnionType,看看其中是不是包含有"A" | "B",如果存在,则返回一个新的联合类型。如果Extract给的值不存在,则新的值直接被忽略:

    type MyUnionType = "A" | "B" | "C" | "D"
    let firstString:Extract<MyUnionType, "A" | "B" | "X"> = "A"
    // ^
    // └ - - 类型是 "A" | "B",由于 "X" 不存在于 MyUnionType,直接被忽略

    与Exclude类似,Extract也不会改变原始的联合类型。


    Omit

    Omit类型用于定制化已有类型。

    以User类型为例:

    type User = {
    firstName: string;
    lastName: string;
    age: number;
    lastActive: number;
    }

    User包含四个属性:firstName、lastName、age以及lastActive。但我们不能保证User类型一直能够满足我们的需求:有些时候我们希望有一个新的类型,这个类型同User大致相同,只是少了age和lastActive属性。那么,我们必须定义一个新的类型吗?不是。Omit就是为了满足这种需要。

    Omit类型语法如下:

    Omit<Type, Omissions>

    其中,第一个泛型参数是Omit作用的类型,第二个参数是联合类型,表示需要忽略的属性。

    例如:

    type User = {
    firstName: string;
    lastName: string;
    age: number;
    lastActive: number;
    }

    type UserNameOnly = Omit<User, "age" | "lastActive">

    上面代码中,UserNameOnly只包含了两个属性:firstName和lastName,age和lastActive则被忽略。这样,我们就获得了可以使用的新类型。比如下面的代码:

    type User = {
    firstName: string;
    lastName: string;
    age: number;
    lastActive: number;
    }

    type UserNameOnly = Omit<User, "age" | "lastActive">
    type UserNameAndActive = Omit<User, "age">

    const userByName:UserNameOnly = {
    firstName: "John",
    lastName: "Doe",
    };
    const userWithoutAge:UserNameAndActive = {
    firstName: "John",
    lastName: "Doe",
    lastActive: -16302124725
    }


    Pick

    Pick类型与Omit相反:Omit会忽略已有类型的某些属性,Pick则是选取已有类型的某些属性。

    来看下面的例子:

    type User = {
    firstName: string;
    lastName: string;
    age: number;
    }
    type UserName = Pick<User, "firstName" | "lastName">

    let user:UserName = {
    firstName: "John",
    lastName: "Doe"
    }

    可以看到,Pick从已有的User类型生成了一个新的类型,我们可以直接使用新的类型。


    NonNullable

    NonNullable类型会将已有类型的null和undefined类型移除。

    例如,我们有这样的类型:

    type MyType = string | number | null | undefined

    但是在某些场景中,我们不需要MyType类型中的null和undefined,那么,我们就可以使用NonNullable类型:

    type MyType = string | number | null | undefined
    type NoNulls = NonNullable<MyType>
    // ^
    // └ - - 类型是 string | number


    Parameters

    Parameters用于根据函数的参数生成一个新的类型。

    假设我们有一个函数:

    const myFunction = (a: string, b: string) => {
    return a + b;
    }

    我们想要调用这个函数,方法有很多。其中一种是创建一个元组(tuple),使用展开运算符(...)去调用:

    const myFunction = (a: string, b: string) => {
    return a + b;
    }

    let passArray:[string, string] = [ 'hello ', 'world' ]

    // Returns 'hello world'
    myFunction(...passArray);

    这里,我们定义了一个元组[string, string],然后给它赋值,最后使用展开运算符运行函数。

    到目前为止,一切都很顺利。但是,如果myFunction的参数变了呢?我们定义的[string, string]都需要修改。Parameters类型就是为了解决这一问题:

    const myFunction = (a: string, b: string) => {
    return a + b;
    }

    type MyType = Parameters<typeof myFunction>

    let myArray:MyType = [ 'hello ', 'world' ];

    myFunction(...myArray)

    Parameters类型实际简化了根据函数参数构建元组的方法。

    既然是元组,我们就可以按照元组的方式去使用Parameters的返回值:

    const myFunction = (a: string, b: string) => {
    return a + b;
    }

    type AType = Parameters<typeof myFunction>[0]
    type BType = Parameters<typeof myFunction>[1]

    let a:AType = 'hello '
    let b:BType = 'world'

    myFunction(a, b)

    Parameters也可以直接使用函数作为参数,例如:

    type AnotherType = Parameters<(a: string, b: number) => void>

    只不过这种直接使用匿名函数的方法并不怎么实用(因为仅仅是使用了匿名函数的声明,并没有定义)。

    如果泛型参数不是一个函数,Parameters会直接返回never类型。


    ConstructorParameters

    ConstructorParameters与Parameters类型,区别在于,后者按照返回函数参数列表返回一个元组,而前者按照类型构造函数参数返回。

    例如,ErrorConstructor声明如下:

    new ErrorConstructor(message?: string): Error

    那么,

    type ErrorType = ConstructorParameters<ErrorConstructor>;
    // ^
    // └ - - 类型是 [message?: string]


    ReturnType

    ReturnType与Parameters类似,只不过ReturnType基于函数的返回值构建一种新的类型。

    我们看一个例子:

    function sendData(a: number, b: number) {
    return {
    a: `${a}`,
    b: `${b}`
    }
    }
    type Data = ReturnType<typeof sendData>
    // The same as writing:
    // type Data = {
    // a: string,
    // b: string
    // }

    由于sendData()函数返回值类型是{ a: string, b: string },Data就是这个类型。这意味着我们不需要维护同一类型的两份拷贝,我们只有在函数中实际返回的那个类型。这无疑简化了我们的代码。


    链接: https://www.fly63.com/article/detial/11806

    JS中Null与Undefined的区别

    在JavaScript中存在这样两种原始类型:Null与Undefined。这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是Undefined?Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。

    Javascript的类型检测

    主要介绍了JS中检测数据类型的几种方式,typeof运算符用于判断对象的类型,但是对于一些创建的对象,它们都会返回\'object\',有时我们需要判断该实例是否为某个对象的实例,那么这个时候需要用到instanceof运算符

    js类型转换的各种玩法

    对于object和number、string、boolean之间的转换关系,ToPrimitive是指转换为js内部的原始值,如果是非原始值则转为原始值,调用valueOf()和toString()来实现。

    JavaScript类型:关于类型,有哪些你不知道的细节?

    Undefined类型表示未定义,它的类型只有一个值为undefined。undefined和null有一定的表意差别。非整数的Number类型无法使用 == 或 === 来比较,因为 JS 是弱类型语言,所以类型转换发生非常频繁

    为你的 JavaScript 项目添加智能提示和类型检查

    近在做项目代码重构,其中有一个要求是为代码添加智能提示和类型检查。智能提示,英文为 IntelliSense,能为开发者提供代码智能补全、悬浮提示、跳转定义等功能,帮助其正确并且快速完成编码。

    js的类型

    基本类型:按值访问,可以操作保存在变量中实际的值;引用类型数据存在堆内存,而引用存在栈区,也就是说引用类型同时保存在栈区和堆区,关于==的执行机制,ECMASript有规范,因为==前后的值交换顺序,返回的值也是一样的,所以在此对规范做出如下总结

    再也不用担心 JavaScript 的数据类型转换了

    JavaScript 是一种弱类型或者说动态类型语言。所以你不用提前声明变量的类型,在程序运行时,类型会被自动确定,你也可以使用同一个变量保存不同类型的数据。

    JavaScript基础之值传递和引用传递

    js的值传递和引用(地址)传递:js的5种基本数据类型 number,string,null,undefined,boolean 在赋值传递时是值传递,js的引用数据类型(object,array,function)进行引用传递,其实底层都是对象。

    JS中的布尔 数字 字符串

    JS中所有的值都可以转换成布尔类型 使用Boolean()或者 !!(两个感叹号),JS中所有的值都可以转换成数字类型,使用Number()或+。数字类型转换场景目的只有一个,用于计算,将后台传递的数据,从字符串转换为数字并参与计算

    if条件中,js的强制类型转换

    众所周知,JS在很多情况下会进行强制类型转换,其中,最常见两种是:1.使用非严格相等进行比较,对==左边的值进行类型转换2.在if判断时,括号内的值进行类型转换,转化为布尔值

    点击更多...

    内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!