Open AnnGreen1 opened 4 months ago
Me:
typescript函数重载是什么?为什么要声明多次?使用|
多标注几种类型不就行了吗?
@GPT-3.5 Turbo: TypeScript函数重载是指在函数定义中声明多个函数签名,每个函数签名都有不同的参数类型或个数。当调用函数时,TypeScript编译器会根据传入参数的类型和个数来确定调用哪个函数签名。
使用函数重载的主要原因是为了提高代码的可读性和类型安全性。通过使用函数重载,可以明确指定函数的不同参数类型和返回值类型,使代码更易于理解和维护。此外,函数重载还可以帮助开发人员在编译时捕获潜在的类型错误,提高代码的质量。
虽然使用|
操作符可以将多种类型标注在一个参数上,但是这种方式并不能完全替代函数重载。因为使用|
操作符只能标注参数的类型,而无法标注参数的个数和返回值类型。而函数重载可以更精确地定义函数的多个版本,以确保函数在不同情况下都能正确地被调用。
因此,尽管在某些情况下使用|
操作符可以简化代码,但在需要精确控制函数参数类型、个数和返回值类型时,函数重载仍然是一个更好的选择。
Me: 请举例说明,并告诉我如何才是typescript中正确的重载,参数类型?返回值类型?参数数量?
@GPT-3.5 Turbo: 下面是一个简单的示例,演示了如何在TypeScript中正确地使用函数重载:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if (typeof a === 'string' && typeof b === 'string') {
return a + b;
} else {
throw new Error('Parameters must be of the same type');
}
}
console.log(add(1, 2)); // 输出 3
console.log(add('Hello', 'World')); // 输出 HelloWorld
在上面的示例中,我们定义了一个名为add
的函数,它有两个重载版本。第一个重载版本接受两个参数为数字类型,并返回一个数字类型的结果;第二个重载版本接受两个参数为字符串类型,并返回一个字符串类型的结果。最后,我们定义了一个实际的函数实现,根据传入参数的类型来执行不同的操作。
在正确使用函数重载时,需要注意以下几点:
any
类型,用于处理未在重载中定义的情况。通过正确使用函数重载,可以提高代码的可读性和类型安全性,确保函数在不同情况下都能正确地被调用。
Me: 参数类型和数量相同,返回值类型相同,这是重载吗?和C++相同吗?
@GPT-3.5 Turbo: 在 TypeScript 中,参数类型和数量相同、返回值类型相同的函数声明并不会被视为函数重载。这种情况下,TypeScript 会将这些函数声明视为同一个函数的不同实现,而不是函数重载。
与 TypeScript 不同,C++ 中的函数重载是指在同一作用域内可以定义多个具有相同函数名但参数类型、数量或顺序不同的函数。在 C++ 中,通过参数类型、数量或顺序的不同来区分同名函数,实现了函数重载的功能。
因此,虽然 TypeScript 和 C++ 都支持函数重载的概念,但在 TypeScript 中,参数类型和数量相同、返回值类型相同的函数声明并不会被视为函数重载,而在 C++ 中,函数重载是通过参数类型、数量或顺序的不同来区分同名函数的。
Me: C++中,我一般会实现每一个重载函数,而在你给出的TypeScript重载示例中为什么只实现了最后一个函数,并且还在里面判断了类型?这样重载还有意义吗?
@GPT-3.5 Turbo: 在我给出的 TypeScript 示例中,只实现了最后一个函数,而没有单独实现每一个重载版本。这是因为在 TypeScript 中,函数重载的目的是为了提供多个函数签名,以便在编译阶段进行类型检查和函数匹配,而实际的函数实现可以集中在最后一个函数中。
在 TypeScript 中,当调用一个重载函数时,编译器会根据提供的参数类型和数量来匹配最合适的函数签名,然后调用最后一个函数实现。因此,在实际的函数实现中,我们可以使用类型检查来处理不同参数类型的情况,以确保函数在不同情况下都能正确地执行。
虽然在 TypeScript 中只实现最后一个函数可能看起来有些不同于在 C++ 中为每个重载函数单独实现的做法,但这种方式仍然是符合 TypeScript 的设计理念的。通过集中实现在最后一个函数中,并在其中进行类型检查,可以简化代码并确保函数的行为符合预期。
因此,尽管在 TypeScript 中的函数重载实现方式与 C++ 稍有不同,但仍然具有意义,可以提高代码的可读性和类型安全性。
Me: 那在typescript中可以和C++中一样,单独实现每一种重载函数,而不去在每个函数内部在考虑类型的问题可以吗?这样种方法更推荐哪种呢?为什么?
@GPT-3.5 Turbo: 在 TypeScript 中,你也可以像在 C++ 中一样,单独实现每一种重载函数,而不在每个函数内部考虑类型的问题。这种方式在 TypeScript 中也是可行的,可以根据不同的参数类型分别实现不同的重载版本。
然而,需要注意的是,如果你选择单独实现每一种重载函数,那么在实现过程中需要确保每个重载函数的参数类型、数量和返回值类型与重载声明完全匹配,以避免类型不匹配的错误。另外,这种方式可能会导致代码冗余,尤其是在参数类型相似但不完全相同的情况下。
相比之下,在 TypeScript 中集中实现所有重载的函数实现并在其中进行类型检查的方式更为推荐。这种方式可以简化代码结构,减少重复代码,并确保函数的行为符合预期。通过集中实现函数实现,可以更好地维护和管理代码,并提高代码的可读性和可维护性。
因此,虽然在 TypeScript 中可以选择单独实现每一种重载函数,但更推荐的做法是集中实现所有重载的函数实现并在其中进行类型检查,以简化代码结构并确保代码的正确性。
大家好,我叫小杜杜,是一个喜欢动手敲代码的小菜鸟,我认为代码应该亲自敲一遍,才能更好的熟记于心,作为一个程序员,我们与自己的小伙伴共同开发、维护于一个项目,需要保持代码的整洁、清晰,使组内的任意一位小伙伴都能快速理解每一代码模块,因此制定了各种各样的规则,去约束我们写代码,只有这样才能我们的代码更加具有可读性、可维护性和健壮性
我们用什么去管理自己的项目,在前几年中,我们使用eslint,用prop-types插件去定义参数的类型,当我接触的时候,我本人是非常非常抵触的,为什么呢?因为我个人觉得非常的麻烦,我现在还记得,当时在老项目装接入eslint满篇爆红,去查为何爆红的场景,想想真是惨绝人寰~
之后接触到TS,一开始本人也是非常抵触,但抱着试一试的态度,去学习,使用它,你会发现越用越好用,甚至觉得不用TS写代码都不爽了,当然有了TS你就可以远离eslint 和prop-types了。
如果你准备接触 TS,或者刚接触,对 TS 不太理解相信这篇文章一定能更好的帮助你,希望大家多多支持~
先来看看知识图,如果你对以下概念有盲区,那么这篇文章应该能很好的帮助到你~
TS 是什么 ?
TS:是TypeScript的简称,是一种由微软开发的自由和开源的编程语言。
TS 和 JS 的关系
对比于 JS,TS 是 JS 的超集,简单的说就是在
JavaScript
的基础上加入了类型系统,让每个参数都有明确的意义,从而带来了更加智能的提示。相对于
JS
而言,TS
属于强类型语言,所以对于项目而言,会使代码更加规范,从而解决了大型项目代码的复杂性,其次,浏览器是不识别TS
的,所以在编译的时候,TS
文件会先编译为JS
文件。安装 TS
执行命令:
查看版本
编译
在线编译
我们为了方便起见,可以使用线上的编辑器:TypeScript Playground,像这样
并且你还可以看看生成对应的
ts
转化ES5
,ES6
之后的代码,也有相关的例子供你查看TS 的基本数据类型
这里将 TS 的数据类型简单的进行下归类:
string
、number
、boolean
、symbol
、bigint
、null
、undefined
array
、Tuple
(元组)、object
(包含Object
和{}
)、function
any
、unknow
、void
、never
、Enum
(枚举)类型推理
、字面量类型
、交叉类型
注:案例中有可能用到
type
和interface
,在下面会详细讲解,有比较模糊的可以先看看基本类型
需要注意:
null
和undefined
两个类型一旦赋值上,就不能再赋值给任何其他类型symbol
是独一无二的,假设再定义一个sym1
,那么sym === sym1 为 false引用类型
Array
两种方式:
Tuple(元组)
Tuple 可以说是
Array
的一种特殊情况,针对上面的arr3
,我们看他的类型可以是string
也可以是number
,但对每个元素没有作出具体的限制。那么
Tuple
的作用就是限制元素的类型并且限制个数的数组,同时Tuple
这个概念值存在于TS
,在JS
上是不存在的这里存在一个问题:在
TS
中,是允许对 Tuple 扩增的(也就是允许使用push
方法),但在访问上不允许object
object
非原始类型,在定义上直接使用 object 是可以的,但你要更改对象的属性就会报错,原因是并没有使对象的内部具体的属性做限制,所以需要使用{}
来定义内部类型Object
(大写的 O),代表所有的原始类型或非原始类型都可以进行赋值,除了null
和undefined
function
定义函数
function
, 另一种为箭头函数
TS
的类型推断功能够正确推断出返回值类型参数类型
?
实现,且可选参数一定要在最后面=
实现...
函数重载
函数重载:是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义。 简单的说:可以在同一个函数下定义多种类型值,最后汇总到一块
特殊类型
any
在 TS 中,任何类型都可以归于
any
类型,所以any
类型也就成了所有类型的顶级类型,同时,如果不指定变量的类型,则默认为 any 类型, 当然不推荐使用该类型,因为这样丧失了 TS 的作用unknow
与
any
一样,都可以作为所有类型的顶级类型,但unknow
更加严格,那么可以说除了any
之下的第二大类型,接下来对比下any
,主要严格于一下两点:unknow
会对值进行检测,而类型any
不会做检测操作,说白了,any
类型可以赋值给任何类型,但unknow
只能赋值给unknow
类型和any
类型unknow
不允许定义的值有任何操作(如 方法,new 等),但 any 可以void
当一个函数,没有返回值时,TS 会默认他的返回值为
void
类型never
表示一个函数永远不存在返回值,TS 会认为类型为
never
,那么与void
相比,never
应该是void
子集, 因为void
实际上的返回值为undefined
,而never
连undefined
也不行符合
never
的情况有:当抛出异常的情况和无限死循环Enum(枚举)
可以定义一些带名字的常量,这样可以清晰表达意图或创建一组有区别的用例
注意:
string
或number
同时我们可以看看翻译为 ES5 是何样子
数字枚举
字符串枚举
字符串枚举要注意的是必须要有默认值,不支持反向映射
常量枚举
除了
数字类型
和字符串类型
之外,还有一种特殊的类型,那就是常量枚举,也就是通过const
去定义enum
,但这种类型不会编译成任何JS
,只会编译对应的值异构枚举
包含了
数字类型
和字符串类型
的混合,反向映射一样的道理类型推论
我们在学完这些基础类型,我们是不是每个类型都要去写字段是什么类型呢?其实不是,在
TS
中如果不设置类型,并且不进行赋值时,将会推论为any类型,如果进行赋值就会默认为类型字面量类型
字面量类型:在
TS
中,我们可以指定参数的类型是什么,目前支持字符串
、数字
、布尔
三种类型。比如说我定义了str 的类型是 '小杜杜'
那么 str 的值只能是小杜杜交叉类型(&)
交叉类型:将多个类型合并为一个类型,使用
&
符号连接,如:同名基础属性合并
我们可以看到
交叉类型
是结合两个属性的属性值,那么我们现在有个问题,要是两个属性都有相同的属性值,那么此时总的类型会怎么样,先看看下面的案列:如果是相同的类型,合并后的类型也是此类型,那如果是不同的类型会如何:
我们在
Aprops
和BProps
中同时加入c属性
,并且c属性
的类型不同,一个是number
类型,另一个是string
类型现在结合为
allProps
后呢? 是不是c属性
是number
或string
类型都可以,还是其中的一种?然而在实际中,
c
传入数字类型
和字符串类型
都不行,我么看到报错,现实的是 c 的类型是 never。这是因为对应
c
属性而言是string & number
,然而这种属性明显是不存在的,所以c
的属性是never
同名非基础属性合并
我们来看看案例,对于混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合。
如果 接口 A 中的 也是 b,类型为 number,就会跟同名基础属性合并一样
Class(类)
在
ES6
中推出了一个叫class(类)
的玩意,具体定义就不说了,相信用过React
的小伙伴一定不陌生.基本方法
在基本方法中有:
静态属性
,静态方法
、成员属性
、成员方法
、构造器
、get set方法
,接下来逐个看看:需要注意的是: 在成员属性中,如果不给默认值,并且不使用是会报错的,如果不想报错就给如 !,如:
name4!:string
让我们看看上述代码翻译成 ES5 是什么样:
私有字段(#)
在 TS 3.8 版本便开始支持ECMACMAScript的私有字段。
需要注意的是
私有字段
与常规字段不同,主要的区别是:#
字符开头,也叫私有名称;只读属性(readonly)
只读属性:用
readonly
修饰,只能在构造函数中初始化,并且在 TS 中,只允许将interface
、type
、class
上的属性标识为readonly
readonly
实际上只是在编译阶段
进行代码检查radonly
修饰的词只能在constructor
阶段修改,其他时刻不允许修改继承(extends)
继承:是个比较重要的点,指的是子可以继承父的思想,也就是说
子类
通过继承父类
后,就拥有了父类
的属性和方法,这点与HOC
有点类似这里又个
super
字段,给不知道的小伙伴说说,其作用是调用父类上的属性和方法修饰符
主要有三种修饰符:
abstract
abstract: 用 abstract 关键字声明的类叫做抽象类,声明的方法叫做抽象方法
注:抽象类是不能直接实例化,只能实例化实现了所有抽象方法的子类
重写和重载
TS 断言和类型守卫
TS 断言
分为三种:
类型断言
、非空断言
、确定赋值断言
当断言失效后,可能使用到:双重断言
类型断言
在特定的环境中,我们会比 TS 知道这个值具体是什么类型,不需要 TS 去判断,简单的理解就是,类型断言会告诉编译器,你不用给我进行检查,相信我,他就是这个类型
共有两种方式:
但需要注意的是:尖括号语法在React中会报错,原因是与
JSX
语法会产生冲突,所以只能使用as 语法非空断言
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符
!
可以用于断言操作对象是非 null 和非 undefined 类型。我们对比下
ES5
的代码我们可以看出来
!
可以帮助我们过滤null
和undefined
类型,也就是说,编译器会默认我们只会传来string
类型的数据,所以可以赋值为str1
但变成
ES5
后!
会被移除,所以当传入null
的时候,还是会打出null
确定赋值断言
在
TS
2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个!
号,以告诉TS
该属性会被明确赋值。双重断言
断言失效后,可能会用到,但一般情况下不会使用
失效的情况:基础类型不能断言为接口
类型守卫
类型守卫:是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。
我个人的感觉是,类型守卫就是你可以设置多种类型,但我默认你是什么类型的意思
目前,常有的类型守卫共有 4 种:in 关键字、typeof 关键字、instanceof和类型谓词(is)
in 关键字
用于判断这个属性是那个里面的
typeof 关键字
用于判断基本类型,如 string | number 等
instanceof 关键字
用于判断一个实例是不是构造函数,或使用类的时候
类型谓词(is)
两者的区别
通过上面的介绍,我们可以发现
断言
与类型守卫
的概念非常相似,都是确定参数的类型,但断言
更加霸道,它是直接告诉编辑器,这个参数就是这个类型,而类型守卫更像确定这个参数具体是什么类型。(个人理解,有不对的地方欢迎指出~)类型别名、接口
类型别名(type)
类型别名:也就是
type
,用来给一个类型起个新名字接口(interface)
接口:在面向对象语言中表示行为抽象,也可以用来描述对象的形状。
使用interface关键字来定义接口
对象的形状
接口可以用来描述
对象
,主要可以包括以下数据:可读属性
、只读属性
、任意属性
继承
继承:与类一样,接口也存在继承属性,也是使用
extends
字段函数类型接口
同时,可以定义函数和类,加
new
修饰的是类,不加 new 的是函数tips: 这就是所谓的类型体操吧。。。
type 和 interface 的区别
通过上面的学习,我们发现
类型别名
和接口
非常相似,可以说在大多数情况下,type
与interface
是等价的但在一些特定的场景差距还是比较大的,接下来逐个来看看
基础数据类型
type
和interface
都可以定义 对象 和 函数type
可以定义其他数据类型,如字符串、数字、元祖、联合类型等,而interface
不行扩展
interface
可以扩展type
,type
也可以扩展为interface
,但两者实现扩展的方式不同。interface
是通过extends
来实现type
是通过&
来实现重复定义
interface
可以多次被定义,并且会进行合并,但type
不行联合类型(Union Types)
联合类型(Union Types): 表示取值可以为多种类型中的一种,未赋值时联合类型上只能访问两个类型共有的属性和方法,如:
从上面看
setInfo
接收一个name
,而name
可以接收string
或number
类型,那么这个参数便是联合类型可辨识联合
可辨识联合:包含三个特点,分别是
可辨识
、联合类型
、类型守卫
,这种类型的本质是:结合联合类型和字面量类型的一种类型保护方法。
如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
也就是上面一起结合使用,这里写个小例子:
定义了
A
、B
、C
三次接口,但这三个接口都包含type
属性,那么type
就是可辨识的属性
,而其他属性只跟特性的接口相关。然后通过可辨识属性
type
,才能使用其相关的属性泛型
泛型:Generics,是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
也就是说,泛型是允许同一个函数接受不同类型参数的一种模版,与
any
相比,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型(PS:泛型是整个 TS 的重点,也是难点,请多多注意~)为什么需要泛型
我们先看看一个例子:
上述的例子我们发现,在
calcArray
中传任何类型的参数,返回的数组都是any
类型由于我们不知道传入的数据是什么,所以返回的数据也为
any的数组
但我们现在想要的效果是:无论我们传什么类型,都能返回对应的类型,针对这种情况怎么办?所以此时
泛型
就登场了泛型语法
我们先用泛型对上面的例子进行改造下,
经过上面的案例,我们发现传入的
字符串
、数字
、对象
,都能返回对应的类型,从而达到我们的目的,接下来我们再看看泛型语法
:第一次看到这个
<T>
我们是不是很懵,实际上这个T
就是传递的类型,从上述的例子来看,这个<T>
就是<string>
,要注意一点,这个<string>
实际上是可以省略的,因为 TS 具有类型推论,可以自己推断类型tips:大概理解一下,泛型就是不明确指定是什么具体的类型,但是规定了都是
T
类型(这是一个可变的,所以说是没有明确指明的),这个规定的类型在需要使用的时候进行“占位”,实际上把'string'
和'number'
字符串当作参数、甚至直接使用1
2
3
...这样来区分也未尝不可多类型传参
我们有多个未知的类型占位,我们可以定义任何的字母来表示不同的参数类型
泛型接口
定义接口的时候,我们也可以使用泛型
泛型类
同样泛型也可以定义类
泛型类型别名
泛型默认参数
所谓默认参数,是指定类型,如默认值一样,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。
泛型常用字母
用常用的字母来表示一些变量的代表:
常用技巧
在 TS 中有许多关键字和工具类型,在使用上,需要注意泛型上的应用,有的时候结合起来可能就有一定的问题
在此特别需要注意
extends
、typeof
、Partial
、Record
、Exclude
、Omit
这几个工具类型extends
extends:检验是否拥有其属性 在这里,举个例子,我们知道
字符串
和数组
拥有length
属性,但number
没有这个属性。上述的
calcArray
的作用只是获取data的数量
,但此时在TS
中会报错,这是因为TS 不确定传来的属性是否具备 length 这个属性,毕竟每个属性都不可能完全相同那么这时该怎么解决呢?
我们已经确定,要拿到传过来数据的
length
,也就是说传过来的属性必须具备length
这个属性,如果没有,则不让他调用这个方法。换句话说,
calcArray
需要具备检验属性的功能,对于上述例子就是检验是否有length
的功能,这是我们就需要extends这个属性帮我们去鉴定:可以看出
calcArray(2)
会报错,这是因为number
类型并不具备length
这个属性typeof
typeof 关键字:我们在类型保护的时候讲解了 typeof 的作用,除此之外,这个关键字还可以实现推出类型,如下图,可以推断中 Props 包含的类型
keyof
keyof 关键字: 可以获取一个对象接口的所有
key
值,可以检查对象上的键是否存在索引访问操作符
索引访问操作符:通过
[ ]
操作符可进行索引访问,可以访问其中一个属性in
in:映射类型, 用来映射遍历枚举类型
infer
infer:可以是使用为条件语句,可以用
infer
声明一个类型变量并且对它进行使用。如Partial
Partial 语法:
Partial<T>
作用:将所有属性变为可选的 ?从上述代码上来看,name 和 age 属于必填,对于 info 来说必须要设置 name 和 age 属性才行,但对于 info1 来说,只要是个对象就可以,至于是否有 name、 age 属性并不重要
Required
Required 语法:
Required<T>
作用:将所有属性变为必选的,与Partial
相反Readonly
Readonly 语法:
Readonly<T>
作用:将所有属性都加上 readonly 修饰符来实现。也就是说无法修改从上述代码上来看,
Readonly
修饰后,属性无法再次更改,智能使用Record
Record 语法:Record<K extends keyof any, T>
作用:将
K
中所有的属性的值转化为T
类型。从上述代码上来看,
InfoProps
的属性分别包含Props
的属性需要注意的一点是:
K extends keyof any
其类型可以是:string
、number
、symbol
Pick
Pick 语法:Pick<T, K extends keyof T>
作用:将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
从上述代码上来看,
Props
原本属性包括name
、age
、sex
三个属性,通过 Pick我们吧name
和age
挑了出来,所以不需要sex
属性Exclude
Exclude 语法:Exclude<T, U>
作用:将 T 类型中的 U 类型剔除。
从上述代码上来看,我们比较了下类型上的,当 T 中有 U 就会剔除对应的属性,如果 U 中又的属性 T 中没有,或 T 和 U 刚好一样的情况都会返回 nerver,且对象永远返回
nerver
Extra
Extra 语法:Extra<T, U>
作用:将 T 可分配给的类型中提取 U。与 Exclude相反
Omit
Omit 语法:Omit<T, U>
作用:将已经声明的类型进行属性剔除获得新类型
与 Exclude的区别:Omit 返回的是新的类型,原理上是在
Exclude
之上进行的,Exclude
是根据自类型返回的NonNullable
NonNullable 语法:
NonNullable<T>
作用:从 T 中排除null
和undefined
ReturnType
ReturnType 语法:
ReturnType<T>
作用:用于获取 函数 T 的返回类型。
从上述代码上来看, ReturnType 可以接受 any 和 never 类型,原因是这两个类型属于顶级类型,包含函数
Parameters
Parameters:
Parameters<T>
作用:用于获取 获取函数类型的参数类型End
参考:
小结
到此,有关
TS
的知识就已经说完了,相信掌握了这些知识,你一定会对TS
有更深的理解,这篇文章按照自己的理解,进行分类,个人觉得这样的分类比较合理,如果有什么更好的建议,欢迎在评论区指出~想到自己刚接触
TS
的时候,是有点抵触的,但随着时间的推移,发现TS
真的很香,并且TS
也不算是很难,只要你花费一定的时间,在结合与项目,你就会发现真香定律相信这篇文章已经极大程度的解决了TS相关的代码,希望这篇文章能让你迅速掌握TS,喜欢的点个赞👍🏻支持下吧(● ̄(エ) ̄●)
作者:小杜杜 链接:https://juejin.cn/post/7088304364078497800 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。