You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classAnimal{move(distanceInMeters: number=0){console.log(`Animal moved ${distanceInMeters}m.`)}}classDogextendsAnimal{bark(){console.log('Woof! Woof!')}}constdog=newDog()dog.bark()dog.move(10)dog.bark()
但与 any 不同的是, unknown 类型的变量不允许被 any 或 unknown 以外的变量赋值,也不允许执行 unknown 类型变量的方法
letnotSure: unknown='sisterAn'letnotSure1: unknown='Hello'letany1: any=12letnum: number=12notSure=notSure1notSure=any1num=notSure// error: Type 'unknown' is not assignable to type 'number'.notSure.toLowerCase()// error: Object is of type 'unknown'.
letvoid1: voidletnull1: null=nullletund1: undefined=undefinedletvoid2: voidvoid1=void2void1=und1void1=null1// Type 'null' is not assignable to type 'void'.
any、unknown、never、void 区别
1. 定义
any :用于描述任意类型的变量,不作任何约束,编译时会跳过对其的类型检查
unknown :表示未知类型,即写代码的时候还不知道具体会是怎样的数据类型
never :永不存在的值的类型,常用于表示永不能执行到终点的函数返回值,例如抛出异常或函数中执行无限循环的代码(死循环)的函数返回值类型
void :表示无任何类型,没有类型,例如没有返回值的函数的返回值类型
any 与 unknown 的区别:
unknown 与 any 类似,但使用前必须进行断言或守卫
never 与 void 的区别:
用于函数时, never 表示函数用于执行不到返回值那一步(抛出异常或死循环)的返回值类型,即永不存在的值的类型,而 void 则表示没有返回值,不返回或返回 undefined
2. 使用
any 类型导致问题太多了,如类型污染,使用不存在的属性或方法而不报错等,而且不不方便后期维护,所以,建议能不用 any 就不用 any ,但是如果声明时并不确定具体的类型,则可以使用 unknown 代替,在使用时用类型断言或类型守卫进行类型收缩
never 类型用于表示永不存在的值的类型,所以常用于构造条件类型来组合出更灵活的类型定义
// never: 从未出现的值的类型// 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y// 构造条件类型 : T extends U ? X : YtypeExclude<T,U>=TextendsU ? never : T// 相当于: type A = 'a'typeA=Exclude<'x'|'a','x'|'y'|'z'>
使用 interface 来定义对象类型,使用类型别名来处理函数签名、联合类型、工具类型等等。这同样也代表了你对这两个工具的理解:interface 就是描述对象对外暴露的接口,其不应该具有过于复杂的类型逻辑,最多局限于泛型约束与索引类型这个层面。而 type alias 就是用于将一组类型的重命名,或是对类型进行复杂编程。
// 接口扩展interfaceSister{sex: number}interfaceSisterAnextendsSister{sex: string}// index.ts(5,11): error TS2430: Interface 'SisterAn' incorrectly extends interface 'Sister'.// Types of property 'sex' are incompatible.// Type 'string' is not assignable to type 'number'.
// 交叉类型typeSister1={sex: number}typeSister2={sex: string}typeSisterAn=Sister1&Sister2// 不报错,此时的 SisterAn 是一个'number & string'类型,也就是 never
为什么要使用 TypeScript ? TypeScript 相对于 JavaScript 的优势是什么?
为什么要使用 Typescript?
在没有
Typescript
以前,大部分项目都是使用原生Javascript
开发。而Javascript
天生是一门"灵活"的语言。所谓所谓"灵活",表现在:而这些灵活通常导致了 JavaScript 代码的肆无忌惮,比如拿数字和数组做求和运算,给函数传入不符合预期的参数等等而这些显而易见的问题编码阶段不会有任何错误提示。
在大型项目中,一个类型"小改动"可能会导致很多处代码需要跟着调整,而这些需要调整的地方在"小改动"前后可能不会有任何报错提示,开发者只能靠肉眼排查,很难且容易遗漏。
我们使用
Typescript
的主要目的就是【类型安全】(type-safe),借助类型声明避免程序做错误的事情。下图是某错误处理平台收集统计的 JavaScript Top10 错误,其中 7 个 TypeError,1 个 ReferenceError:
而这 8 种问题,我们都能用 TypeScript 在编码早期及时应对
TypeScript 相对于 JavaScript 的优势是什么?
1. TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目,增加了代码的可读性和可维护性
尤其是在第三方开源库中(例如组件库),类型系统尤为重要,现在很多项目都是用 TypeScript 写的,如果依赖的库没有 TypeScript 声明,在调用时就会传递大量类型为 any 的值,最终影响项目自身使用 TypeScript 应该获得的价值(强类型推导)。
因此在开发设计第三方库时,大都会使用 TypeScript 声明。一个库如果足够热门的话,你不做 TypeScript 声明也会有热心用户做一个发布出来的。
2. TypeScript 是一门静态类型、弱类型的语言,它是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性
类型系统按照「类型检查的时机」来分类,可以分为:
JavaScript 就是一门解释型语言,没有编译阶段,所以它是动态类型:
TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型,这段 TypeScript 代码在编译阶段就会报错了:
另外,得益于 TypeScript 强大的 类型推论,上面的代码并没有手动声明变量
foo
的类型,但在变量初始化时自动推论出它是一个number
类型:以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的
所以,TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以它们都是弱类型
3. TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力
TypeScript 增强了编辑器(IDE)的功能,包括代码补全、接口提示、跳转到定义、代码重构等,这在很大程度上提高了开发效率。给开发 TypeScript 项目、中小型项目中迁移 TypeScript 提供了便捷
4. TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)
TypeScript 坚持与 ECMAScript 标准同步发展,并推进了很多 ECMAScript 语法提案,比如可选链操作符(
?.
)、空值合并操作符(??
)、Throw 表达式、正则匹配索引等5. TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript
在老 JavaScript 项目中,如果你想使用 TypeScript,可以使用 TypeScript 编写新文件,老的 JavaScript 文件可以继续使用
参考链接
TypeScript 的主要特点是什么?
Typescript 的数据类型有哪些?
typescript 和 javascript 几乎一样,拥有相同的数据类型,另外在 javascript 基础上提供了更加实用的类型供开发使用
在开发阶段,可以为明确的变量定义为某种类型,这样 typescript 就能在编译阶段进行类型检查,当类型不合符预期结果的时候则会出现错误提示。
typescript 的数据类型主要有如下:
boolean
布尔类型
number
数字类型,和
javascript
一样,typescript
的数值类型都是浮点数,可支持二进制、八进制、十进制和十六进制进制表示:
string
字符串类型,和
JavaScript
一样,可以使用双引号("
)或单引号('
)表示字符串作为超集,当然也可以使用模版字符串``进行包裹,通过 ${} 嵌入变量
array
数组类型,跟
javascript
一致,通过[]
进行包裹,有两种写法:方式一:元素类型后面接上
[]
方式二:使用数组泛型,
Array<元素类型>
:tuple 元组
元祖类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
赋值的类型、位置、个数需要和定义(生明)的类型、位置、个数一致
enum 枚举
enum
类型是对 JavaScript 标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字any
可以指定任何类型的值,在编程阶段还不清楚类型的变量指定一个类型,不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,这时候可以使用
any
类型使用
any
类型允许被赋值为任意类型,甚至可以调用其属性、方法定义存储各种类型数据的数组时,示例代码如下:
null 和 和 undefined
在
JavaScript
中null
表示 "什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,而undefined
表示一个没有设置值的变量默认情况下
null
和undefined
是所有类型的子类型, 就是说你可以把null
和undefined
赋值给number
类型的变量但是
ts
配置了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自void
用于标识方法返回值的类型,表示该方法没有返回值。
never
never
是其他类型 (包括null
和undefined
)的子类型,可以赋值给任何类型,代表从不会出现的值但是没有类型是 never 的子类型,这意味着声明
never
的变量只能被never
类型所赋值。never
类型一般用来指定那些总是会抛出异常、无限循环object
对象类型,非原始类型,常见的形式通过
{}
进行包裹总结
和
javascript
基本一致,也分成:在基础类型上,
typescript
增添了void
、any
、emum
等原始类型对 TypeScript 中接口的理解?应用场景?
一、是什么
接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法
简单来讲,一个接口所描述的是一个对象相关的属性和方法,但并不提供具体创建此对象实例的方法
typescript
的核心功能之一就是对类型做检测,虽然这种检测方式是"鸭式辨型法",而接口的作用就是为为这些类型命名和为你的代码或第三方代码定义一个约定二、使用方式
接口定义如下:
例如有一个函数,这个函数接受一个
User
对象,然后返回这个User
对象的name
属性:可以看到,参数需要有一个
user
的name
属性,可以通过接口描述user
参数的结构这些属性并不一定全部实现,上述传入的对象必须拥有
name
和age
属性,否则typescript
在编译阶段会报错,如下图:如果不想要
age
属性的话,这时候可以采用可选属性?
,如下表示:这时候
age
属性则可以是number
类型或者undefined
类型有些时候,我们想要一个属性变成只读属性,在
typescript
只需要使用readonly
声明,如下:当我们修改属性的时候,就会出现警告,如下所示:
这是属性中有一个函数,可以如下表示:
如果传递的对象不仅仅是上述的属性,这时候可以使用:
接口还能实现继承,如下图:
也可以继承多个,父类通过逗号隔开,如下:
三、应用场景
例如在
javascript
中定义一个函数,用来获取用户的姓名和年龄:如果多人开发的都需要用到这个函数的时候,如果没有注释,则可能出现各种运行时的错误,这时候就可以使用接口定义参数变量:
包括后面讲到类的时候也会应用到接口
参考文献
对 TypeScript 中类的理解?应用场景?
一、是什么
类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础
传统的面向对象语言基本都是基于类的,
JavaScript
基于原型的方式让开发者多了很多理解成本在
ES6
之后,JavaScript
拥有了class
关键字,虽然本质依然是构造函数,但是使用起来已经方便了许多但是
JavaScript
的class
依然有一些特性还没有加入,比如修饰符和抽象类TypeScript
的class
支持面向对象的所有特性,比如 类、接口等二、使用方式
定义类的关键字为
class
,后面紧跟类名,类可以包含以下几个模块(类的数据成员):如下例子:
继承
类的继承使用过
extends
的关键字Dog
是一个 派生类,它派生自Animal
基类,派生类通常被称作子类,基类通常被称作 超类Dog
类继承了Animal
类,因此实例dog
也能够使用Animal
类move
方法同样,类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写,通过
super
关键字是对父类的直接引用,该关键字可以引用父类的属性和方法,如下:修饰符
可以看到,上述的形式跟
ES6
十分的相似,typescript
在此基础上添加了三种修饰符:私有修饰符
只能够在该类的内部进行访问,实例对象并不能够访问
并且继承该类的子类并不能访问,如下图所示:
受保护修饰符
跟私有修饰符很相似,实例对象同样不能访问受保护的属性,如下:
有一点不同的是
protected
成员在子类中仍然可以访问除了上述修饰符之外,还有只读修饰符
只读修饰符
通过
readonly
关键字进行声明,只读属性必须在声明时或构造函数里被初始化,如下:除了实例属性之外,同样存在静态属性
静态属性
这些属性存在于类本身上面而不是类的实例上,通过
static
进行定义,访问这些属性需要通过 类型.静态属性 的这种形式访问,如下所示:上述的类都能发现一个特点就是,都能够被实例化,在
typescript
中,还存在一种抽象类抽象类
抽象类做为其它派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法,如下所示:这种类并不能被实例化,通常需要我们创建子类去继承,如下:
三、应用场景
除了日常借助类的特性完成日常业务代码,还可以将类(class)也可以作为接口,尤其在
React
工程中是很常用的,如下:由于组件需要传入
props
的类型Props
,同时有需要设置默认props
即defaultProps
,这时候更加适合使用class
作为接口先声明一个类,这个类包含组件
props
所需的类型和初始值:当我们需要传入
props
类型的时候直接将Props
作为接口传入,此时Props
的作用就是接口,而当需要我们设置defaultProps
初始值的时候,我们只需要:Props
的实例就是defaultProps
的初始值,这就是class
作为接口的实际应用,我们用一个class
起到了接口和设置初始值两个作用,方便统一管理,减少了代码量参考文献
对 TypeScript 中函数的理解?与 JavaScript 函数的区别?
一、是什么
函数是
JavaScript
应用程序的基础,帮助我们实现抽象层、模拟类、信息隐藏和模块在
TypeScript
里,虽然已经支持类、命名空间和模块,但函数仍然是主要定义行为的方式,TypeScript
为JavaScript
函数添加了额外的功能,丰富了更多的应用场景函数类型在
TypeScript
类型系统中扮演着非常重要的角色,它们是可组合系统的核心构建块二、使用方式
跟
javascript
定义函数十分相似,可以通过funciton
关键字、箭头函数等形式去定义,例如下面一个简单的加法函数:上述只定义了函数的两个参数类型,这个时候整个函数虽然没有被显式定义,但是实际上
TypeScript
编译器是能够通过类型推断到这个函数的类型,如下图所示:当鼠标放置在第三行
add
函数名的时候,会出现完整的函数定义类型,通过:
的形式来定于参数类型,通过=>
连接参数和返回值类型当我们没有提供函数实现的情况下,有两种声明函数类型的方式,如下所示:
当存在函数重载时,只能使用方式一的形式
可选参数
当函数的参数可能是不存在的,只需要在参数后面加上
?
代表参数可能不存在,如下:这时候参数
b
可以是number
类型或者undefined
类型,即可以传一个number
类型或者不传都可以剩余类型
剩余参数与
JavaScript
的语法类似,需要用...
来表示剩余参数如果剩余参数
rest
是一个由number
类型组成的数组,则如下表示:函数重载
允许创建数项名称相同但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力
关于
typescript
函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用|
操作符或者?
操作符,把所有可能的输入类型全部包含进去,用于具体实现这里的函数重载也只是多个函数的声明,具体的逻辑还需要自己去写,
typescript
并不会真的将你的多个重名function
的函数体进行合并例如我们有一个 add 函数,它可以接收
string
类型的参数进行拼接,也可以接收number
类型的参数进行相加,如下:三、区别
从上面可以看到:
参考文献
对 TypeScript 中泛型的理解?应用场景?
一、是什么
泛型程序设计(generic programming)是程序设计语言的一种风格或范式
泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在
typescript
中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性假设我们用一个函数,它可接受一个
number
参数并返回一个number
参数,如下写法:如果我们打算接受一个
string
类型,然后再返回string
类型,则如下写法:上述两种编写方式,存在一个最明显的问题在于,代码重复度比较高
虽然可以使用
any
类型去替代,但这也并不是很好的方案,因为我们的目的是接收什么类型的参数返回什么类型的参数,即在运行时传入参数我们才能确定类型这种情况就可以使用泛型,如下所示:
可以看到,泛型给予开发者创造灵活、可重用代码的能力
二、使用方式
泛型通过
<>
的形式进行表述,可以声明:函数声明
声明函数的形式如下:
定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型
T
和 泛型U
:接口声明
声明接口的形式如下:
那么当我们想传入一个 number 作为参数的时候,就可以这样声明函数:
类声明
使用泛型声明类的时候,既可以作用于类本身,也可以作用与类的成员函数
下面简单实现一个元素同类型的栈结构,如下所示:
使用方式如下:
如果上述只能传递
string
和number
类型,这时候就可以使用<T extends xx>
的方式猜实现约束泛型,如下所示:除了上述的形式,泛型更高级的使用如下:
例如要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值
这时候就设计到泛型的索引类型和约束类型共同实现
索引类型、约束类型
索引类型
keyof T
把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示:上述为什么需要使用泛型约束,而不是直接定义第一个参数为
object
类型,是因为默认情况object
指的是{}
,而我们接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如T extends object
使用如下图所示:
多类型约束
例如如下需要实现两个接口的类型约束:
可以创建一个接口继承上述两个接口,如下:
正确使用如下:
通过泛型约束就可以达到多类型约束的目的
三、应用场景
通过上面初步的了解,后述在编写
typescript
的时候,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性的时候,这种情况下就可以使用泛型灵活的使用泛型定义类型,是掌握
typescript
必经之路参考文献
四、自己的理解
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
比如 Vue3 的 ref 的类型属性就是泛型:
有时我们可能想为 ref 内的值指定一个更复杂的类型,可以通过使用 Ref 这个类型:
或者,在调用 ref() 时传入一个泛型参数,来覆盖默认的推导行为:
如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型:
any、never、unknown 和 void 有什么区别?
any
any
类型用于描述一个我们根本不知道类型的变量,或者说可以是任意类型的变量,不作任何约束,编译时会跳过对其的类型检查unknown
unknown
表示未知类型,即写代码的时候还不知道具体会是怎样的数据类型,是typescript 3.0
中引入的新类型, 与any
类似,所有类型都可以分配给unknown
类型但与
any
不同的是,unknown
类型的变量不允许被any
或unknown
以外的变量赋值,也不允许执行unknown
类型变量的方法这种限制有很强的防御性,但如果我们要对未知类型执行某些操作,也不是没有办法
方式一:使用类型断言缩小未知范围
方式二:使用类型守卫进行类型收缩
我们仅在
notSure
为string
类型时,才执行toLowerCase
方法,TypeScript 编译器会理解这一点,并假设类型总结: any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。
never
never
,永不存在的值的类型,是 typescript 2.0 中引入的新类型,那什么是永不存在的类型,我们知道变量一旦声明,都会默认初始化为undefined
,也不是永不存在的值,但其实有一些场景,值会永不存在,例如,那些总是会抛出异常或函数中执行无限循环的代码(死循环)的函数返回值类型变量也可以声明为
never
类型,因为它是永不存在值的类型,所以任何类型都不能赋值给never
类型(除了never
本身之外)。 即使any
也不可以赋值给never
void
void
某种程度上来说正好与any
相反,表示无任何类型,没有类型,如果是函数则应没有返回值或者返回undefined
:也可以声明一个
void
类型的变量,不过你只能为它赋予undefined
、null
(注意,"strictNullChecks": true
时会报错)和void
类型的值any、unknown、never、void 区别
1. 定义
any
:用于描述任意类型的变量,不作任何约束,编译时会跳过对其的类型检查unknown
:表示未知类型,即写代码的时候还不知道具体会是怎样的数据类型never
:永不存在的值的类型,常用于表示永不能执行到终点的函数返回值,例如抛出异常或函数中执行无限循环的代码(死循环)的函数返回值类型void
:表示无任何类型,没有类型,例如没有返回值的函数的返回值类型any 与 unknown 的区别:
unknown
与any
类似,但使用前必须进行断言或守卫never 与 void 的区别:
用于函数时,
never
表示函数用于执行不到返回值那一步(抛出异常或死循环)的返回值类型,即永不存在的值的类型,而void
则表示没有返回值,不返回或返回undefined
2. 使用
any
类型导致问题太多了,如类型污染,使用不存在的属性或方法而不报错等,而且不不方便后期维护,所以,建议能不用any
就不用any
,但是如果声明时并不确定具体的类型,则可以使用unknown
代替,在使用时用类型断言或类型守卫进行类型收缩never
类型用于表示永不存在的值的类型,所以常用于构造条件类型来组合出更灵活的类型定义void
常用于表示函数没有返回值 (可以被赋值为 null 和 undefined)参考原文
使用 TS 实现一个判断传入参数是否是数组类型的方法?
unknown 用于变量类型不确定,但肯定可以确定的情形下,比如下面这个示例中,参数总归会有个值,根据这个值的类型进行不同的处理,这里使用 unknown 替代 any 则会更加类型安全。
interface 与 type 异同点,如何选择?
相同点:
区别:
类型别名的右边可以是任何类型,包括基本类型、元祖、类型表达式( & 或 | 等);而在接口声明中,右边必须为变量结构。例如,下面的类型别名就不能转换成接口
如何选择
建议优先选择接口,当接口不满足时再使用类型别名
及格线
&
)。interface 和 type 可以混合扩展,也就是说 interface 可以扩展 type,type 也可以扩展 interface优秀回答
使用 interface 来定义对象类型,使用类型别名来处理函数签名、联合类型、工具类型等等。这同样也代表了你对这两个工具的理解:interface 就是描述对象对外暴露的接口,其不应该具有过于复杂的类型逻辑,最多局限于泛型约束与索引类型这个层面。而 type alias 就是用于将一组类型的重命名,或是对类型进行复杂编程。
如何选择 Interface 、 Type
虽然 官方 中说几乎接口的所有特性都可以通过类型别名来实现,但建议优先选择接口,接口满足不了再使用类型别名,在 typescript 官网 Preferring Interfaces Over Intersections 有说明,具体内容如下:
简单的说,接口更加符合 JavaScript 对象的工作方式,简单的说明下,当出现属性冲突时:
Interface 和 Type 的不同点
由于 Type 定义的实际是一个别名,所以 Type 可以描述一些基本类型、联合类型和元组的别名。
Interface 可以重复定义,并将合并所有声明的属性为单个接口。而 Type 不可重复定义。
Type 可以使用 in 关键字动态生成属性,而 Interface 的索引值必须是 string 或 number 类型,所以 Interface 并不支持动态生成属性。
什么是类型谓词
收窄类型,类型守卫。
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。
换句话说:类型守卫是运行时检查,确保一个值在所要类型的范围内
目前主要有四种的方式来实现类型保护:
对 TypeScript 中高级类型的理解?有哪些?
一、是什么
除了
string
、number
、boolean
这种基础类型外,在typescript
类型声明中还存在一些高级的类型应用这些高级类型,是
typescript
为了保证语言的灵活性,所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景二、有哪些
常见的高级类型有如下:
交叉类型
通过
&
将多个类型合并为一个类型,包含了所需的所有类型的特性,本质上是一种并的操作语法如下:
适用于对象合并场景,如下将声明一个函数,将两个对象合并成一个对象并返回:
联合类型
联合类型的语法规则和逻辑 "或" 的符号一致,表示其类型为连接的多个类型中的任意一个,本质上是一个交的关系
语法如下:
例如
number
|string
|boolean
的类型只能是这三个的一种,不能共存如下所示:
类型别名
类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型
可以使用
type SomeName = someValidTypeAnnotation
的语法来创建类型别名:此外类型别名可以是泛型:
也可以使用类型别名来在属性里引用自己:
可以看到,类型别名和接口使用十分相似,都可以描述一个对象或者函数
两者最大的区别在于,
interface
只能用于定义对象类型,而type
的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛类型索引
keyof
类似于Object.keys
,用于获取一个接口中 Key 的联合类型。类型约束
通过关键字
extend
进行约束,不同于在class
后使用extends
的继承作用,泛型内使用的主要作用是对泛型加以约束类型约束通常和类型索引一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用
extends
和keyof
进行约束。映射类型
通过
in
关键字做类型的映射,遍历已有接口的key
或者是遍历联合类型,如下例子:上述的结构,可以分成这些步骤:
所以最终
ReadOnlyObj
的接口为下述:条件类型
条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。
上面的意思就是,如果 T 是 U 的子集,就是类型 X,否则为类型 Y
三、总结
可以看到,如果只是掌握了
typeScript
的一些基础类型,可能很难游刃有余的去使用typeScript
,需要了解一些typescript
的高阶用法并且
typescript
在版本的迭代中新增了很多功能,需要不断学习与掌握参考文献
对 TypeScript 装饰器的理解?应用场景?
TypeScript 中的 Declare 关键字有什么作用?
我们知道所有的 JavaScript 库/框架都没有 TypeScript 声明文件,但是我们希望在 TypeScript 文件中使用它们时不会出现编译错误。为此,我们使用 declare 关键字。在我们希望定义可能存在于其他地方的变量的环境声明和方法中,可以使用 declare 关键字。
例如,假设我们有一个名为 myLibrary 的库,它没有 TypeScript 声明文件,在全局命名空间中有一个名为 myLibrary 的命名空间。如果我们想在 TypeScript 代码中使用这个库,我们可以使用以下代码:
TypeScript 运行时将把 myLibrary 变量赋值为任意类型(any)。这是一个问题,我们不会得到智能感知在设计时,但我们将能够使用库在我们的代码。
对 TypeScript 中命名空间与模块的理解?区别?
一、模块
TypeScript
与ECMAScript
2015 一样,任何包含顶级import
或者export
的文件都被当成一个模块相反地,如果一个文件不带有顶级的
import
或者export
声明,那么它的内容被视为全局可见的例如我们在在一个
TypeScript
工程下建立一个文件1.ts
,声明一个变量a
,如下:然后在另一个文件同样声明一个变量
a
,这时候会出现错误信息提示重复声明
a
变量,但是所处的空间是全局的如果需要解决这个问题,则通过
import
或者export
引入模块系统即可,如下:在
typescript
中,export
关键字可以导出变量或者类型,用法与es6
模块一致,如下:通过
import
引入模块,如下:二、命名空间
命名空间一个最明确的目的就是解决重名问题
命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的
这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中
TypeScript
中命名空间使用namespace
来定义,语法格式如下:以上定义了一个命名空间
SomeNameSpaceName
,如果我们需要在外部可以调用SomeNameSpaceName
中的类和接口,则需要在类和接口添加export
关键字使用方式如下:
命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,如下:
编译成
js
如下:三、区别
参考文献
如何在 React 项目中应用 TypeScript?
一、前言
单独的使用
TypeScript
并不会导致学习成本很高,但是绝大部分前端开发者的项目都是依赖于框架的例如与
Vue
、React
这些框架结合使用的时候,会有一定的门槛使用
TypeScript
编写React
代码,除了需要TypeScript
这个库之外,还需要安装@types/react
、@types/react-dom
至于上述使用
@types
的库的原因在于,目前非常多的JavaScript
库并没有提供自己关于TypeScript
的声明文件所以,
ts
并不知道这些库的类型以及对应导出的内容,这里@types
实际就是社区中的DefinitelyTyped
库,定义了目前市面上绝大多数的JavaScript
库的声明所以下载相关的
JavaScript
对应的@types
声明时,就能够使用使用该库对应的类型定义二、使用方式
在编写
React
项目的时候,最常见的使用的组件就是:无状态组件
主要作用是用于展示
UI
,如果使用js
声明,则如下所示:但这时候
ts
会出现报错提示,原因在于没有定义porps
类型,这时候就可以使用interface
接口去定义porps
即可,如下:但是我们都知道
props
里面存在children
属性,我们不可能每个porps
接口里面定义多一个children
,如下:更加规范的写法是使用
React
里面定义好的FC
属性,里面已经定义好children
类型,如下:React.FC 显式地定义了返回类型,其他方式是隐式推导的
React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全
React.FC 为 children 提供了隐式的类型(ReactElement | null)
有状态组件
可以是一个类组件且存在
props
和state
属性如果使用
TypeScript
声明则如下所示:上述通过泛型对
props
、state
进行类型定义,然后在使用的时候就可以在编译器中获取更好的智能提示关于
Component
泛型类的定义,可以参考下 React 的类型定义文件node_modules/@types/React/index.d.ts
,如下所示:从上述可以看到,
state
属性也定义了可读类型,目的是为了防止直接调用this.state
更新状态*受控组件
受控组件的特性在于元素的内容通过组件的状态
state
进行控制由于组件内部的事件是合成事件,不等同于原生事件,
例如一个
input
组件修改内部的状态,常见的定义的时候如下所示:常用
Event
事件对象类型:T
接收一个DOM
元素类型三、总结
上述只是简单的在
React
项目使用TypeScript
,但在编写React
项目的时候,还存在hooks
、默认参数、以及store
等等......TypeScript
在框架中使用的学习成本相对会更高,需要不断编写才能熟练參考文献
如何在 Vue 项目中应用 TypeScript?
如何将项目从 JavaScript 迁移到 TypeScript?
将一个 JavaScript 项目迁移到 TypeScript 的过程中,你可以很好地避免这些问题,专注于 TypeScript 本身的逻辑编写。同时,由于你对这个项目原本的脉络已经相当熟悉,包括其中的各种数据与状态,因此在编写类型时也可以更加得心应手。
但要注意的是,从 JavaScript 到 TypeScript 的迁移也是有技巧的,可不能直接盲干,这里我们介绍一个推荐的迁移方案与它的步骤组成,供你作为实践的参考:
根据项目规模的不同,你可以考虑「全量迁移」与「渐进迁移」两个方案。注意,全量迁移并不是说空出一个月时间专门做 TS 的迁移,而是先将所有的 JS 文件改成 TS 文件,先享受到 TypeScript 的完整类型检查能力,但实际的迁移工作还没开始。而渐进迁移则是说,你可以每个月抽一周时间来搞迁移,比如说这次想改
<UserProfile ``/>
组件,就把这个组件文件后缀改成.tsx
,然后必须完成文件内的所有类型迁移。而对于渐进迁移,你也可以通过启用 allowJs 与 checkJs 配置,来先使用一部分类型检查能力。这两种方案可以根据你对项目规模的定义来选择,如果你认为这个项目还是比较庞大的,你担心牵一发动全身,那就可以选择渐进迁移。反之,如果你觉得你能 hold 住,那么完全可以一步到位!
在迁移过程中的一个大忌就是,你明明只应该补充下类型,却觉得原来的逻辑不顺眼直接顺手改掉了,或者感觉使用的 npm 包太老,顺手替换了个更潮流的包。千万不要这么做!否则如果迁移过程中哪里出现了问题,为了定位问题根源,大概率你又要将它们回退回去,甚至包括一些无辜的类型代码...,简直就是在给你自己增加工作量了。
每次迁移应该是纯粹的,在针对 TypeScript 类型的迁移过程中,不要修改任何有关逻辑的部分,计算你实在忍不住,也只能标个 TODO,下次再发起个针对逻辑优化的重构吧。
迁移的第一步,一定是基于项目的数据补充类型定义,比如页面组件的状态、接口的响应、枚举、全局状态等等等等。为什么?TypeScript 再智能,也不可能为你自动补全这些类型,而没有这些类型,它的能力就是相当局限的。大部分地方会被推断为 any / unknown 类型,导致类型检查基本失效了。只有你先完善了项目的整个类型体系,才能在后面的重构过程中,不断得到来自类型的提示与警告,从而"安全"地完成迁移流程。
同时最开始的类型补充可以是粗糙的,到处复制粘贴也没问题,毕竟你不能未卜先知哪些类型是有关联的。但随着迁移的进行,这些类型定义应该是要被不断完善,慢慢关联起来,编织成一张类型大网的。
完成类型定义补充后,你应该首先完善项目中那些工具方法,因为正常情况下,大部分的数据变化都发生在工具方法中,完成了对这些工具方法的入参与出参的定义后,所有引用了它们的组件都能从中受益。但这一步往往也需要一定时间,毕竟在数据变化的过程中你很容易发现到处都是 TS 类型报错,如果你希望更快一点点,也可以仅仅完成出入参类型的明确,对于过程中的类型日后再来完善。
如果这个项目其实是一套产品的一部分,那么推荐你可以有意识地标记那些可提供给其它项目复用的类型,并将它们的类型定义尽可能完善。这样一来,以后就算你是直接复制粘贴,也能省下一笔功夫了。
总结一下,就是这么几点:选择合适的迁移方案、避免发生逻辑改动、优先补充类型定义与完善工具方法以及有意识地沉淀通用的类型定义。使用这一套组合拳,能让你的迁移过程更加合理与稳定,同时也能帮助你更好地磨炼自己的技巧。
tsconfig.json 有什么作用?
tsconfig.json 文件是 JSON 格式的文件。
在 tsconfig.json 文件中,可以指定不同的选项来告诉编译器如何编译当前项目。
目录中包含 tsconfig.json 文件,表明该目录是 TypeScript 项目的根目录。
参考
The text was updated successfully, but these errors were encountered: