TypeScript 的精彩功能
积木
我一路学到的技巧。
去年我开始以 JavaScript 作为我的主要编程语言。这对于之前使用 C# 和 Java 的我来说,是一个全新的体验。但过了一段时间后,我开始对类型错误感到厌烦。虽然 ESLint 可以帮助我避免拼写错误,但它并不擅长处理类型问题。此外,记住每个函数参数也增加了我的认知负担。我知道命名规则会有所帮助,但作用有限。TypeScript 帮我解决了这两个问题,现在我再也不用担心 React 组件的 prop 或函数了。
TypeScript 在 VS Code 中也得到了很好的支持,可以在编码过程中发现错误。它还会高亮显示类型,方便快速浏览。好了,就先说到这里,现在该说说我过去几个月学到的东西了。
积木
本文不会深入探讨 TypeScript 的基本语法和原理。我建议您阅读Dev.to上的以下文章:
TypeScript入门基础教程(作者:A. Sharif)
TypeScript教程:循序渐进的TypeScript学习指南(作者:Educative)
联合类型
联合类型类似于“或”运算符,它接受多个类型,但只返回所需的类型。让我们来看一个例子来更好地理解它。
有时我们需要一个函数,它接受一个数字或字符串作为参数。虽然可以用 `any` 来实现,但这很简单,只有在非常复杂的情况下才需要用到 `any`。
我们改用类型联合体吧。
const complexFunction = (paramter: string | number) => {//code here.};
我们可以使用预定义类型来实现这一点,让我举个例子来说明。假设我们有货币类型,目前只支持美元、欧元和英镑。
type Currency = 'USD' | 'EUR' | 'GBP';
现在,任何分配给这种类型的变量,都只能是这三个值之一。这样就降低了意外赋值错误的概率。
交叉类型
交叉类型将多种类型组合成一种类型。当我们想将现有类型组合成新类型时,这非常有用。
假设我们有发动机类型和颜色类型。我们想使用这些类型创建一个汽车类型,让我们来实现它。
type Author = {name: string, group:string}
type Admin = {name:string, privilege: string[] }
type User = Author & Admin; //{ name: string, group: string, privilege: string[]}
这非常直观,但请确保类型相似的属性具有相同的类型。
type A = {points: string}
type B = {points: number}
type C = A & B //Will throw an error since points properties have different types.
在使用 React 时,经常会遇到这种情况,即通过将自定义 props 与元素预定义的 props 结合起来。
type Props = {
color: string;
};
const Button: React.FC<Props & React.HTMLAttributes<HTMLButtonElement>> = ({
children,
style,
color,
...props
}) => (
<button style={{ ...style, color }} {...props}>
{children}
</button>
);
// later
<Button color="red" onClick={()=>{}} >Click Here</Button>
绝不
“从不输入”是一个很奇怪的规则,TypeScript 文档是这样定义的:
never 类型表示永远不会出现的值类型。
根据定义,Never 类型表示该值永远不会出现,甚至不会出现 undefined。
我们来看一些例子。
const OneFunction = (param):never=> { // Will throw an error, since it returns undefined
console.log(22) // But we said it should never return.
}
const SecondFunction = (param):never => {
while(true){ // Now you see we never returns anything.
}
}
const Third Function = (param):never => {
throw new Error('Shit happens man.')
}
从上面的例子中我们可以注意到两个规律。如果函数会无限循环运行或者中断程序执行,我们应该使用 `never` 类型。我们还可以使用 `never` 来编写自定义错误处理程序。如果你还有其他使用场景,请告诉我,我使用 `never` 的次数不多。
类型断言
通过类型断言,我们可以告诉 TypeScript:“嘿,兄弟,这个变量的类型就是这样,你不需要进行类型推断。” TypeScript 知道在这种情况下你更清楚,所以它会按照你的指示行事并完成任务。
let a:any= 213 // this is assigned to 'any' type.
let b:number = a as number // using the as keyword, we can assert typescript
// is for sure a number
我们还可以使用类型断言来使变量不可变。
let a = {name:"alex", age:10}
a.name = "Jhon" // expected behavior
let b = {name:"alex", age:10} as const;
b.name = "jhon" //Typescript compiler will yell at you for mutating it.
未知
“未知”比其他任何方案都好。未知类型的变量不能赋值给任何值,但我们可以将其赋值给其他变量。
“未知”比其他任何方案都好。未知类型的变量不能赋值给任何值,但我们可以将其赋值给其他变量。
好了,该举例子了。
let a:unknown
let b:number = 5
a=b // works.
b=a // Throws an error.
我们可以通过两种方式处理未知类型:缩小范围或使用类型断言。
let a:unknown;
let b:number = 5
if(typeof a === 'number'){
b = a // it works
}
b = a as number;// it works too.
映射类型
映射类型接受一个现有类型,并将其属性映射到另一个类型。
const Type<T> = { [P in keyof T]: P }
//Keys //Types
我希望它的理解是:对于键为 T(类型为 T 的属性)的每个P(属性):赋予它这种类型。
keyof 运算符表示我们正在查找类型为 T 的所有属性。
我们可以使用映射类型来实现所有神奇的功能,我们可以创建部分类型、必需类型等等。
type Partial<T> = {
[P in keyof T]?: T[P];
// Keys of T type of each property
};
type Required<T> = {
[P in keyof T]: T[P];
// Keys of T type of each property
// The absense of ? means that every property is required in this Type.
};
type Pick<T, K extends keyof T>= {
[P in K]: T[P]
// P = Properties Selected assigned to generic variable called K
}
// Examples
type User = {
name: string,
email: string,
age?: number
}
let user:User = {name:"Alex", email:"example@test.com"} // Works fine.
let partialUser:Partial<User> = {} // Works fine, since all properties are not required.
let requiredUser: Required<User> = {name: 'Alex'} // throws an error, name and age are required.
requiredUser = {name: "Alex", email: "example@test.com" ,age:12} // Works fine.
let userContactInfo: Pick<User, 'name' | 'email'> = {age:23} // Error. We only picked name and email properties.
userContactInfo = {name:"Alex", email:"example@test.com"}
TypeScript 内置了这些以及其他映射类型。欲了解更多信息,请阅读Arthur Vincent Simon 的文章《TypeScript 实用类型》。
条件类型
条件类型一开始可能有点棘手,很难看出它的用途。首先,我们来看一个它能解决的问题。
我们有一个函数,它接受一个字符串或 null 作为参数,并返回字符串或 null。如果传入字符串,我们希望它返回一个字符串,否则返回 null。TypeScript 似乎能够很直观地推断出这一点。
const toLowerCase = (str:string|null):string|null => str ? str.toLowerCase() : null;]
const name = toLowerCase('Alex') // name is a string or null
说实话,一开始没成功确实挺让人意外的,或许 TypeScript 团队应该试试 GPT 3 这个东西。它看起来很有前景。
总之,这正是条件类型大显身手的好地方。
const toLowerCase = <T extends string | null>(
str: T
): T extends string ? string : null => str ? str.toLowerCase() : null;
//Throws an error
//Type 'string' is not assignable to type 'T extends string ? string : null'.
const name = toLowerCase('Alex')
哎呀,果然不行,我知道这很令人失望,显然 TS 无法对使用类型参数定义的条件返回类型的函数的返回值进行类型检查。
对于这种用例,我们可以使用函数重载。
function toLowerCase(str: null): null;
function toLowerCase(str: string): string;
function toLowerCase(str: string | null): string | null {
return str ? str.toLowerCase() : null;
}
const res = toLowerCase(null) //null
const res = toLowerCase('Alex') //string
虽然不完美,但总算完成了任务。我很期待您就如何处理这个问题提出意见。
我一路学到的技巧。
类型保护
类型守卫在需要限定类型范围时非常有用,你可能已经知道 JavaScript 中的 typeof 运算符。实际上,它在 TypeScript 中也可以用作类型守卫。
const logString = (a: unknown): void =>{
if(typeof a==='string'){
console.log(a) // a is infered as string thanks to the guard we wrote.
}
}
我们还可以定义自定义类型保护。
type Car = {
engine: string;
};
const tesla = {
engine: "electric"
};
const isCar = (car: Car): car is Car => {
return typeof car.engine === "string";
};
if (isCar(tesla)) {
console.log(tesla.engine); // 'electric' and tesla is a car in this block.
}
从常量派生类型
我从这个精彩的演讲中借鉴了这个想法。通过定义一个常量变量,我们可以从中推导出类型。我们有一个图标系统,可以轻松地推导出它的类型,从而减少每次添加新图标时更新类型的工作量。
const Icons = {
twitter: ()=>//..,
github: ()=>//..
devto: ()=>//..
} as const
type Icon = keyof typeof Icons // "twitter" | "github" | "devto";
使用类型注释
每当开发者为参数或属性定义其用途时,我都会为他们祈祷。每种类型都附上注释,这让我从盲目乐观中清醒过来。这也有助于消除各种假设。
type Car = {
/** It powers the car. */
engine: string,
/** Timer for self-destruction. Starts during launch. */
selfDesruct:number
}
const getCar = (car:Car)=>{}
在哪里可以找到第三方库的类型?
通常情况下,库都支持 TypeScript,但如果不支持,请搜索你的库名称 + @types,希望你能找到。
你可以在推特上关注我@AmineJv(我很快就会开始发推文,是时候浴火重生了。)
文章来源:https://dev.to/aminejvm/cool-stuff-with-typescript-2aah

