Typescript 日常使用的技巧和窍门
我经常看到 JS 开发者在 Typescript 中创建合适的类型时遇到困难。有些人使用常见的类型any
,而另一些人则使用不明确的类型。
首先,我想强调的是,良好的类型定义可以帮助你减少思考,并减少检查实现的时间。在函数式编程中,函数定义也因同样的原因而至关重要。你的类型应该清晰明确,并严格定义数据的结构。
今天,我们将探讨一些有关如何使用某些实用程序类型的技巧以及一些可以在日常生活中为您提供帮助的额外案例。
挑选和省略
这两个实用程序是 Typescript 自带的实用程序的一部分。它们有助于防止我们每次需要类似功能时都需要重写接口。让我们通过一个实际的例子来看一下。
想象一下,我们正在创建一个用于某些组件的商店。
interface Stores {
user: User,
theme: Theme
// ... some others
}
如果我们想要定义我们的组件的道具,并且这些道具也附带一些商店,我们不需要像那样复制它:
interface AvatarProps {
user: User,
rounded: boolean
// ... some others
}
相反,我们可以使用这些实用类型来防止重复这些类型,并减少一些错误,例如为用户属性添加另一种类型。
interface AvatarProps extends Pick<Stores, "user">{
rounded: boolean
// ... some others
}
Pick
util 只会创建一个新类型,其键值必须与我们传入的第二个类型匹配。想象一下,一个函数有两个参数,第一个参数是整个类型,第二个参数是一个包含我们需要“选择”的名称的并集。记住,并集是两个或多个类型的并集,在本例中,我们使用一个固定的字符串来匹配每个键。
interface Foo {
key1: number,
key2: number,
key3: number
}
type FooPicked = Pick<Foo , "key1" | "key2">
/*
This will result in a type like that:
interface FooPicked {
key1: number,
key2: number
}
*/
Omit
util 的作用相同,但顺序相反。我的意思是,它不会取出所有与并集匹配的键,而是“忽略”所有与并集匹配的键。
interface Foo {
key1: number,
key2: number,
key3: number
}
type FooOmited = Omit<Foo , "key1" | "key2">
/*
This will result in a type like that:
interface FooOmited {
key3: number
}
*/
部分的
我们之前讨论过 store,所以让我们继续讨论。在这种情况下,让我们考虑 action、mutation 或任何会执行更新的操作。例如,让我们以 React 在类中使用的旧 setState 为例。
// state
this.state = {
foo: "foo",
bar: "bar"
}
// mutation
this.setState({
foo: "foo"
})
setState 方法只需要接收整个状态的一部分,但我们不能使用 Pick 或 Omit,因为我们不知道哪个键会被省略。因此,对于这些情况,我们需要发送一个“部分接口”,它将与整个接口合并。
// state
interface State {
foo: string,
bar: string
}
// mutation
type SetState = (value: Partial<State>) => State;
Partial
但幕后究竟做了什么呢?其实并没有那么复杂。它只是为每个一级属性添加了一个可选值。
// state
interface State {
foo: string,
bar: string
}
type PartialState = Partial<State>;
/*
This will result in a type like that:
interface PatialState {
foo?: string,
bar?: string
}
*/
您可能会发现其他需要使用它的情况。只需记住,仅将可选项放在第一级属性中,如果您有嵌套对象,则子属性将不受此实用程序的影响。
只读
如果您喜欢使用不可变数据,也许您会喜欢这个关键字。TypeScript允许您确定对象的哪些属性可以修改,哪些不能修改。继续使用 stores,如果您使用 Flux 架构,您不希望状态被修改,而只想在每次操作中重新创建状态。
因此,对于这些情况,将这些属性设置为只读是有帮助的,因为如果有人试图修改它,它就会引发错误。
interface Stores {
readonly user: User,
readonly theme: Theme
// ... some others
}
此外,您可以使用 Readonly 实用程序
type ReadonlyStores = Readonly<Stores>
当您尝试修改任何值时,您将看到一条错误消息。
const store: ReadonlyStores = {
user: new User(),
theme: new Theme(),
// ... some others
}
stores.user = new User()
// Error: Cannot assign to 'user' because it is a read-only property.
重要的
此检查会在编译时抛出错误,但不会像 TypeScriptconst
那样在运行时抛出错误。这意味着,如果您的代码没有被 TypeScript 跟踪,它很容易在运行时修改您的属性。只需避免跳过文件中的 TypeScript 规则即可。
巧妙使用推断类型
TypeScript 拥有非常强大的推断算法。这意味着有时我们不需要明确指定变量的类型,因为它会直接为你提供类型信息。
let a = "a" // Typescript infer that it will be a "string"
a = 3 // It'll throw an error
// Just need to specify the type if you are not passing a value to the variable
let a: string;
a = "a"
// In another way it will be typed as any
let a; // typescript typed as any (some config will prevent this automatic any type)
a = "a"
a = 3 // it will NOT throw an error
我们可以利用这个超能力为自己谋利。继续我们的商店,而不是像那样创建界面……
interface Stores {
user: User,
theme: Theme
// ... some others
}
const stores: Stores = {
user: new User(),
theme: new Theme()
}
...我们可以将责任交给 typescript 来自动创建它。
const stores = {
user: new User(),
theme: new Theme()
}
type Stores = typeof stores;
Commontypeof
关键字在 TypeScript 中焕发了新的活力。它将返回 TypeScript 根据变量声明推断出的类型。所以这两段代码的作用是一样的。
我喜欢这个功能,因为在这种情况下,类型完全依赖于声明。如果添加新字段,只需在声明中添加,它就会立即传递到类型中。相比手动创建接口,你需要自己传递,这可能会带来一些错误。
结论
TypeScript 很棒,但正如您在 readonly 和 const 之间看到的差异,TypeScript 只是为开发人员创建了一个保护层,以使代码对所有人都更安全。但生成的 JS 代码不会遵循这些规则。因此,它可能会修改 readonly 属性或访问私有属性,因为它只是在您编写代码时的一个保护层。
另外,如果您使用类来私有化某些方法或属性,那么在编译之前它们只是“私有”的。如果您确实想使用私有值,可以使用闭包工厂,这也可以稍微减少编译后代码的包大小,因为现在不需要像使用类时那样进行编译。如果您正在寻找这方面的示例,请查看 Mark Erikson 在 react-redux 订阅中所做的重写。
当你使用打字稿时记住这一点,它将帮助你了解幕后发生的事情。
感谢您阅读这篇文章。希望它能对您的日常工作有所帮助。
如果您想了解更多,我强烈推荐 Typescript 实用程序文档。
https://www.typescriptlang.org/docs/handbook/utility-types.html
文章来源:https://dev.to/daniacu/tips-and-tricks-for-typescript-to-daily-use-1cca