写 js 很多年,刚开始写 ts 的时候,着实有些不习惯,整体感觉就像一个落拓不羁的少年被圈在了关闭的学校一样,起初用了一段时间发现,ts 确实是学校外面一个严苛的教诲主任,当你一旦不恪守校规的时候,就会让你叫家长(报错)。缓缓的你也就会成为一个灵巧懂事的好学生了,上面大家看看怎么样能力一步一步学好
一、养成“先思考后入手”的好习惯
在以往的开发过程中,习惯总是“先想好一个大略,而后边做边想再边改”。这样的劣势是执行快,顺利的时候效率会很高,但更多的时候会一直地颠覆本人先前的想法,置信不少的人也有跟我相似的领会。
ts
是 js
的超集。意思就是在 ts
中能够间接书写 js
。在我的第一感觉里,js
就像是编译后的可执行文件,家喻户晓 ts
是强类型的语言,就像是 Java 语言。这也意味着它能无效制约开发者在开发过程中“得心应手”的水平。上面具体介绍下👇
1. 定义类型形式和扩大
-
TypeScript
中定义类型有两种形式:接口(interface
)和类型别名(type alias
)。在上面的例子中,除了语法不一样,定义的类型是一样的://interface interface PointI { x: number; y: number; } interface SetPointI {(x:number, y:number): void; } // or //type alias type PointT = { x: number; y: number; } type SetPointT = (x:number, y:number) => void;
-
接口和类型别名不仅均能够扩大,而且接口和类型别名并不互斥的,也就是说,接口能够扩大类型别名,类型别名也能够扩大接口
// interface extends type alias type PointX = {x: number;} interface PointY extends PointX {y: number;} // type alias extends interface interface Point2X{x: number;} type Point2Y = Point2X & {y: number;}
-
接口和类型别名也有差异,比方一个接口能够定义屡次,并将被视为一个接口,然而类型别名却不能够反复
接口和类型别名的选用机会:- 在定义公共 API(如编辑一个库)时应用
interface
,这样能够不便使用者继承接口; - 在定义组件属性(
Props
)和状态(State
)时,倡议应用type
,因为type
的约束性更强; type
类型不能二次编辑,而interface
能够随时扩大。
- 在定义公共 API(如编辑一个库)时应用
2. TS 反对的 JS 新个性
2.1 可选链(Optional Chaining)
-
TypeScript 3.7 实现了呼声最高的 ECMAScript 性能之一:可选链(Optional Chaining)。有了可选链后,咱们编写代码时如果遇到
null
或undefined
就能够立刻进行某些表达式的运行。可选链的外围是新的?.
运算符.obj?.prop obj?.[expr] arr?.[index] func?.(args)
-
可选链(Optional Chaining)
?.
是ES11(ES2020)
新增的个性,可选链 能够让咱们在查问具备多层级的对象时,不再须要进行冗余的各种前置校验:let age = user && user.info && user.info.getAge() let age = user?.info?.getAge?.()
- 但须要留神的是,
?.
与&&
运算符行为略有不同,&&
专门用于检测false
值,比方空字符串、0、NaN、null 和 false 等。而?.
只会验证对象是否为null
或undefined
,对于 0 或空字符串来说,并不会呈现“短路”。
2.2 可选属性
-
在面向对象语言中,接口是一个很重要的概念,它是对行为的形象,而具体如何口头须要由类去实现。TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行形象以外,也罕用于对「对象的形态(Shape)」进行形容 。
在 TypeScript 中应用interface
关键字就能够申明一个接口:interface Person { name: string; age: number; } let zhangsan: Person = { name: "zhangsan", age: 33, };
- 上述代码中,咱们申明了
Person
接口,它蕴含了两个必填的属性name
和age
。在初始化 Person 类型变量时,如果短少某个属性,TypeScript 编译器就会提醒相应的错误信息,比方: -
为了解决上述的问题,咱们能够把某个属性申明为可选的:
interface Person { name: string; age?: number; } let lisi: Person = {name: "lisi"}
2.3 空值合并运算符(Nullish coalescing Operator)
-
当空值合并运算符的左表达式不为
null
或undefined
时,不会对右表达式进行求值。const goods = {price: 0,} let goods1 = goods.price ?? '暂无报价' let goods2 = goods.jdPrice ?? '暂无报价' console.log(goods1)//0 console.log(goods2)// 暂无报价
-
与逻辑或操作符(
||
)不同,||
会在左侧操作数为false
值(例如,''
或0
)时返回右侧操作数。也就是说,如果应用||
来为某些变量设置默认值,可能会遇到意料之外的行为:const goods = {price: 0,} let goods1 = goods.price || '暂无报价' let goods2 = goods.price ?? '暂无报价' console.log(goods1)// 暂无报价 console.log(goods2)//0
3. 类型收窄
TypeScript
类型收窄就是从 宽类型 转换成 窄类型 的过程,其罕用于解决联结类型变量的场景。-
在
TypeScript
中,有许多办法能够收窄变量的类型:- 类型断言
- 类型守卫
- 双重断言
3.1 类型断言
- 类型断言有两种:
值 as 类型
or< 类型 > 值
个别咱们对立应用值 as 类型
这样的语法,因为<>
容易跟泛型语法起抵触。 -
当
TypeScript
不确定一个联结类型的变量到底是哪个类型的时候,咱们 只能拜访此联结类型的所有类型中共有的属性或办法interface rabbit { name: string; jump(): void;} interface dog { name: string; run(): void;} function isRabbit1(animal: rabbit | dog){return animal.name}
- 而有时候,咱们的确须要在还不确定类型的时候就拜访其中一个类型特有的属性或办法,如:
-
上图的例子中,获取
animal.jump
的时候会报错。此时能够应用类型断言,将animal
断言成rabbit
类型,就能够解决拜访animal.jump
时报错的问题:interface rabbit { name: string; jump(): void;} interface dog { name: string; run(): void;} function isRabbit(animal: rabbit | dog){if(typeof (animal as rabbit).jump === 'function'){return true} return false }
-
类型断言虽好,然而不能滥用,滥用之后可能会绕过编译器,然而无奈防止 运行时 的谬误。
interface rabbit { name: string; jump(): void;} interface dog { name: string; run(): void;} function rabbitJump(animal: rabbit | dog) {(animal as rabbit).jump()} const jack: dog = { name: 'Jack', run() {console.log("跑") } } rabbitJump(jack)// 运行会报错
TypeScript
编译器信赖了咱们的断言,故在调用rabbitJump()
时没有编译谬误,但因为jack
上并没有jump
办法,就会导致在运行时产生谬误。- 应用类型断言时肯定要分外小心,尽量避免断言后调用办法或援用深层属性,以缩小不必要的运行时谬误。
3.2 类型守卫
-
类型守卫次要有以下几种形式:
- typeof:用于判断
number
,string
,boolean
或symbol
四种类型; - instanceof:用于判断一个实例是否属于某个类
- in:用于判断一个属性 / 办法是否属于某个对象
- typeof:用于判断
- 能够利用
typeof
实现类型收窄和never
类型的个性做全面性查看,如上面的代码所示:
type Foo = string | number
function test(input: Foo) {if (typeof input == 'string') {// 这里 input 的类型「收紧」为 string} else if (typeof input == 'number') {// 这里 input 的类型「收紧」为 number} else {
// 这里 input 的类型「收紧」为 never
const isShow: never = input
}
}
- 能够看到,在最初的
else
分支外面,咱们把收窄为never
的input
赋值给一个显示申明的never
变量,如果所有逻辑正确,那么这里应该可能编译通过。然而如果起初有一天你的共事批改了Foo
的类型,并且遗记了批改 test 办法外面控制流的时候: - 这时候
else
分支的input
类型会被收窄为boolean
类型,导致无奈赋值给never
类型,这时就会产生一个编译谬误。 - 应用
instanceof
运算符收窄变量的类型 -
应用
in
做属性查看interface Foo {foo: string;} interface Bar {bar: string;} function test(input: Foo | Bar) {if ('foo' in input) {// 这里 input 的类型「收紧」为 Foo} else {// 这里 input 的类型「收紧」为 Bar} }
3.3 双重断言
- 类型断言并不总是能胜利,比方:
- 如上例子中的代码将会报错(类型 “Event” 到类型 “HTMLElement” 的转换可能是谬误的,因为两种类型不能充沛重叠。如果这是无意的 …),只管曾经应用了类型断言。
-
如果你依然想应用那个类型,你能够应用双重断言。首先断言成兼容所有类型的
any
,编译器将不会报错:function handler(event: Event) {const element = (event as any) as HTMLElement; // OK }
- TypeScript 是怎么确定单个断言是否足够 ? 当
S
类型是T
类型的子集,或者T
类型是S
类型的子集时,S
能被胜利断言成T
。这是为了在进行类型断言时提供额定的安全性,齐全毫无根据的断言是危险的,如果你想这么做,你能够应用any
。
4. 枚举
-
枚举是组织收集有关联变量的一种形式, 首先咱们看看一般枚举,如果你应用过其它编程语言应该会很相熟。
enum Color { Red = 1, //1 Green, //2 Blue, //3 }
- 如上,咱们定义了一个数字枚举,
Red
应用初始化为1
。其余的成员会从1
开始主动增长。换句话说,Color.Red
的值为1
,Green
为2
,Blue
为3
,Red
要是没有初始化,即从0
开始。
然而这样写就有问题了: -
当枚举某个成员非数字时,上面增长的枚举成员须要和其保持一致,这样方能防止报错
enum Color { Red, // 0 Green = "Green",// Green Blue = "Blue",// Blue }
-
一般枚举的值不会在编译阶段计算,而是保留到程序的执行阶段,咱们看看上面的例子:
enum Color { // 常量枚举 Red, purple, Green = Color.Red, Blue = 1 + 1, // 十分量枚举 yellow = Math.random(), black = 'hello'.length, }
-
上例的编译后果是:
var Color; (function(Color) {Color[(Color['Red'] = 0)] = 'Red'; Color[(Color['purple'] = 0)] = 'purple'; Color[(Color['Green'] = 0)] = 'Green'; Color[(Color['Blue'] = 2)] = 'Blue'; Color[(Color['yellow'] = Math.random())] = 'yellow'; Color[(Color['black'] = 'hello'.length)] = 'black'; })(Color || (Color = {}));
-
先让咱们聚焦
Color[(Color['Red'] = 0)] = 'Red''
这行代码,其中Color['Red'] = 0
的意思是将Color
对象里的Red
成员值设置为0
。留神,JavaScript 赋值运算符返回的值是被赋予的值(在此例子中是0
),因而下一次 JavaScript 运行时执行的代码是Color[0] = 'Red'
。意味着你能够应用Color
变量来把字符串枚举类型革新成一个数字或者是数字类型的枚举类型,如下所示:enum Color { Red, Blue = 1 + 1, yellow = Math.random(), black = 'hello'.length, } console.log(Color[0]); // 'Red' console.log(Color['Red']); // 0 console.log(Color[Color.Red]); // 'Red'
5. 高级类型
- 除了
string
、number
、boolean
这种根底类型外,咱们还应该理解一些类型申明中的一些高级用法。
5.1 extends
关键字
-
根底用法:
T extends U ? X : Y
-
示意,如果 T 能够赋值给 U(类型兼容),则返回 X,否则返回 Y;以内置的泛型接口
Extract
为例,它的实现如下:type Extract<T, U> = T extends U ? T : never
TypeScript
将应用never
类型来示意不应该存在的状态。下面的意思是,如果 T 中的类型在 U 存在,则返回,否则摈弃。- 假如咱们两个类,有三个公共的属性,能够通过
Extract
提取这三个公共属性: TypeScript
中内置了很多工具泛型,除了介绍的这些,内置的泛型在TypeScript
内置的lib.es5.d.ts
中都有定义,所以不须要任何依赖就能够间接应用。
5.2 应用 keyof
-
根底实例:
interface Foo { name: string; age: number } type T = keyof Foo // 等同于 type T = "name" | "age"
extends
常常与keyof
一起应用,例如咱们有一个getValue
办法专门用来获取对象的值,然而这个对象并不确定,咱们就能够应用extends
和keyof
进行束缚:- 当传入对象没有的
key
时,编辑器则会报错。
5.3 应用 in
in
则能够遍历枚举类型, 例如:
type Keys = "a" | "b"
type Obj = {[p in Keys]: any
} // {a: any, b: any}
keyof
产生联结类型,in
则能够遍历枚举类型, 所以他们常常一起应用。
二、好的习惯都须要缓缓养成
如果只是把握了 TypeScript 的一些根底类型,可能很难熟能生巧的去应用 TypeScript。想要很好的驾驭它,只能一直的学习和把握它。
本文只是介绍了几种罕用的实际,心愿还没接触 TypeScript
或对 TypeScript
还不太熟悉的小伙伴赶快在我的项目实际起来,致力晋升代码可维护性和开发幸福感,感激大家的浏览🙏
三、参考文档
- TypeScript 官网文档